Docker multi-stage builds: smaller, safer images

By LearnSome.tech · Published 2026-11-04

TL;DR

A multi-stage build uses multiple `FROM` statements in one Dockerfile, and each `FROM` can use a different base and begins a new stage of the build. None of the build tools required to build the application are included in the resulting image. You can name a stage by adding `AS <NAME>` to its `FROM` instruction, and `docker build --target build` stops at the stage named build.

What is a Docker multi-stage build?

A multi-stage build is a Dockerfile with more than one FROM statement, where each one begins a new stage of the build. Think of the stages as separate workbenches: you do the messy work on one and carry only the finished part to the next. Each FROM instruction clears any state created by previous instructions, so a new stage starts clean.

COPY --from lets you copy files from an image, a build stage or a named context instead of the build context. In Docker's example, the second FROM instruction starts a new build stage with the scratch image as its base. The Go SDK and any intermediate artefacts are left behind and not saved in the final image.

Why use multi-stage builds?

Multi-stage builds make sure the resulting output only contains the files that are needed to run the application. A small image with minimal dependencies can considerably lower the attack surface. Using multiple stages can also let you build more efficiently by executing build steps in parallel.

Keep the compiler, test tools and source files in the build stage. Aim for a runtime image that holds the app and what it needs to run, and nothing else. You also need only the single Dockerfile, with no separate build script.

How do you write a multi-stage Dockerfile?

Name each stage by adding AS and a name to its FROM instruction, then use that name in the COPY instruction. Here is a Dockerfile for a Node.js API that compiles TypeScript, with comments on what each stage is for.

# syntax=docker/dockerfile:1

# build: every dependency, including dev tools, and the compiled output
FROM node:22-bookworm-slim AS build
WORKDIR /app
# package files first: a code-only change reuses the cached npm ci layer
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

# deps: production dependencies only
FROM node:22-bookworm-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

# release: the image you ship, with no compiler and no dev packages
FROM node:22-bookworm-slim AS release
WORKDIR /app
ENV NODE_ENV=production
# paths after --from are read from the root of the named stage
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
# the official Node.js images include an unprivileged user called node
USER node
CMD ["node", "dist/server.js"]

Naming means that even if the instructions in your Dockerfile are re-ordered later, the COPY doesn't break. The source path of COPY --from is always resolved from the filesystem root of the stage you name.

If a service can run without privileges, use USER to change to a non-root user. Swap the npm commands for your own toolchain and the shape stays the same: build, select, ship.

Which base image should the final stage use?

Choose a minimal base image that matches your requirements for the stage you ship. A smaller base image shrinks the size of your image and minimises the number of vulnerabilities introduced through the dependencies. Docker recommends the Alpine image because it is tightly controlled and small in size, while still being a full Linux distribution.

Keep only the final stage small; the build stage can use a full image with every tool you like. When your program needs a language runtime, use that runtime's slim image and test that the app still starts.

How do you build or test just one stage?

You can specify a target build stage with --target, so you don't need to build the entire Dockerfile including every stage.

# the full build: produces the release stage
docker build -t orders-api .

# stop after the build stage, for example to run the tests inside it
docker build --target build -t orders-api:build .

Docker lists debugging a specific build stage and using a testing stage among the scenarios where a target is useful. With BuildKit, only the stages that the target stage depends on are built. The legacy builder processes all stages leading up to the selected target, and will build a stage even if the target doesn't depend on it.

What mistakes do people make with multi-stage builds?

Check every multi-stage Dockerfile you review against the list below.

  • Build arguments and environment variables persist in the final image, so pass secrets with secret mounts or SSH mounts instead.
  • Unrelated build stages do not have access to an ARG variable, so each stage that uses it must include the ARG instruction.
  • Image tags are mutable, so pin the base image to a specific digest when you need the same version every time.
  • A .dockerignore file excludes files not relevant to the build without restructuring your source repository.

For a secret a build step really needs, a secret mount makes it temporarily available inside the build container for the duration of that one instruction.

Free official resources

The Docker docs page on multi-stage builds shows how you use multiple FROM statements in your Dockerfile. Their building best practices page says multi-stage builds let you reduce the size of your final image. Docker's Get started guide is a 15-minute tutorial in which you run a container, start a multi-container application and package the application as an image.

From our labs

In the multi-stage lesson of our Docker course, you split a build into stages so the toolchain never ships. You then build a single stage on purpose with a target, and compare what shipped with what stayed behind. One task asks you to pass a secret to one build step without it reaching the image, its history or the logs. The lesson ends by reading the task API's finished Dockerfile line by line.

Start the free lesson: Why Containers: Bare Metal, Machines And Processes (Docker Architecture & Production Containers)

The course: Docker Architecture & Production Containers

Terms in this guide

Sources

Written by the LearnSome.tech team with AI assistance; every fact checked against the cited source.