Docker Multi-Stage Builds
The Problem with Single-Stage Builds
A typical Go Docker image built with a single stage can be over 1GB because it includes the entire Go toolchain, source code, and build cache — none of which are needed at runtime.
# ❌ Single-stage: image is ~1.2GB
FROM golang:1.22
WORKDIR /app
COPY . .
RUN go build -o server .
CMD ["./server"]
Multi-Stage to the Rescue
Multi-stage builds let you use one stage for building and another for running:
# ✅ Multi-stage: image is ~15MB
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o server .
FROM alpine:3.19
RUN apk --no-cache add ca-certificates
COPY --from=builder /app/server /server
EXPOSE 8080
CMD ["/server"]
Even Smaller with scratch
For fully static binaries, you can use the scratch base image (literally empty):
FROM scratch
COPY --from=builder /app/server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
CMD ["/server"]
This produces images as small as 5–10MB.
Build Arguments and Caching
Use build arguments for versioning and leverage Docker’s layer caching:
ARG VERSION=dev
RUN go build -ldflags="-X main.version=${VERSION}" -o server .
Summary
Multi-stage builds are essential for production Go containers. They reduce image size by 100x, minimize the attack surface, and speed up deployments.