Docker Multi-Stage Builds: How to Shrink Your Container Images by 90% in 2026

If your Docker images are bloated and slow to deploy, multi-stage builds are the single most impactful optimization you can make. By separating your build environment from your runtime environment, you can slash image sizes from gigabytes to megabytes — often achieving a 90% reduction. In this guide, we will walk through practical multi-stage build patterns for real-world applications.

Why Image Size Matters

Large Docker images cause real problems in production:

  • Slower deployments — pulling a 2GB image across a network adds minutes to every deploy
  • Higher costs — registry storage and bandwidth add up fast at scale
  • Larger attack surface — every unnecessary package is a potential vulnerability
  • Slower CI/CD — build pipelines bottleneck on image push/pull

Multi-stage builds solve all of these by letting you use full-featured build tools during compilation, then copy only the final artifacts into a minimal runtime image.

The Basic Pattern

A multi-stage Dockerfile uses multiple FROM statements. Each one starts a new stage, and you can copy files between stages:

# Stage 1: Build
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Production
FROM node:22-alpine AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]

The builder stage has all your dev dependencies and source code. The production stage only gets the compiled output. Everything else is discarded.

Real-World Example: Go Application

Go is where multi-stage builds really shine, because Go compiles to a static binary that needs zero runtime dependencies:

# Stage 1: Build
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o server ./cmd/server

# Stage 2: Minimal runtime
FROM scratch
COPY --from=builder /app/server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
ENTRYPOINT ["/server"]

Notice we use FROM scratch — the empty base image. The final image contains literally just your binary and SSL certificates. A typical result:

  • Build image: ~1.1 GB (golang:1.23-alpine + dependencies)
  • Final image: ~12 MB (just the binary)

That is a 99% reduction.

Python Multi-Stage Pattern

Python is trickier because it is interpreted, but multi-stage builds still help enormously by separating build-time compilation from runtime:

# Stage 1: Build dependencies
FROM python:3.13-slim AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc libpq-dev && rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

# Stage 2: Runtime
FROM python:3.13-slim
WORKDIR /app
COPY --from=builder /install /usr/local
COPY . .
EXPOSE 8000
CMD ["gunicorn", "app:create_app()", "-b", "0.0.0.0:8000"]

Key trick: --prefix=/install puts all pip packages in a clean directory you can copy wholesale. The runtime image never needs gcc or other build tools.

Advanced: Three-Stage Build with Testing

You can add a test stage that runs in CI but does not affect your production image:

# Stage 1: Dependencies
FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

# Stage 2: Test (runs in CI)
FROM deps AS test
COPY . .
RUN npm run lint && npm run test

# Stage 3: Build for production
FROM deps AS builder
COPY . .
RUN npm run build && npm prune --production

# Stage 4: Production
FROM node:22-alpine
WORKDIR /app
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
USER appuser
EXPOSE 3000
CMD ["node", "dist/index.js"]

Build just the production image: docker build --target production .

Run tests in CI: docker build --target test .

Pro Tips for Maximum Optimization

1. Use Alpine or Distroless Base Images

Alpine Linux images are ~5MB versus ~130MB for Debian-based images. For even smaller images, Google Distroless provides minimal images with just a language runtime:

# Ultra-minimal Java runtime
FROM gcr.io/distroless/java21-debian12
COPY --from=builder /app/target/app.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]

2. Order Layers for Cache Efficiency

Always copy dependency files before source code. This way, npm ci or pip install is cached unless dependencies actually change:

COPY package*.json ./     # Changes rarely
RUN npm ci                 # Cached when package.json unchanged  
COPY . .                   # Changes often — but previous layers cached

3. Use .dockerignore

Keep build context small to speed up builds:

node_modules
.git
*.md
.env
dist
coverage
.github

4. Leverage BuildKit Cache Mounts

Docker BuildKit (enabled by default in 2026) supports cache mounts that persist across builds:

# syntax=docker/dockerfile:1
FROM python:3.13-slim AS builder
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

This keeps pip/npm caches between builds without bloating the final image.

Measuring Your Results

Always verify your optimization worked:

# Compare image sizes
docker images | grep myapp

# Inspect layers
docker history myapp:latest

# Deep dive with dive tool
dive myapp:latest

The dive tool is especially useful — it shows you exactly what each layer contributes and highlights wasted space.

Conclusion

Multi-stage builds are a must-have technique for any containerized application. Start with the basic two-stage pattern, then layer on optimizations like distroless bases, cache mounts, and targeted build stages. The payoff is immediate: smaller images, faster deploys, and a tighter security posture. If you have not revisited your Dockerfiles recently, now is the time.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

Privacy Policy · Contact · Sitemap

© 7Tech – Programming and Tech Tutorials