<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Weifeng Liu</title>
    <description>The latest articles on DEV Community by Weifeng Liu (@weifeng_liu_57eea5c2123f9).</description>
    <link>https://dev.to/weifeng_liu_57eea5c2123f9</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4243676%2Fd414bee8-fef3-46f2-aceb-2f25bea1448b.jpg</url>
      <title>DEV Community: Weifeng Liu</title>
      <link>https://dev.to/weifeng_liu_57eea5c2123f9</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/weifeng_liu_57eea5c2123f9"/>
    <language>en</language>
    <item>
      <title>ldd says 'not a dynamic executable' — your static binary is fine</title>
      <dc:creator>Weifeng Liu</dc:creator>
      <pubDate>Fri, 13 Nov 2026 23:39:10 +0000</pubDate>
      <link>https://dev.to/weifeng_liu_57eea5c2123f9/ldd-says-not-a-dynamic-executable-your-static-binary-is-fine-mkf</link>
      <guid>https://dev.to/weifeng_liu_57eea5c2123f9/ldd-says-not-a-dynamic-executable-your-static-binary-is-fine-mkf</guid>
      <description>&lt;h2&gt;
  
  
  Short answer
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ldd&lt;/code&gt; reads the dynamic section of an ELF file. A &lt;strong&gt;statically linked&lt;/strong&gt; binary has none,&lt;br&gt;
so &lt;code&gt;ldd&lt;/code&gt; reports &lt;code&gt;not a dynamic executable&lt;/code&gt; and exits 1. &lt;strong&gt;The binary is not broken.&lt;/strong&gt;&lt;br&gt;
It simply does not need a loader at runtime.&lt;/p&gt;

&lt;p&gt;This is the most common false alarm in Linux binary troubleshooting: people read it as&lt;br&gt;
"the executable is invalid" and start looking for a missing library that does not exist.&lt;/p&gt;
&lt;h2&gt;
  
  
  Confirm it in three commands
&lt;/h2&gt;

&lt;p&gt;Take any static binary — a Go or Rust build, or &lt;code&gt;busybox&lt;/code&gt; from a static image — and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;$ ldd ./hello-static
        not a dynamic executable
$ &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt;
1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;$ file ./hello-static
./hello-static: ELF 64-bit LSB executable, x86-64, version 1 &lt;span class="o"&gt;(&lt;/span&gt;SYSV&lt;span class="o"&gt;)&lt;/span&gt;,
statically linked, Go &lt;span class="nv"&gt;BuildID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rLXBd7VvG-TozsbigJ3a/..., stripped
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;$ readelf -d ./hello-static
There is no dynamic section &lt;span class="k"&gt;in&lt;/span&gt; this file.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;file&lt;/code&gt; says &lt;strong&gt;statically linked&lt;/strong&gt; and &lt;code&gt;readelf -d&lt;/code&gt; says &lt;strong&gt;no dynamic section&lt;/strong&gt;. Both agree&lt;br&gt;
with &lt;code&gt;ldd&lt;/code&gt;. Nothing is missing.&lt;/p&gt;
&lt;h2&gt;
  
  
  What the message actually means
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Says&lt;/th&gt;
&lt;th&gt;Because&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ldd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;not a dynamic executable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no &lt;code&gt;DT_NEEDED&lt;/code&gt;, no &lt;code&gt;PT_INTERP&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;file&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;statically linked&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;same reason, different wording&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;readelf -d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;There is no dynamic section in this file.&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the &lt;code&gt;.dynamic&lt;/code&gt; section is absent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;`readelf -l \&lt;/td&gt;
&lt;td&gt;grep -c INTERP`&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Contrast with a dynamically linked binary on the same host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;$ readelf -l ./hello-new &lt;span class="o"&gt;|&lt;/span&gt; grep -A1 INTERP
  INTERP         0x0000000000000318 0x0000000000000318 0x0000000000000318
