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.
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.
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.
See Multi-stage builds.
Test yourself
What ends up in the final image of a multi-stage build?
Every stageOnly what the last stage contains, including files copied with COPY --fromOnly the first stageNothing 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.