A Practical Guide to Docker Multi-Stage Builds
The Problem with Docker Images
A typical Go application compiles to a 10MB binary. But a naive Docker image can easily balloon to 800MB+ because it includes the entire Go toolchain and build dependencies.
Enter Multi-Stage Builds
Multi-stage builds let you use one image to build your application and a different, minimal image to run it:
# Stage 1: Build
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o server .
# Stage 2: Run
FROM alpine:3.19
RUN apk add --no-cache ca-certificates
COPY --from=builder /app/server /server
EXPOSE 8080
CMD ["/server"]
The final image is only ~15MB — the size of Alpine plus your binary.
Why This Matters
Smaller images mean:
- Faster deployments — pulling 15MB vs 800MB is a huge difference
- Better security — fewer packages = fewer CVEs
- Lower costs — less storage and bandwidth in your container registry
A More Advanced Example
For a React frontend with a Go backend:
# Stage 1: Build frontend
FROM node:20-alpine AS frontend
WORKDIR /app
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/ .
RUN npm run build
# Stage 2: Build backend
FROM golang:1.23-alpine AS backend
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
COPY --from=frontend /app/dist ./static
RUN CGO_ENABLED=0 go build -o server .
# Stage 3: Run
FROM scratch
COPY --from=backend /app/server /server
COPY --from=backend /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
CMD ["/server"]
Using FROM scratch gives you the absolute minimal image — just your binary and nothing else.
Pro Tips
Order your COPY commands carefully — Docker caches layers. Copy
go.modandgo.sumfirst, then rungo mod download, THEN copy source code. This way, dependency downloads are cached unless dependencies change.Use
.dockerignore— excludenode_modules,.git, and build artifacts from your build context:node_modules .git *.log distDon’t run as root — add a non-root user in your final stage:
RUN addgroup -S app && adduser -S app -G app
USER app
Wrapping Up
Multi-stage builds are one of Docker’s best features. They keep your images small, secure, and production-ready without complex build scripts. If you’re not using them yet, start today!