Safeguard
Developer Tools•

How do you use Docker build targets for test, debug and production?

Name your stages and build up to one with the target option. Keep debug tools and test data out of the production image while sharing common layers.

Safeguard Security Team
3 min read

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

StagePurposeShips to production
baseRuntime, user, shared settingsYes, as the parent
buildCompilers and dev packagesNo
testTest tools and fixturesNo
debugShell and diagnostic toolsNo
productionRuntime plus built outputYes
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

  1. 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.
  2. Test fixtures with realistic data never reach a registry that production pulls from.
  3. 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

  1. 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.
  2. Tagging debug and production images with the same name. Use distinct tags, and push only production to the release repository.
  3. Copying everything from the build stage. Copy only the built output. The wider the copy, the more leaks through.
  4. 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.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.