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

FROM [--platform=<platform>] <image | stage> [AS <name>]
COPY [--chown=<user>:<group>] [--chmod=<perms>] --from=<name | index | image> <src>... <dest>
docker build --target <name> [--output <dest>] <context>

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>, and docker 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 as ENV and WORKDIR.
  • 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

RuleDetail
CharactersMust start with a letter, followed by letters, digits, -, _ or .. build-env, go_1.23 and prod are valid. 1st and my stage are not.
CaseCase-insensitive. AS Build and --from=build refer to the same stage.
UniquenessEach name may be used once per Dockerfile. BuildKit fails the build on a duplicate stage name.
ShadowingA 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.
VariablesThe 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:

ValueExampleCopies from
Stage nameCOPY --from=build /out/app /appThe filesystem of the named stage.
Stage indexCOPY --from=0 /out/app /appThe nth FROM in the file, counting from 0.
Image referenceCOPY --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 .dockerignore are not involved.
  • --chown resolves user and group names using /etc/passwd and /etc/group of the destination stage. In scratch and similar images, use numeric IDs (--chown=65532:65532). See COPY for --chown and --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 / --output changes 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 ARG before the first FROM is global, and only FROM lines can see it.
  • An ARG declared inside a stage is scoped to that stage and ends at the next FROM.
  • ENV is inherited only through FROM <stage>, never through COPY --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:

BehaviourBuildKitLegacy builder
Which stages are builtOnly 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.
ParallelismIndependent stages run concurrently.Sequential.
COPY --from=<image>SupportedSupported
RUN --mount=from=<stage>, cache mountsSupportedNot supported
--output / -oSupportedNot supported
Cache across stagesPer-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 --target if you need them as images.
  • Stage results are cached like any other layer. See how Docker decides to use the build cache.

Tutorial

Step-by-step guide with complete Dockerfiles.

Multi-Stage Builds Tutorial

Try Our Tools

Create and visualize multi-stage builds.

Multi-stage Builder