Building Minimal Docker Images with Multi-Stage Builds
Learn how to use Docker multi‑stage builds to create tiny production images that contain only your compiled app and its runtime dependencies.
18 Aug 2025, 08:27 UTC

Desired outcome
Create a Docker image that contains only the compiled application and its essential runtime libraries, omitting build tools, source files, and intermediate layers to reduce image size and attack surface.
Prerequisites
- Docker Engine version 20.10 or newer (multi-stage builds have been supported since 17.05).
- A source code repository with a buildable project (e.g., a Go, Java, or Node.js application).
- Basic familiarity with writing Dockerfiles and running
docker build. - Access to a terminal with sufficient privileges to execute Docker commands (typically the user must be in the
dockergroup or run withsudo).
Focused procedure
- Define a builder stage – start from an image that includes the necessary build tools (e.g.,
golang:1.22for Go,maven:3.9-eclipse-temurin-17for Java). Install dependencies, compile the artifact, and place the output in a known directory. - Define a final stage – start from a minimal runtime base (e.g.,
alpine:3.20orscratchfor fully static binaries). Copy only the compiled artifact and any required runtime files from the builder stage usingCOPY --from=builder. - Write the Dockerfile – example for a Go application:
# Builder stage
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o myapp .
# Final stage
FROM alpine:3.20
WORKDIR /root/
COPY --from=builder /app/myapp .
EXPOSE 8080
CMD ["./myapp"]
Replace myapp with your binary name and adjust the base images as needed for your language.
- Build the image – run the build command in the directory containing the Dockerfile:
docker build -t myapp:latest .
Docker will output progress for each stage; no output is invented here.
Expected checks
- List the image and note its size:
docker image ls myapp. Compare this size to a single‑stage build that includes the build tools; the multi‑stage image should be markedly smaller. - Inspect layers:
docker image history myapp:latest. Verify that layers containing build tools (e.g.,golangormaven) appear only as intermediate layers and are not part of the final root filesystem. - Run a container to confirm functionality:
docker run --rm -p 8080:8080 myapp:latest
Check that the application starts, binds to the expected port, and responds correctly (e.g., returns HTTP 200 on a health endpoint). No specific output is assumed.
Recovery options
- If the build fails, examine the Dockerfile syntax and stage names; ensure the
ASalias matches the--fromreference. - Revert temporarily to a single‑stage Dockerfile to isolate whether the failure is in the builder or final stage.
- Use
docker build --no-cache .to eliminate problems caused by cached layers. - Keep the builder image (e.g., tag it
myapp-builder) to inspect intermediate files:docker create --name extract myapp-builderthendocker cp extract:/app/myapp ./. - If the final stage fails to run (e.g., missing libraries when using
scratch), switch to a base that provides the required libc (such asalpineordebian-slim) and rebuild.
Limitations and practical verification
Multi-stage builds reduce size only if unnecessary files are not copied into the builder stage. Avoid adding .git, logs, or large dependency caches to the build context; use a .dockerignore file to exclude them.
When using scratch as the final base, ensure the binary is fully statically linked; otherwise the container will exit with an error about missing shared libraries. You can verify this by running ldd myapp inside the builder stage and confirming no dynamic dependencies appear.
After building, a quick way to confirm the image contains only the intended files is to start a container and list its root filesystem:
docker run --rm myapp:latest ls -laR /
Check that no build‑tool directories (e.g., /go, /root/.m2) are present.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.