$ readelf -d ./hello-new &lt;span class="o"&gt;|&lt;/span&gt; grep NEEDED
 0x0000000000000001 &lt;span class="o"&gt;(&lt;/span&gt;NEEDED&lt;span class="o"&gt;)&lt;/span&gt;   Shared library: &lt;span class="o"&gt;[&lt;/span&gt;libstdc++.so.6&lt;span class="o"&gt;]&lt;/span&gt;
 0x0000000000000001 &lt;span class="o"&gt;(&lt;/span&gt;NEEDED&lt;span class="o"&gt;)&lt;/span&gt;   Shared library: &lt;span class="o"&gt;[&lt;/span&gt;libm.so.6&lt;span class="o"&gt;]&lt;/span&gt;
 0x0000000000000001 &lt;span class="o"&gt;(&lt;/span&gt;NEEDED&lt;span class="o"&gt;)&lt;/span&gt;   Shared library: &lt;span class="o"&gt;[&lt;/span&gt;libgcc_s.so.1&lt;span class="o"&gt;]&lt;/span&gt;
 0x0000000000000001 &lt;span class="o"&gt;(&lt;/span&gt;NEEDED&lt;span class="o"&gt;)&lt;/span&gt;   Shared library: &lt;span class="o"&gt;[&lt;/span&gt;libc.so.6&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An &lt;code&gt;INTERP&lt;/code&gt; entry plus a list of &lt;code&gt;NEEDED&lt;/code&gt; libraries is what &lt;code&gt;ldd&lt;/code&gt; needs to do its job.&lt;br&gt;
A static binary has neither.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where this shows up in practice
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Go and Rust builds.&lt;/strong&gt; &lt;code&gt;CGO_ENABLED=0&lt;/code&gt; Go builds and musl-targeted Rust builds are static&lt;br&gt;
by default. Running &lt;code&gt;ldd&lt;/code&gt; on them is a category error, not a defect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distroless and scratch images.&lt;/strong&gt; There is no shell, no &lt;code&gt;binutils&lt;/code&gt;, sometimes no &lt;code&gt;ldd&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Static busybox.&lt;/strong&gt; &lt;code&gt;busybox --install&lt;/code&gt; links applets into a single static binary; every&lt;br&gt;
applet reports this message.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jump hosts without binutils.&lt;/strong&gt; A slim &lt;code&gt;debian:12&lt;/code&gt; image has neither &lt;code&gt;file&lt;/code&gt; nor &lt;code&gt;readelf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;$ file ./hello-static
sh: 1: file: not found
$ readelf -h ./hello-static
sh: 1: readelf: not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is exactly the situation the &lt;a href="https://dev.to/tools/elf-inspector/"&gt;ELF Inspector&lt;/a&gt; exists for — it&lt;br&gt;
parses in your browser, so the target host needs no tooling and the binary is not uploaded.&lt;/p&gt;

&lt;h2&gt;
  
  
  When &lt;code&gt;not a dynamic executable&lt;/code&gt; is a real problem
&lt;/h2&gt;

&lt;p&gt;Rarely, but it matters:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;You expected a dynamic binary.&lt;/strong&gt; If a build system silently produced a static binary,
&lt;code&gt;dlopen()&lt;/code&gt; will fail at runtime because there is no dynamic loader in the process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;glibc NSS breaks.&lt;/strong&gt; A fully static glibc binary cannot load NSS modules
(&lt;code&gt;getpwnam&lt;/code&gt;, DNS via &lt;code&gt;getaddrinfo&lt;/code&gt; can behave oddly). Use &lt;code&gt;-static&lt;/code&gt; with care, or prefer
static musl.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The binary was corrupted mid-transfer.&lt;/strong&gt; Combined with a truncated file, &lt;code&gt;ldd&lt;/code&gt; can
also report this. Check the size and a checksum if the file came over the network.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In every other case the message is informational.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to use instead
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# what kind of file is it at all&lt;/span&gt;
file ./binary

&lt;span class="c"&gt;# architecture, word size, endianness, type&lt;/span&gt;
readelf -h ./binary &lt;span class="o"&gt;|&lt;/span&gt; head -20

&lt;span class="c"&gt;# any dynamic dependencies (empty output = static)&lt;/span&gt;
readelf -d ./binary &lt;span class="o"&gt;|&lt;/span&gt; grep NEEDED

&lt;span class="c"&gt;# interpreter present?&lt;/span&gt;
readelf -l ./binary &lt;span class="o"&gt;|&lt;/span&gt; grep INTERP

&lt;span class="c"&gt;# the same information without installing binutils&lt;/span&gt;
&lt;span class="c"&gt;# -&amp;gt; open the binary in the browser ELF Inspector&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser tool reports the same fields and adds the verdicts that are easy to get wrong&lt;br&gt;
by hand: PIE, static vs shared object, full RELRO, stripped, and the required&lt;br&gt;
&lt;code&gt;GLIBC_*&lt;/code&gt; / &lt;code&gt;GLIBCXX_*&lt;/code&gt; symbol versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  One safety note about &lt;code&gt;ldd&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;For &lt;strong&gt;dynamically linked&lt;/strong&gt; binaries, &lt;code&gt;ldd&lt;/code&gt; is not a pure reader. Most implementations&lt;br&gt;
execute the program's own loader (&lt;code&gt;LD_TRACE_LOADED_OBJECTS&lt;/code&gt;) to get the resolution result.&lt;br&gt;
On an untrusted binary that means running its interpreter.&lt;/p&gt;

&lt;p&gt;Static binaries are not affected — that is the one case where &lt;code&gt;ldd&lt;/code&gt; genuinely cannot&lt;br&gt;
execute anything. For untrusted files, prefer &lt;code&gt;readelf -d&lt;/code&gt; or the browser tool, which only&lt;br&gt;
parse data.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does &lt;code&gt;not a dynamic executable&lt;/code&gt; mean the binary is corrupted?&lt;/strong&gt; No. It means there is no&lt;br&gt;
dynamic section to inspect. A corrupted file usually gives a different error&lt;br&gt;
(&lt;code&gt;cannot execute binary file&lt;/code&gt;, &lt;code&gt;invalid ELF header&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does &lt;code&gt;ldd&lt;/code&gt; exit 1 then?&lt;/strong&gt; Because it could not perform the requested inspection.&lt;br&gt;
Exit status reflects the tool's success, not the binary's validity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I know whether a binary is static or dynamic without ldd?&lt;/strong&gt; &lt;code&gt;file&lt;/code&gt; prints&lt;br&gt;
&lt;code&gt;statically linked&lt;/code&gt; or &lt;code&gt;dynamically linked&lt;/code&gt;; &lt;code&gt;readelf -d&lt;/code&gt; prints the dynamic section or&lt;br&gt;
says there is none.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can a static binary still fail at runtime?&lt;/strong&gt; Yes — on a kernel that does not support its&lt;br&gt;
architecture, on a filesystem mounted &lt;code&gt;noexec&lt;/code&gt;, or when it needs files it expects to find&lt;br&gt;
(glibc static + NSS). Those are different errors than this one.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>docker</category>
      <category>programming</category>
      <category>devops</category>
    </item>
    <item>
      <title>glibc and GLIBCXX versions by distribution, measured inside 27 official images</title>
      <dc:creator>Weifeng Liu</dc:creator>
      <pubDate>Fri, 13 Nov 2026 23:37:53 +0000</pubDate>
      <link>https://dev.to/weifeng_liu_57eea5c2123f9/glibc-and-glibcxx-versions-by-distribution-measured-inside-27-official-images-2li</link>
      <guid>https://dev.to/weifeng_liu_57eea5c2123f9/glibc-and-glibcxx-versions-by-distribution-measured-inside-27-official-images-2li</guid>
      <description>&lt;p&gt;I kept hitting the same two errors on builds that worked a minute earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version `GLIBC_2.34' not found
version `GLIBCXX_3.4.29' not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The advice everywhere is "copy the library from the machine where it works". I tried&lt;br&gt;
it properly, with a real build, and it fails one layer deeper than you expect. That&lt;br&gt;
sent me looking for an actual table of what each distribution ships — and none&lt;br&gt;
existed, so I measured one.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I measured
&lt;/h2&gt;

&lt;p&gt;For each release: &lt;code&gt;ldd --version&lt;/code&gt; for glibc, and the highest &lt;code&gt;GLIBCXX_*&lt;/code&gt; symbol&lt;br&gt;
version inside &lt;code&gt;libstdc++.so.6&lt;/code&gt;. Every row came out of the official image, not from&lt;br&gt;
memory. 27 releases, each image removed immediately afterwards.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Distribution&lt;/th&gt;
&lt;th&gt;Release&lt;/th&gt;
&lt;th&gt;glibc&lt;/th&gt;
&lt;th&gt;musl&lt;/th&gt;
&lt;th&gt;Highest GLIBCXX&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ubuntu&lt;/td&gt;
&lt;td&gt;16.04&lt;/td&gt;
&lt;td&gt;2.23&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;3.4.21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ubuntu&lt;/td&gt;
&lt;td&gt;18.04&lt;/td&gt;
&lt;td&gt;2.27&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;3.4.25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ubuntu&lt;/td&gt;
&lt;td&gt;20.04&lt;/td&gt;
&lt;td&gt;2.31&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;3.4.28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ubuntu&lt;/td&gt;
&lt;td&gt;22.04&lt;/td&gt;
&lt;td&gt;2.35&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;3.4.30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ubuntu&lt;/td&gt;
&lt;td&gt;24.04&lt;/td&gt;
&lt;td&gt;2.39&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;3.4.33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ubuntu&lt;/td&gt;
&lt;td&gt;25.04&lt;/td&gt;
&lt;td&gt;2.41&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;3.4.34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debian&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;2.28&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;3.4.25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debian&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;2.31&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;3.4.28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debian&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;2.36&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;3.4.30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debian&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;2.41&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;3.4.33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alpine&lt;/td&gt;
&lt;td&gt;3.20&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;1.2.5&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CentOS&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;2.17&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;3.4.19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rocky Linux&lt;/td&gt;
&lt;td&gt;8 / 9&lt;/td&gt;
&lt;td&gt;2.28 / 2.34&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;3.4.25 / 3.4.29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AlmaLinux&lt;/td&gt;
&lt;td&gt;8 / 9&lt;/td&gt;
&lt;td&gt;2.28 / 2.34&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;3.4.25 / 3.4.29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon Linux&lt;/td&gt;
&lt;td&gt;2 / 2023&lt;/td&gt;
&lt;td&gt;2.26 / 2.34&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;3.4.24 / 3.4.33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;openSUSE Leap&lt;/td&gt;
&lt;td&gt;15.6&lt;/td&gt;
&lt;td&gt;2.38&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;3.4.34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arch Linux&lt;/td&gt;
&lt;td&gt;rolling&lt;/td&gt;
&lt;td&gt;2.44&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;3.4.36&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Full 27-row table and the method: &lt;strong&gt;&lt;a href="https://readelf.com/guides/glibc-version-by-distribution/" rel="noopener noreferrer"&gt;readelf.com/guides/glibc-version-by-distribution&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The trap nobody mentions
&lt;/h2&gt;

&lt;p&gt;Look at Rocky 9: &lt;strong&gt;glibc 2.34, but only GLIBCXX 3.4.29.&lt;/strong&gt; A C++ binary built on&lt;br&gt;
Ubuntu 22.04 (GLIBCXX 3.4.30) satisfies every glibc requirement there and still&lt;br&gt;
refuses to start. Two independent version gates, and people only check one.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why copying libstdc++.so.6 does not work
&lt;/h2&gt;

&lt;p&gt;This is the part worth testing rather than repeating. Built on &lt;code&gt;ubuntu:24.04&lt;/code&gt; with&lt;br&gt;
g++ 13.3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ./hello-new
built with 13.3.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same binary on &lt;code&gt;debian:12&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ./hello-new
./hello-new: libstdc++.so.6: version `GLIBCXX_3.4.32' not found
./hello-new: libstdc++.so.6: version `GLIBCXX_3.4.31' not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now copy the newer library across, keeping the SONAME:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;$ cp /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.33 ./lib/libstdc++.so.6
$ &lt;span class="nv"&gt;LD_LIBRARY_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./lib ldd ./hello-new &lt;span class="o"&gt;|&lt;/span&gt; grep stdc++
        libstdc++.so.6 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; ./lib/libstdc++.so.6      &lt;span class="c"&gt;# the copy IS used&lt;/span&gt;

$ &lt;span class="nv"&gt;LD_LIBRARY_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./lib ./hello-new
./hello-new: libc.so.6: version &lt;span class="sb"&gt;`GLIBC_2.38' not found
             (required by ./lib/libstdc++.so.6)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loader picked the bundled library, and it still failed — because that newer&lt;br&gt;
&lt;code&gt;libstdc++&lt;/code&gt; was itself built against glibc 2.38, and Debian 12 has 2.36.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The C++ runtime and the C runtime move as a pair.&lt;/strong&gt; Pulling one forward drags the&lt;br&gt;
other with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Build on the oldest image that still compiles your code.&lt;/strong&gt; One command, no guessing:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   docker run --rm -v &lt;span class="s2"&gt;"$PWD"&lt;/span&gt;:/src -w /src debian:10 g++ -O2 -o app app.cpp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Link the C++ runtime statically&lt;/strong&gt; — removes the GLIBCXX requirement entirely
(glibc stays dynamic, so you still need a compatible glibc):
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   g++ -O2 -static-libstdc++ -static-libgcc -o app app.cpp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Go fully static against musl&lt;/strong&gt; when the binary has to run anywhere:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   docker run --rm -v &lt;span class="s2"&gt;"$PWD"&lt;/span&gt;:/src -w /src alpine:3.20 \
     sh -c &lt;span class="s1"&gt;'apk add --no-cache g++ &amp;amp;&amp;amp; g++ -O2 -static -o app app.cpp'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Match the target image&lt;/strong&gt; if it only ever runs in a container.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Checking a binary without running it
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;readelf --version-info ./app&lt;/code&gt; prints the required symbol versions, and&lt;br&gt;
&lt;code&gt;grep -aoE 'GLIBCXX_3\.4\.[0-9]+' /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | sort -V | tail -1&lt;/code&gt;&lt;br&gt;
prints what the host offers. Compare the two and you know before deploying.&lt;/p&gt;

&lt;p&gt;I also built a browser-based ELF inspector that reads the same fields without&lt;br&gt;
uploading anything: &lt;strong&gt;&lt;a href="https://readelf.com/tools/elf-inspector/" rel="noopener noreferrer"&gt;https://readelf.com/tools/elf-inspector/&lt;/a&gt;&lt;/strong&gt; — useful on jump&lt;br&gt;
hosts where &lt;code&gt;binutils&lt;/code&gt; is not installed (a slim &lt;code&gt;debian:12&lt;/code&gt; image has neither &lt;code&gt;file&lt;/code&gt;&lt;br&gt;
nor &lt;code&gt;readelf&lt;/code&gt;).&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Measured 2026-11-14 on x86-64. Corrections welcome — if a row is wrong for your&lt;br&gt;
distribution, that is a bug and I want to know.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>docker</category>
      <category>cpp</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
