Compare fully static linking (`--static --libc=musl`) with mostly-static linking (`StaticExecutableWithDynamicLibC`) for a Spring Boot native image. When would you pick each?
answer
- fully static = musl + everything → scratch
- mostly-static = glibc dynamic → distroless
- glibc static breaks NSS/getaddrinfo
- musl needs extra toolchain
- StaticExecutableWithDynamicLibC = -H: flag
basics
~20 sFully static (--static --libc=musl) links everything including libc, so the binary runs on an empty scratch image. Mostly-static links everything except glibc, which stays dynamic, so it runs on distroless (which provides glibc). Use musl-static for scratch, mostly-static for distroless.
solid answer
~40 sBoth produce self-contained-ish Spring Boot native binaries but differ in how they treat libc. **Fully static** (`--static` combined with `--libc=musl`) statically links *all* libraries, including the musl C library, yielding a binary with zero external dependencies that runs on `scratch`. It needs the **musl toolchain** installed on the build machine. **Mostly-static** (`-H:+StaticExecutableWithDynamicLibC`) links every library statically *except* the standard C library (glibc), which remains dynamically loaded — so it requires a base image that ships glibc, like **distroless**. Mostly-static exists because statically linking **glibc** is problematic (its NSS/`getaddrinfo` machinery misbehaves when static), so you keep glibc dynamic and static-link everything else. Pick musl-static when you want the absolute smallest scratch image; pick mostly-static when you're comfortable on distroless and want to avoid the musl toolchain and its edge cases.
code
kotlin · 12 linesgraalvmNative {
binaries {
named("main") {
// Option A: fully static, runs on scratch (needs musl toolchain)
// buildArgs.add("--static")
// buildArgs.add("--libc=musl")
// Option B: mostly-static, runs on distroless (glibc stays dynamic)
buildArgs.add("-H:+StaticExecutableWithDynamicLibC")
}
}
}go deeper
Know the two modes exist and their target images (scratch vs distroless).
Should articulate the glibc-vs-musl split and match each mode to its base image and toolchain.
Should explain the NSS/getaddrinfo root cause and the mutual exclusivity of the flags.
Should decide fleet-wide policy weighing toolchain maintenance, DNS reliability, and image-size targets.
## The three linking modes GraalVM offers 1. **Dynamic (default)** — the binary loads shared libraries (glibc, libz, …) from the container at runtime. Needs a base image containing them (e.g. `debian`, `ubuntu`). 2. **Fully static** — `--static`. Links *everything* statically, including libc. On Linux this only works with a **static libc**; in practice that means **musl**, selected with `--libc=musl`. Result: no external dependencies → runs on **`scratch`**. 3. **Mostly-static** — `-H:+StaticExecutableWithDynamicLibC`. Statically links all libraries **except the standard C library (glibc)**, which stays dynamic. Result: needs only glibc at runtime → runs on **distroless** images that ship glibc. ## Why musl for fully static? **glibc is not designed to be statically linked.** Its **NSS (Name Service Switch)** subsystem — used by `getaddrinfo`/`gethostbyname` for DNS and hostname resolution — dynamically `dlopen`s plugin modules at runtime. A statically linked glibc binary can't load those, so hostname/DNS lookups may fail or warn. That's why fully static uses **musl** (which has no such dynamic-plugin requirement), and why the mostly-static mode deliberately leaves **glibc dynamic**. ## Toolchain requirements - **musl-static** requires a **musl toolchain** on the builder: `musl-gcc`, and typically `zlib` compiled against musl. This is extra setup (or a prebuilt builder image / buildpack). - **mostly-static** uses the normal glibc toolchain already present — less setup. ## Configuring in Spring Boot Native Build Tools Gradle (`org.graalvm.buildtools.native`) or Maven `native-maven-plugin` both accept `buildArgs` that pass straight through to `native-image`: - Fully static: `--static`, `--libc=musl` - Mostly-static: `-H:+StaticExecutableWithDynamicLibC` (an `-H:` host option; some GraalVM versions expose `--static-nolibc`) ## Decision guide | Want | Base image | Args | Toolchain | |------|-----------|------|-----------| | Smallest possible | `scratch` | `--static --libc=musl` | musl (extra) | | Small + safe DNS, easy build | `distroless` (glibc) | `-H:+StaticExecutableWithDynamicLibC` | glibc (default) | | Simplicity, don't care about size | `debian`/`ubuntu`/temurin | none (dynamic) | none | ## Gotchas - `--static` is **Linux-only**. - Don't combine `--static` with `-H:+StaticExecutableWithDynamicLibC` — they're mutually exclusive linking strategies. - Fully static + glibc (i.e. `--static` without musl) is **discouraged**: it links a static glibc and can break DNS via NSS.
- Why is statically linking glibc discouraged?glibc's NSS (Name Service Switch) dlopens resolver modules at runtime for hostname/DNS lookups (getaddrinfo). A static glibc binary can't load them, so DNS resolution warns or fails. musl has no such dynamic-plugin need, which is why fully static uses musl and mostly-static keeps glibc dynamic.
- What extra build-machine setup does `--libc=musl` require?A musl toolchain: musl-gcc plus supporting libraries such as zlib built against musl. Without it, the native-image build fails. Buildpacks or a prebuilt builder image can supply this.
saying these in an interview costs you the question
- Claiming `--static` alone gives a safe fully-static binary on any Linux — without musl it statically links glibc and can break DNS.
- Saying mostly-static runs on scratch — it needs glibc, so it needs distroless (or another glibc-bearing image).
- Thinking musl static needs no special toolchain.