Multi-Stage Docker Builds
Learn multi-stage Docker builds to create smaller, more secure production images. Use build stages, copy artifacts between stages, and optimize image layers.
Detailed Explanation
Multi-Stage Builds
Multi-stage builds use multiple FROM statements in a single Dockerfile. Each stage can use a different base image, and you copy only the artifacts you need into the final image.
Basic Multi-Stage Pattern
# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Production
FROM node:20-alpine AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]
Building Specific Stages
Use --target to build up to a specific stage:
# Build only the builder stage (e.g., for testing)
docker build --target builder -t my-app:build .
# Build the production stage
docker build --target production -t my-app:prod .
Go Application Example
Multi-stage builds are especially effective for compiled languages:
FROM golang:1.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o server .
FROM scratch
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]
The final image contains only the compiled binary — no Go toolchain, no source code. This produces images as small as 5–15 MB.
Cache Optimization
Order your COPY and RUN instructions from least to most frequently changing:
# These layers are cached if lock files haven't changed
COPY package.json package-lock.json ./
RUN npm ci
# This layer rebuilds when source code changes
COPY . .
RUN npm run build
Use Case
Building production-grade container images for Node.js, Go, Rust, and Java applications. Reducing image size from hundreds of megabytes to tens of megabytes by excluding build tools and source code from the final image.