Introduction
Docker has revolutionized how we build, package, and deploy applications. One of its most powerful features is multi-stage builds, which allow developers to create lightweight, production-ready images without sacrificing flexibility during development.
In this article, we’ll walk step by step through the concept, benefits, and implementation of multi-stage builds in Docker, with practical examples and commands you can run yourself.
What Are Multi-Stage Builds?
A multi-stage build is a Docker build process that uses multiple FROM statements in a single Dockerfile.
- Each
FROMstarts a new stage. - You can copy artifacts (like compiled binaries) from one stage to another.
- The final image contains only what you need for production, reducing size and attack surface.
Why Use Multi-Stage Builds?
- ✅ Smaller Images: Only essential files are included.
- ✅ Security: No unnecessary build tools in production.
- ✅ Flexibility: Use different base images for building and running.
- ✅ Maintainability: Cleaner Dockerfiles with clear separation of concerns.
Step-by-Step Example: Building a Go Application
Let’s build a simple Go app using multi-stage builds.
1. Create a Sample Go Application
mkdir docker-multistage-demo
cd docker-multistage-demo
Create a file main.go:
package main
import "fmt"
func main() {
fmt.Println("Hello, Docker Multi-Stage Build!")
}
2. Write the Multi-Stage Dockerfile
Create a file named Dockerfile:
# Stage 1: Build
FROM golang:1.21 AS builder
# Set working directory
WORKDIR /app
# Copy source code
COPY . .
# Build the binary
RUN go build -o myapp
# Stage 2: Production
FROM alpine:latest
# Set working directory
WORKDIR /app
# Copy binary from builder stage
COPY --from=builder /app/myapp .
# Command to run the app
CMD ["./myapp"]
3. Build the Docker Image
Run:
docker build -t myapp:multistage .
4. Run the Container
docker run --rm myapp:multistage
Expected output:
Hello, Docker Multi-Stage Build!
Comparing Image Sizes
Let’s see why multi-stage builds matter.
Without Multi-Stage (single stage using golang image):
docker build -t myapp:single .
docker images myapp:single
With Multi-Stage:
docker images myapp:multistage
Another Example: Node.js with Multi-Stage
For web apps, you can use multi-stage builds to separate build tools (like npm, webpack) from the final runtime.
# Stage 1: Build
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Stage 2: Production
FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
This ensures your final image only contains the static files served by Nginx, not Node.js or npm.
Best Practices
- Use Alpine or other minimal base images for production.
- Name your stages (
AS builder) for clarity. - Copy only what’s necessary (
COPY --from=builder). - Keep Dockerfiles clean and modular.
Conclusion
Multi-stage builds are a game-changer for Docker users. They help you:
- Reduce image size
- Improve security
- Simplify deployment
Whether you’re building Go binaries, Node.js apps, or complex microservices, multi-stage builds should be part of your Docker toolkit.