Multi-Stage Build Syntax Reference
Last reviewed on 2026-09-25
The rules for FROM … AS, COPY --from and --target, and how each builder evaluates stages.
Looking for the walkthrough? The Docker multi-stage builds tutorial has complete examples: Go and Node.js, COPY --from with --chown, dev/test/prod targets, and exporting artifacts with -o. This page lists the syntax only.
Syntax
Every FROM starts a new stage with an empty filesystem, apart from its base. The build result is the target stage: the last one in the file unless --target names another.
FROM … AS <name>
AS <name>names the stage. Other instructions refer to it by that name:FROM <name>,COPY --from=<name>,RUN --mount=from=<name>, anddocker build --target <name>.FROM <name>with the name of an earlier stage bases the new stage on that stage's result. It inherits the filesystem and metadata such asENVandWORKDIR.- Unnamed stages can be referenced only by zero-based index, and only in
--from(COPY --from=0).
FROM node:22 AS deps # stage 0, "deps"
FROM deps AS build # stage 1, based on deps
FROM node:22-slim # stage 2, unnamed, final
COPY --from=build /app/dist ./dist
Stage naming rules
| Rule | Detail |
|---|---|
| Characters | Must start with a letter, followed by letters, digits, -, _ or .. build-env, go_1.23 and prod are valid. 1st and my stage are not. |
| Case | Case-insensitive. AS Build and --from=build refer to the same stage. |
| Uniqueness | Each name may be used once per Dockerfile. BuildKit fails the build on a duplicate stage name. |
| Shadowing | A stage name takes precedence over an image of the same name. With FROM golang AS alpine, a later FROM alpine refers to the stage, not alpine:latest. Don't name stages after images. |
| Variables | The stage name itself is literal. The image part of FROM expands global ARGs: FROM golang:${GO_VERSION} AS build. |
COPY --from
--from accepts three kinds of value, resolved in this order:
| Value | Example | Copies from |
|---|---|---|
| Stage name | COPY --from=build /out/app /app | The filesystem of the named stage. |
| Stage index | COPY --from=0 /out/app /app | The nth FROM in the file, counting from 0. |
| Image reference | COPY --from=nginx:1.27-alpine /etc/nginx/mime.types /etc/ | The image, which is pulled if needed. Supported by both BuildKit and the legacy builder. |
- Source paths are absolute paths inside the source stage or image. The build context and
.dockerignoreare not involved. --chownresolves user and group names using/etc/passwdand/etc/groupof the destination stage. Inscratchand similar images, use numeric IDs (--chown=65532:65532). See COPY for--chownand--chmod.- Only files are copied. Metadata of the source stage (
ENV,USER,ENTRYPOINT) is not.
--target
docker build --target build -t myapp:build .
docker build --target export -o ./out . # BuildKit only: write the stage's files to ./out
- Builds the named stage and makes it the result, instead of the last stage.
- If the name doesn't exist, the build fails with
target stage "…" could not be found. -o/--outputchanges where the result goes (type=local,type=tar,type=image, …). It doesn't change which stages are built.
ARG scope across stages
ARG GO_VERSION=1.23 # global: usable in FROM lines only
FROM golang:${GO_VERSION} AS build
ARG GO_VERSION # re-declare (no value) to use it inside the stage
RUN echo "building with Go ${GO_VERSION}"
FROM alpine:3.20
# GO_VERSION is not set here unless declared again
- An
ARGbefore the firstFROMis global, and onlyFROMlines can see it. - An
ARGdeclared inside a stage is scoped to that stage and ends at the nextFROM. ENVis inherited only throughFROM <stage>, never throughCOPY --from.
BuildKit vs the legacy builder
BuildKit is the default builder in Docker Desktop and Docker Engine 23.0+. The legacy builder is still available with DOCKER_BUILDKIT=0, but it is deprecated. The two builders treat stages differently:
| Behaviour | BuildKit | Legacy builder |
|---|---|---|
| Which stages are built | Only the target and the stages it depends on (via FROM, COPY --from, RUN --mount=from). Unused stages are skipped. | Every stage from the top of the file down to the target, in order, whether or not the target needs it. |
| Parallelism | Independent stages run concurrently. | Sequential. |
COPY --from=<image> | Supported | Supported |
RUN --mount=from=<stage>, cache mounts | Supported | Not supported |
--output / -o | Supported | Not supported |
| Cache across stages | Per-stage graph: a miss in one stage doesn't affect an unrelated stage. | Layer chain per stage. Unrelated stages still get built. |
In practice, a test stage placed before the production stage runs on every build with the legacy builder, but only when targeted with BuildKit.
Notes
- Multi-stage builds require Docker 17.05 or later.
- Intermediate stages don't add to the size of the final image. Only files you copy into the target stage do.
- Intermediate stages are not tagged. Build them with
--targetif you need them as images. - Stage results are cached like any other layer. See how Docker decides to use the build cache.