Multi-Stage Builds
[!NOTE] This module explores the core principles of Multi-Stage Builds, deriving solutions from first principles and hardware constraints to build world-class, production-ready expertise.
1. The Bloat Problem
In the early days of Docker, if you wanted to compile a Go application, you needed a Dockerfile that started with FROM golang.
- Source Code: 5 MB
- Go Compiler + Tools: 800 MB
- Final Image: 805 MB
This is bad because:
- Size: You are shipping a compiler to production.
- Security: Compilers and build tools increase the attack surface.
2. The Solution: Multi-Stage Builds
Multi-stage builds allow you to use multiple FROM instructions in a single Dockerfile. Each FROM instruction starts a new stage. You can copy artifacts from one stage to another, leaving behind everything you don’t need.
The Syntax
# Stage 1: The Builder (Named "builder")
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp .
# Stage 2: The Runner (Starts fresh)
FROM scratch
# Copy ONLY the binary from the "builder" stage
COPY --from=builder /app/myapp /myapp
CMD ["/myapp"]
Result: A 5 MB image.
3. Real-World Examples
1. Java (Maven + JRE)
Don’t ship the JDK (Development Kit) to production. Ship the JRE (Runtime Environment).
# Stage 1: Build with Maven (Heavy)
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests
# Stage 2: Run with JRE (Light)
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=build /app/target/myapp.jar app.jar
CMD ["java", "-jar", "app.jar"]
2. Go (Golang + Scratch)
Go binaries can be statically linked, meaning they don’t even need an OS like Ubuntu or Alpine. They can run on scratch (an empty image).
# Stage 1: Build
FROM golang:1.21 AS builder
WORKDIR /src
COPY . .
# CGO_ENABLED=0 creates a static binary
RUN CGO_ENABLED=0 go build -o /bin/app .
# Stage 2: Run
FROM scratch
COPY --from=builder /bin/app /app
CMD ["/app"]
[!TIP] What is Scratch?
scratchis a special Docker image that is empty. It has no shell, no libraries, no users. It is perfect for static binaries (Go, Rust) because it offers the smallest possible footprint and zero security vulnerabilities from OS packages.
4. Interactive: The Size Crusher
Visualize how multi-stage builds strip away the fat.
Standard vs Multi-Stage Build
5. Advanced: Copying from Other Images
You can also copy files from completely different images, not just previous stages. This is useful for grabbing tools without installing them.
FROM alpine:latest
# Copy the 'terraform' binary from the official HashiCorp image
COPY --from=hashicorp/terraform:latest /bin/terraform /bin/terraform
6. Summary
- Separate: Use one stage for building (tools included) and one for running (minimal).
- Alias: Name your stages (
AS builder) for clarity. - Target: You can build a specific stage using
docker build --target builder ..