How to use multi-stage Docker builds to shrink production images
Multi-stage builds compile in one stage and copy only artifacts into a slim runtime stage, keeping compilers and dev dependencies out of production images. Use COPY --from and --target to control output.
Multi-stage Docker builds
A multi-stage build uses several FROM statements in one Dockerfile. Early stages compile or bundle the application; the final stage copies only the resulting artifacts. Build tools, source code, and dev dependencies never reach production, so the image is smaller and safer.
Prerequisites
- Docker installed.
- An application that has a build step (compiled binary, bundled assets, or transpiled output).
Steps
1. Identify build vs runtime needs
List what is needed only to build (compilers, SDKs, dev packages) versus what is needed to run (the binary or bundle, plus a minimal runtime). Only the second set belongs in the final image.
2. Define a build stage
Name the stage with AS:
FROM golang:1.22 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app ./cmd/server
3. Define a slim runtime stage
Start a fresh, minimal base for the final image:
FROM alpine:3.20
RUN adduser -D appuser
USER appuser
For a static binary you can use an even smaller base later.
4. Copy artifacts between stages
Use COPY --from to pull the built artifact into the runtime stage:
COPY --from=build /out/app /usr/local/bin/app
ENTRYPOINT ["/usr/local/bin/app"]
The full Dockerfile combines all stages in order.
5. Build a specific target
During development you can stop at an intermediate stage:
docker build --target build -t myapp:build .
This is useful for running tests inside the build stage without producing the runtime image.
6. Compare final image sizes
Build the full image and inspect:
docker build -t myapp:multi .
docker images myapp
The multi-stage image typically drops from hundreds of megabytes to tens.
Verification
Run docker run --rm myapp:multi. The application should start without any build tools present. Confirm with docker run --rm myapp:multi sh -c 'which go || echo no-go' that the Go toolchain is absent from the final image.
Next Steps
Replace the runtime base with a distroless image for the smallest footprint. Cache the dependency-download stage in CI to speed up builds, and add a test stage that fails the build when tests fail.
Prerequisites
- Familiarity with Dockerfiles
- A compiled or bundled application
Steps
- 1Identify build vs runtime needs
- 2Define a build stage
- 3Define a slim runtime stage
- 4Copy artifacts between stages
- 5Build a specific target
- 6Compare final image sizes