Skip to main content

Thinking About Docker 2: Static Linking, Layers, and Image Efficiency

Created: April 24, 2026 CalmOps 3 min read

Introduction

A major container optimization question is whether to ship statically linked binaries or rely on dynamic libraries in the runtime image.

This decision affects image size, compatibility, security updates, and operational simplicity.

Static vs Dynamic Linking

Dynamic linking

Binary depends on shared libraries at runtime.

Pros:

  1. Smaller binary.
  2. Shared libs reused across programs.

Cons:

  1. Runtime image must include matching libraries.
  2. Dependency mismatch can break startup.

Static linking

Binary embeds required libraries into executable.

Pros:

  1. Portable runtime behavior.
  2. Easier minimal runtime images.

Cons:

  1. Larger binary.
  2. Rebuild needed for library CVE fixes.

Inspect Dependencies with ldd

Use ldd to inspect dynamic dependencies:

ldd ./app

If output says:

not a dynamic executable

the binary is statically linked.

Go and Linking Behavior

Go binaries are often close to static by default for pure Go code.

When CGO is enabled, dynamic libc dependencies can appear.

Typical static-style build:

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app ./cmd/server

Always verify with ldd after build.

C/C++ Static Linking Notes

For C/C++ static builds on glibc-based systems, you may need static libc packages (for example glibc-static on some distros).

But fully static glibc has caveats. Many teams prefer:

  1. musl-based builds for true static linking, or
  2. dynamic linking with slim runtime image that includes required libs.

Docker Layers and Storage Efficiency

Container filesystems use layered storage. A common misconception is that seeing repeated files in multiple container views means disk is linearly duplicated.

Reality:

  1. Read-only layers are shared across images when digest matches.
  2. Writable container layers are separate per container.
  3. Disk usage still grows with many near-duplicate layers.

Use these commands to inspect real usage:

docker system df
docker image ls
docker history <image>

Practical Image Optimization Strategy

  1. Multi-stage builds.
  2. Minimal runtime base images.
  3. Avoid package managers in final stage.
  4. Copy only required artifact(s).
  5. Pin base image digests.

Example multi-stage Dockerfile:

FROM golang:1.24 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o app ./cmd/server

FROM gcr.io/distroless/static
COPY --from=build /src/app /app
ENTRYPOINT ["/app"]

Security Trade-Offs

Static binaries are simple operationally, but CVE response differs:

  1. Dynamic: patch shared library in base image and redeploy.
  2. Static: rebuild binary with patched toolchain/libs and redeploy.

Both can be secure if your patch pipeline is mature.

Debugging Runtime Failures

If container starts locally but fails in production:

  1. Check architecture mismatch (amd64 vs arm64).
  2. Check libc expectations.
  3. Check missing CA certificates/timezone data in minimal images.
  4. Check executable permissions and entrypoint.

Useful checks:

file ./app
ldd ./app
docker run --rm -it --entrypoint sh <image>

When to Prefer Static Builds

  1. Single binary services.
  2. Minimal container runtime requirements.
  3. Environments with dependency drift risk.

When Dynamic Builds Are Fine

  1. Complex native dependency stacks.
  2. Existing distro-based runtime images.
  3. Teams with strong base-image patch workflows.

Conclusion

Static linking is a powerful container strategy, but it is not automatically better in every case. The right choice depends on dependency profile, security patch workflow, and operational constraints.

Use ldd and image inspection tools to make decisions based on evidence, not assumptions.

Resources

Comments

Share this article

Scan to read on mobile