Multi-Stage Builds in Docker: A Complete Guide

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 FROM starts 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.

Leave a Reply