Docker Images with Multi-Stage Builds

Docker Images with Multi-Stage Builds

- 4 mins

Docker Images with Multi-Stage Builds

If you’ve ever struggled with oversized Docker images that take forever to ship or open up security holes, then you’ll appreciate what we’re covering today: Multi-Stage Docker Builds — a smarter, cleaner, and more scalable approach to building container images.


Overview

In this tutorial, you’ll learn:

  1. What is a multi-stage Docker build?
  2. Optimizing a Node.js image
  3. Trimming down a Golang image
  4. Size & performance impact
  5. Optimization tips
  6. Best practices

🔗 Official Docker Multi-Stage Build Docs


What Is a Multi-Stage Build?

A multi-stage build uses multiple FROM layers in one Dockerfile. Each stage does a specific job — like compiling code or installing dependencies — and only the necessary artifacts are passed into the final image.

Goal: Keep your production image small, clean, and dependency-free.


Example 1: Node.js App

Traditional Dockerfile (Not Ideal)

Create a file: Dockerfile.bloat

FROM node:18

WORKDIR /app
COPY . .
RUN npm install && npm run build

CMD ["npm", "start"]

Problems:

Multi

You can see my image is 1.09GB


Multi-Stage Version

Create a new file: Dockerfile

# First stage: build
FROM node:18 AS builder

WORKDIR /app

# Copy only package files first for better caching
COPY package*.json ./

# Install all dependencies
RUN npm install

# Copy the rest of the application
COPY . .

# Run the build script
RUN npm run build

# Second stage: production image
FROM node:18-slim

WORKDIR /app

# Copy built app and node_modules from builder
COPY --from=builder /app ./

# Use npm start, same as in the traditional version
CMD ["npm", "start"]

Result:

Multi

The image is 192MB

Quick Comparison

Run:

docker images | grep -E 'nodeapp'

Multi


Example 2: Go Application

Bloated Version

Create Dockerfile.golang.bloat:

FROM golang:1.21

WORKDIR /app
COPY . .
RUN go build -o app

CMD ["./app"]

Issues:


Optimized Multi-Stage Dockerfile

FROM golang:1.21 AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app

FROM alpine:latest

WORKDIR /root/
COPY --from=builder /app/app .

CMD ["./app"]

Image Size: From ~700MB ➜ ~20MB (can drop under 5MB with scratch)


Why Multi-Stage Wins:


Pro Tips for Go Builds

Tip Description
CGO_ENABLED=0 Builds a static binary, no C libraries
GOOS, GOARCH Cross-compile for Linux, Windows, ARM, etc.
FROM scratch Minimal image with just the binary
distroless base Secure images with no package manager

Best Practices

Example .dockerignore:

.git
node_modules
*.md
tests/

Final Thoughts

Multi-stage builds are a simple yet powerful way to:


🔗 Explore more in the official Docker docs


Thanks for reading!

Guneycan Sanli

Guneycan Sanli

Guneycan Sanli

A person who like learning, music, travelling and sports.

comments powered by Disqus