Name each stage with AS in your Dockerfile, then build up to the stage you want using the target option of docker build. Keep a lean production stage as the default and put debug tools or test data in separate stages, so they never ship by accident.
What Docker says
Docker's multi-stage documentation explains that each FROM starts a new build stage, and that you can copy selected artifacts between stages, leaving behind what you do not want in the final image. Adding AS and a name lets you refer to a stage in COPY by name, and the names keep working if you reorder instructions later. The target option builds only up to the named stage. The page suggests it for a debug stage with extra tools next to a lean production stage, or a testing stage with test data.
With BuildKit, only the stages the target depends on are processed. The legacy builder processes every stage up to the target.
A layout that works
| Stage | Purpose | Ships to production |
|---|---|---|
| base | Runtime, user, shared settings | Yes, as the parent |
| build | Compilers and dev packages | No |
| test | Test tools and fixtures | No |
| debug | Shell and diagnostic tools | No |
| production | Runtime plus built output | Yes |
FROM runtime-base AS base
FROM base AS build
RUN make app
FROM build AS test
RUN make test
FROM base AS debug
RUN apt-get update && apt-get install -y strace
FROM base AS production
USER 10001
CI builds the test target on every pull request, and the release job builds production. Developers build debug when they need a shell.
Security benefits
- Production contains no compilers, package managers beyond what runs the app, or test data, so there is less to patch and less for an attacker to use.
- Test fixtures with realistic data never reach a registry that production pulls from.
- Debug tooling is an explicit choice, not something left in by default.
The Docker page does not use the words attack surface, but notes that none of the build tools are included in the resulting image. See multi-stage builds and attack surface.
Pitfalls
- Making the debug stage the last stage in the file. Without a target option, a plain build produces the last stage, so put production last or always pass a target.
- Tagging debug and production images with the same name. Use distinct tags, and push only production to the release repository.
- Copying everything from the build stage. Copy only the built output. The wider the copy, the more leaks through.
- Scanning only the build stage. Scan the image you ship.
Verify
After each build, list the image contents or generate an SBOM and confirm the production image lacks the tools you expected to leave behind.
Quick check
Which stage does a build produce with no target?
The last stage in the Dockerfile. Put production last, or always name the target.
Do unused stages cost build time?
With BuildKit, only stages the target depends on are built. The legacy builder processes every stage up to the target.
Safeguard
Safeguard does SBOM and dependency scanning, and its policies and gates can run in CI so a bad image does not reach production. You can try a free scan before deciding anything.
Sources
Last reviewed: 19 October 2026.