Dockerfile Multi-Stage Build Analyzer

Get the stage graph for a multi-stage Dockerfile: which stages reach the final image, which are dead, and whether every COPY --from resolves to a stage that exists above it.

Paste below, or drop a file anywhere on this panel

Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.

The answer appears here

Paste on the left and press Analyze. Nothing leaves this tab.

Examples

Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.

A misspelled stage name

Docker reads the target as an image and fails at the pull, so the error mentions the registry rather than the typo

FROM golang:1.22 AS builder
RUN go build -o /app .

FROM alpine
COPY --from=bulider /app /app

A stage nothing uses

BuildKit skips it, so it is dead weight in the file rather than wasted build time

FROM alpine AS used
RUN touch /a

FROM alpine AS orphan
RUN touch /b

FROM alpine
COPY --from=used /a /a

A build token in the builder stage

It is not in the final image and it is in the build cache, and --target on that stage hands it over

FROM alpine AS builder
ENV NPM_TOKEN=REPLACE_ME
RUN npm ci

FROM alpine
COPY --from=builder /app /app

Common mistakes

These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

  1. Debugging a misspelled stage name as a registry problem

    Docker reads an unrecognised --from target as an image reference, so the error is a failed pull. The credentials are fine and the stage name is wrong.

    Instead:Check the --from target against the AS aliases in the file before touching anything to do with the registry.

  2. Assuming a discarded stage makes a build secret safe

    The value is genuinely absent from the final image and genuinely present in the build cache layer, and --target on that stage produces a runnable image containing it.

    Instead:Use RUN --mount=type=secret. It keeps the value out of every layer rather than relying on the layer being thrown away.

  3. Deleting an unused stage without checking CI

    A stage nothing copies from may still be the --target of a test or lint job, which no part of the Dockerfile records.

    Instead:Grep the pipeline for --target before deleting, and leave a comment on any stage that exists only for CI.

A bad stage name produces a registry error, not a typo report

That one behaviour is why a graph is more useful here than a rule list. Docker cannot tell a misspelled stage from an image it has not heard of, so it tries to pull it.

An unrecognised --from is treated as an image

There is no separate namespace for stage names. COPY --from=bulider is a valid instruction referring to an image called bulider, so the build fails at the pull with an authentication or not-found error. People then check their registry credentials, which are fine. The fix is a spelling correction three lines up.

A stage can only copy from one above it

File order is the order, and it is not inferred from the dependency graph. Referring to a stage defined lower down fails, and so does a stage copying from itself, which happens when two aliases get transposed during a refactor.

An unused stage costs nothing with BuildKit and is not free without it

BuildKit builds only what the target needs, so a dead stage is skipped. The classic builder walks the file and builds all of them. Since BuildKit has been the default for a while this is mostly a readability problem now, with one exception: if CI builds with --target set past the dead stage, it does get built, so deleting it breaks a pipeline nobody reading this file can see.

A secret in a discarded stage is not in the image, and is not safe either

This is worth being precise about, because the loose version of the advice is wrong. A credential set in a builder stage genuinely does not ship in the final image, so docker history on the pushed image will not show it. It is written into the build cache layer, and anybody who runs the build with --target pointed at that stage gets a runnable image containing it. RUN --mount=type=secret keeps it out of any layer at all, which is the difference that matters.

Two stages with the same alias is accepted

Docker does not reject it. A COPY --from naming that alias resolves to one of them, the build succeeds, and the wrong artefact is in the image. This is the failure mode with no error message attached, which is why it is worth checking for rather than waiting to notice.

What this cannot see

Whether the build works, whether the copied paths exist, or how large the result is. It reads the text you paste, in your browser, and never runs Docker. Anything that looks like a credential is reported by key name with the value redacted.

More docker tools

Docker Container Image Reference Parser Is that a registry or a Docker Hub user? Dockerfile Linter What this image will do to you later docker run to Kubernetes YAML And the flags that have no equivalent Docker Compose to Kubernetes And why depends_on has no equivalent Docker Logging Driver Generator json-file never rotates Dockerfile Non-Root USER Generator Numeric, or Kubernetes rejects it Docker OCI Image Labels Generator One of them actually does something Docker Hub Pull Rate Limit Calculator Counted per IP, not per person Docker Registry Storage Calculator Deleting a tag frees nothing Docker Build Context Analyzer All of it uploads before anything runs Docker Compose to docker run Converter Names stop resolving Docker Compose Version Migrator version: is obsolete now Docker Compose Network and Port Viewer Everything reaches everything Docker Command Explainer Which flags hand over the host Docker BuildKit Cache Mount Generator apt deletes the cache you just mounted Docker BuildKit Secret Mount Generator ARG is in docker history forever Docker .dockerignore Pattern Tester *.log does not match logs/app.log Docker Image Manifest Viewer unknown/unknown is not broken Docker Image Config Viewer Anyone who can pull can read your Env Docker Registry Token Decoder Check the scope before the credentials Docker HEALTHCHECK Generator Docker does nothing when it fails Docker Network Subnet Calculator 31 networks, then it stops Docker History Analyzer The rm did not save anything Docker Run to Compose Converter Some flags have no equivalent Docker CMD and ENTRYPOINT Tester Why docker stop takes ten seconds Docker Port Mapping Tester -p reaches past your firewall Docker -v to --mount Converter -v invents missing directories Docker Restart Policy Simulator The backoff resets after ten seconds Docker Exit Code Explainer 137 is two different problems Docker CPU Limit Converter --cpu-shares limits nothing Docker Memory Limit Calculator -m 512m allows a gigabyte Docker Compose Interpolation Tester An unset variable is an empty string Docker Environment Variable Precedence Tester Your shell does not reach the container Docker Compose Override Merge Previewer An override can add a port, never remove one Dockerignore Validator It is not .gitignore

Elsewhere on the site