Introduction
Docker multi-stage builds allow you to use multiple FROM statements in your Dockerfile. Each FROM instruction can use a different base, and each of them begins a new stage of the build.
Prerequisites
- Docker installed (version 17.05+)
- Basic understanding of Dockerfiles
- A sample application (we'll use a Go app)
Step 1: The Problem with Single-Stage Builds
A typical single-stage Dockerfile for a Go application:
FROM golang:1.21
WORKDIR /app
COPY . .
RUN go build -o myapp .
CMD ["./myapp"]
This results in an image of ~800MB because it includes the entire Go toolchain.
Step 2: Multi-Stage Build Solution
Now let's use multi-stage builds:
# Build stage
FROM golang:1.21 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o myapp .
# Production stage
FROM alpine:3.19
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/myapp .
CMD ["./myapp"]
Result: Image size reduced from ~800MB to ~15MB!
Step 3: Building and Testing
# Build the image
docker build -t myapp:multi-stage .
# Check the image size
docker images myapp
# Run the container
docker run -p 8080:8080 myapp:multi-stage
Step 4: Advanced Patterns
Using Build Arguments
FROM golang:1.21 AS builder
ARG VERSION=dev
RUN go build -ldflags="-X main.version=${VERSION}" -o myapp .
Caching Dependencies
Copy dependency files first to leverage Docker layer caching:
COPY go.mod go.sum ./
RUN go mod download
COPY . .
Best Practices
.dockerignore to exclude unnecessary filesscratch or distroless for minimal imagesConclusion
Multi-stage builds are essential for creating production-ready Docker images. They reduce image size, minimize attack surface, and improve deployment speed.