How to Set Up Multi-Stage Docker Builds for Smaller Images

Multi-stage Docker builds separate the build environment from the final runtime image, producing significantly smaller, more secure production images. This guide covers implementing this pattern effectively.

The Problem with Single-Stage Builds

FROM node:20
COPY . .
RUN npm install
RUN npm run build
CMD ["node", "dist/server.js"]

A single-stage build includes your entire build toolchain (compilers, dev dependencies, build tools) in the final image, even though the running application only needs the compiled output — results in unnecessarily large, less secure images carrying tools not needed at runtime.

The Multi-Stage Solution

FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM node:20-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]

The first stage (builder) has full build tooling; the final stage starts fresh from a minimal base image, copying only the specific build artifacts actually needed at runtime — the build tools themselves never make it into the final image.

Using an Even Smaller Base Image for the Final Stage

FROM node:20-alpine

Alpine-based images are significantly smaller than standard Debian-based images — worth considering for the final runtime stage where minimal size is valuable, though verify your application's dependencies are actually compatible with Alpine's musl libc (occasionally a source of subtle compatibility issues).

Multi-Stage Build for a Compiled Language (Go Example)

FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp

FROM alpine:latest
COPY --from=builder /app/myapp /myapp
CMD ["/myapp"]

Particularly dramatic size benefit for compiled languages — the final image needs only the compiled binary, not the entire Go toolchain, resulting in a genuinely minimal final image.

Verifying the Size Improvement

docker images myapp

Compare image size before and after adopting multi-stage builds — the improvement is often dramatic, particularly for compiled languages or applications with heavy build-time dependencies.

Security Benefits Beyond Size

A smaller final image with fewer included tools also has a reduced attack surface — build tools, compilers, and package managers present in a single-stage image are unnecessary capabilities available to an attacker who compromises a running container; multi-stage builds naturally exclude these from the runtime image.

Using Multiple Intermediate Stages

FROM node:20 AS dependencies
COPY package*.json ./
RUN npm install

FROM dependencies AS builder
COPY . .
RUN npm run build

FROM node:20-slim AS runtime
COPY --from=builder /app/dist ./dist

More complex builds can use several named stages, each building on the previous, improving both organization and caching efficiency (see How to Build Docker Images Efficiently in CI (Layer Caching)).

Common Errors

Application fails to start in the final stage despite building successfully — verify you're copying all genuinely necessary runtime files/dependencies from the builder stage; it's easy to forget a required file that was implicitly present in a single-stage build but needs explicit copying in a multi-stage setup.

Continue Reading

Browse more articles in DevOps & CI/CD.

  • multi-stage docker build, smaller docker images, docker build optimization, docker builder pattern
  • 0 Utenti hanno trovato utile questa risposta
Hai trovato utile questa risposta?

Articoli Correlati

How to Set Up a Self-Hosted GitHub Actions Runner on a VPS

GitHub Actions' hosted runners work well for most projects, but a self-hosted runner on your own...

How to Deploy Automatically on Git Push (Webhook-Based Deployment)

Automating deployment whenever you push to a specific branch removes the manual "SSH in and pull"...

How to Set Up Blue-Green Deployment on a VPS

Blue-green deployment runs two identical production environments — only one live at a time...

How to Use Ansible for Server Configuration Management

Ansible automates server configuration through simple, human-readable YAML files — letting...

Infrastructure as Code Basics: Managing VPS Config with Terraform

Terraform lets you define infrastructure (VPS instances, networks, DNS records) as code, applied...