Docker Tutorial · Building Images · Lesson 10 of 26

Multi-Stage Builds

Build with full toolchains, ship only the result: small, safer images with multi-stage Dockerfiles.

Create a free account to save this page, take notes and track your progress. Sign in

Two stages, one small image

A multi-stage Dockerfile has several FROM lines. Earlier stages hold compilers and build dependencies; the final stage copies only the artifacts with COPY --from=<stage>:

FROM golang:1.26-alpine AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app .

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

The result has no compiler, no source code and no shell: smaller to pull and with far fewer packages to patch.

A Go service: a large toolchain image, a tiny final image
trap 'docker image rm -f rc-multi:1 > /dev/null 2>&1' EXIT
cat > main.go <<'EOF'
package main

import "fmt"

func main() { fmt.Println("hello from a static Go binary") }
EOF
printf 'module example.com/hello\n\ngo 1.26\n' > go.mod
cat > Dockerfile <<'EOF'
FROM golang:1.26-alpine AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app .

FROM gcr.io/distroless/static-debian13
COPY --from=build /out/app /app
ENTRYPOINT ["/app"]
EOF
docker build -q -t rc-multi:1 . > /dev/null
docker run --rm rc-multi:1
size() { docker image ls "$1" --format '{{.Size}}'; }
echo "build stage image golang:1.26-alpine: $(size golang:1.26-alpine) on disk"
echo "final image rc-multi:1:               $(size rc-multi:1) on disk"

Output

hello from a static Go binary
build stage image golang:1.26-alpine: 364MB on disk
final image rc-multi:1:               10.5MB on disk

Run this on your computer — see “Get Started” for setup.

Test yourself

What ends up in the final image of a multi-stage build?

  1. Every stage
  2. Only what the last stage contains, including files copied with COPY --from
  3. Only the first stage
  4. Nothing until you push
Show answer

Answer: Only what the last stage contains, including files copied with COPY --from

Earlier stages are discarded unless their files are copied into the final stage.

Was this page helpful?