A Practical Guide to Docker Multi-Stage Builds

July 12, 2026 · DevOps

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

  1. Order your COPY commands carefully — Docker caches layers. Copy go.mod and go.sum first, then run go mod download, THEN copy source code. This way, dependency downloads are cached unless dependencies change.

  2. Use .dockerignore — exclude node_modules, .git, and build artifacts from your build context:

    node_modules
    .git
    *.log
    dist
    
  3. Don’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!

devops docker