What does building with -ldflags '-extldflags "-static"' do, and why does glibc warn about getaddrinfo?
answer
- two levels of forwarding, not a Go flag
- the C toolchain's linker receives it
- static link, no loader needed at startup
- C lookups may open modules at runtime
- the failure moves from startup into a request
basics
~20 sIt forwards -static to the external C linker, so a cgo build links the C library into the executable instead of loading it at startup. glibc still opens its lookup modules at run time, so it warns.
solid answer
~40 sWhen cgo is in the build, the Go linker hands off to the system C toolchain. `-ldflags '-extldflags "-static"'` forwards `-static` to that external linker, so the C library is linked into the executable and the result has no dynamic loader dependency — a way to get a self-contained artifact without giving up cgo. The catch is glibc-specific: `getaddrinfo` and the password-database functions go through the name-service switch, which loads its backends as shared objects at run time. Linking statically does not remove that, so glibc emits the familiar warning that such a program needs the matching shared libraries at run time, and lookups can fail on a host that has none. Practically, either build against a C library designed for static linking, or drop cgo entirely with `CGO_ENABLED=0` and accept the pure-Go lookups.
code
text · 7 lines$ go build -ldflags '-extldflags "-static"' -o app .
# warning: Using 'getaddrinfo' in statically linked applications
# requires at runtime the shared libraries from the glibc version
# used for linking
$ file ./app
./app: ELF 64-bit LSB executable, x86-64, ... statically linked, ...go deeper
You are unlikely to be asked this, but recognise the shape: -ldflags carries options to the Go linker, and -extldflags forwards its value on to the C toolchain's linker.
Explain the two levels of forwarding and what static linking buys — no dynamic loader at startup — and be able to check the result with file or ldd rather than trusting the flag.
Name the caveat without prompting: some C libraries resolve names by loading modules at run time, so a static link moves the failure from startup into a request. Say what you would run to verify, and why you would prefer disabling cgo.
Treat an inherited build line carrying this flag as a decision nobody currently owns. Decide whether the dependency forcing cgo is worth the exception, and who verifies lookups on a production-like host before each release.
## Where the flag goes Read the nesting carefully, because it is the whole point of the question: ``` go build -ldflags '-extldflags "-static"' -o app . ``` `-ldflags` passes options to the **Go linker**. `-extldflags` is one of those options, and its value is passed on to the **external linker** — the system C toolchain's linker that the Go linker invokes when cgo is in the build. So `-static` is not a Go flag at all; it is a C-toolchain flag being forwarded two levels down. Using `-extldflags` also implies external link mode, which is already what a cgo build uses. The purpose is to get the one property most people want from `CGO_ENABLED=0` — a binary with no run-time dependency on a C library — in a build that still needs cgo for something else. ## Why glibc complains During such a link, glibc typically emits a warning of the form: *Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking.* People see it, note that the binary was produced anyway, and ship. The warning is accurate and it matters. glibc implements hostname and user lookups through the **name service switch**: `getaddrinfo` and `getpwnam` do not contain the lookup logic themselves; they consult a configuration file and then load a backend module — files, DNS, a directory service — as a *shared object*, at run time. Static linking cannot fold that in, because the module to load is decided when the program runs, not when it is linked. So a statically linked glibc program is static in the ordinary sense and still dependent on shared objects for exactly the two lookups this leaf cares about. On a host that has the right glibc shared objects at compatible versions, it works. On a host that has none — a machine carrying nothing but your binary — the lookups fail, or fail for some sources and not others. That is a *worse* failure mode than the dynamic build, because it happens at lookup time, deep in a request, instead of at startup. ## How to decide There is an order of preference here worth being able to state: 1. **`CGO_ENABLED=0`.** No C, no external linker, a genuinely self-contained binary, and lookups done by Go code whose behaviour is the same everywhere. Take this unless something forces cgo. 2. **cgo, built against the target's C library.** Keep the dynamic link and make the build environment match the run environment. Honest coupling, no surprises, but your builder is now tied to your target. 3. **cgo plus `-extldflags "-static"` against a C library designed for static linking.** Some C libraries implement lookups without loading modules at run time, and for those the static link behaves as advertised. 4. **cgo plus `-extldflags "-static"` against glibc.** Works for programs that never resolve a hostname or a user; a latent trap for anything that does. Most teams that reach option four wanted option one and did not realise a dependency had turned cgo on. ## Verifying rather than believing A static link is easy to check: `file` on the artifact says `statically linked` and names no interpreter; `ldd` reports that it is not a dynamic executable. What that check cannot tell you is whether a lookup will succeed at run time, because the shared objects are opened lazily and only along the code paths that need them. So a passing `ldd` plus a program that never resolves anything in its smoke test is exactly how this ships. The honest verification is a run: exercise the actual hostname and user lookups on a host that resembles production, and watch them succeed. Combined with an A/B of the same code built with `CGO_ENABLED=0`, it tells you both whether the static build works and whether you needed cgo at all. ## Why it is worth knowing anyway You will meet this flag in inherited build scripts, usually copied from somewhere with no comment attached. Being able to say what each level of the nesting does — Go linker, external linker, C library — and to name the one caveat that makes it dangerous is a good demonstration that you understand where the boundary between Go's toolchain and the system toolchain sits.
- The static build passes file and ldd but a hostname lookup fails in production. Why did the checks not catch it?Because `file` and `ldd` describe load-time dependencies, and the modules glibc needs for lookups are opened lazily, at lookup time, along code paths a smoke test may never take. The only check that covers it is exercising the real lookup on a host that resembles production.
- Given the caveat, when would you still choose -extldflags "-static" over CGO_ENABLED=0?When cgo is genuinely required by a dependency and the C library you link against does not resolve names by loading modules at run time. Then the static link delivers what it promises. With glibc and a service that resolves hostnames, the honest choice is to remove the cgo dependency or to match the build environment to the target.
saying these in an interview costs you the question
- Thinks -extldflags is an option of the Go linker itself
- Treats the glibc warning as noise to be suppressed
- Assumes a static link removes every run-time dependency
- Believes ldd proves lookups will work in production