It's the single most effective way to make production images small and secure.
Building software needs compilers, dev headers, test tools and package caches; running it needs none of that. A multi-stage build puts several FROM instructions in one Dockerfile. Each starts a new stage, and you copy just the artifacts you want from earlier stages with COPY --from=<stage>. Only the last stage becomes the image (unless you pick another), so build tools never reach production.
Typical shapes:
- Compiled languages (Go, Rust): build in
golang:1.25, copy the single binary intogcr.io/distroless/staticor evenscratch. The image drops from about 1 GB to about 10 MB. - Frontend apps:
npm run buildin a Node stage, copy thedist/folder into annginxstage. - Interpreted apps: install dependencies with build tools present (to compile native wheels), then copy the installed packages or virtualenv into a slim runtime stage.
Name stages with AS, as in FROM golang:1.25 AS build, and reference them by name.
Other useful tricks:
docker build --target test .stops at a given stage, so one Dockerfile can define dev, test and prod images.COPY --from=nginx:1.27 /etc/nginx/nginx.conf .copies from any image, not just stages.- BuildKit builds independent stages in parallel and skips stages the target doesn't depend on.
Make sure the runtime stage really has what the program needs at run time: CA certificates, timezone data, the C library if the binary is dynamically linked (static Go binaries need CGO_ENABLED=0).
Example
# syntax=docker/dockerfile:1
FROM golang:1.25 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /out/server ./cmd/server
FROM build AS test
RUN go test ./...
FROM gcr.io/distroless/static-debian12 AS prod
COPY --from=build /out/server /server
USER nonroot
ENTRYPOINT ["/server"]
# docker build -t api:prod . -> last stage (prod), ~10 MB
# docker build --target test -t api:test .Remember
- Only the final (or --target) stage becomes the image
- COPY --from=stage copies artifacts between stages
- --target lets one Dockerfile produce dev, test and prod images
- Check the runtime stage has certs, libc and data files the app needs
Watch out for
- Shipping a dynamically linked binary into scratch or distroless/static, where it fails with a confusing 'no such file or directory'.