Docker Multi-Stage Builds: Smaller, Faster, More Secure Images

Master Docker multi-stage builds to produce lean production images — reduce image size by 90%, eliminate build tools from runtime, and speed up CI pipelines.

Docker Multi-Stage Builds: Smaller, Faster, More Secure Images

The average developer Dockerfile copies source code, installs compilers, runs tests, and ships everything — including the Go compiler, npm cache, and build secrets — into the production image. Multi-stage builds fix this.

The Problem with Single-Stage Builds

Result: ~900 MB image containing the entire Go toolchain that’s only needed at build time.

Multi-Stage: The Right Way

Result: ~8 MB image. The distroless image contains only the app binary and its runtime dependencies — no shell, no package manager, no attack surface.

Optimizing Build Cache

Docker caches each layer. Put the things that change least at the top.

If you only change main.go, Docker reuses the go mod download layer. Build time drops from 3 minutes to 15 seconds.

Running Tests in the Build Pipeline

Tests are enforced in CI without a separate test step. docker build fails if tests fail.

Node.js Multi-Stage Build

node_modules from the deps stage contains only production packages. devDependencies (TypeScript compiler, test runners) never reach production.

Python Multi-Stage with Virtual Environment

Secrets in Builds — Don’t Bake Them In

Build with: docker build --secret id=pypi_token,env=PYPI_TOKEN .

Image Size Comparison

Base Image Size Use Case
ubuntu:22.04 77 MB Debugging, needs shell
alpine:3.19 7.4 MB Small Linux with shell
distroless/static 2.5 MB Go static binaries
distroless/base 20 MB Dynamically linked binaries
scratch 0 B Fully static binaries only

Security Best Practices

Multi-stage builds are not just an optimization — they’re a security practice. Shipping a Go binary in a distroless image means an attacker who escapes the container has no curl, no bash, no apt-get to work with.