Docker Multi-Stage Build Not Copying Files Correctly
When files that clearly exist in an earlier build stage don't show up in the final image after a COPY --from, the cause is almost always a mismatch between the stage name (or index) referenced and the actual path where the files ended up in that stage — not a Docker bug.
The Problem
Your multi-stage Dockerfile builds successfully, but the final image is missing files you expected to be copied over from an earlier stage:
docker run your-image ls /app/dist
ls: /app/dist: No such file or directory
Checking the build stage directly (by temporarily building only up to that stage) confirms the files genuinely exist there, which points the problem specifically at the COPY --from step itself.
Why It Happens
Multi-stage builds rely on precise stage naming and path references, and small mismatches between stages fail silently rather than producing an obvious error. Common causes:
- A typo or mismatch in the stage name used in
COPY --from, referencing a stage name that doesn't exactly match what was declared withASin an earlierFROMline - The source path in the earlier stage is different from what's assumed — for example, the build output actually lands in
/app/buildrather than/app/dist, depending on the build tool's own configuration, independent of anything Docker controls - The working directory (
WORKDIR) differs between stages, so a relative path that worked in one stage's context resolves to a different absolute path in the stage being copied from - The build step that's supposed to generate the files didn't actually run successfully in that stage, and its failure was masked by a later command in the same
RUNline still succeeding
The Fix
First, confirm the stage name in COPY --from exactly matches the name declared with AS in the corresponding FROM line:
FROM node:18 AS build
# ...
FROM node:18-alpine
COPY --from=build /app/dist ./dist
The name after AS (build here) must match exactly, including case, in the corresponding --from= reference. A mismatch here doesn't always throw an obvious error in every Docker version — sometimes it just silently copies nothing or fails with a less-than-clear message.
Verify the actual output path inside the build stage rather than assuming it based on convention. Build a partial image stopping at just that stage, to inspect it directly:
docker build --target build -t debug-build .
docker run -it debug-build sh
ls -la /app
This lets you see exactly where the build output landed, confirming (or correcting) the path used in your COPY --from instruction in the final stage.
Confirm WORKDIR is consistent, or use absolute paths explicitly in the COPY --from instruction rather than relying on relative paths that depend on matching working directories between stages that may not actually match:
FROM node:18 AS build
WORKDIR /app
RUN npm run build
# output lands in /app/dist
FROM node:18-alpine
WORKDIR /usr/src/app
COPY --from=build /app/dist ./dist
Notice the source path (/app/dist) is absolute and independent of the final stage's own WORKDIR, avoiding ambiguity about which directory the relative path would otherwise resolve against.
If a build command is silently failing without halting the build, split combined RUN commands so a failure in the build step doesn't get masked by a later command in the same line succeeding regardless:
# Risky: if npm run build fails, this line might still succeed overall
RUN npm run build; echo "done"
# Safer: fails the build step immediately if the build itself fails
RUN npm run build
Still Not Working?
If everything above checks out and the copy still doesn't bring over what you expect, use numeric stage indexes instead of names as a diagnostic step — referencing --from=0 for the first stage, --from=1 for the second, and so on, sidesteps any possibility of a naming mismatch entirely and can help confirm whether the issue is specifically in stage naming versus something else in the path or build process.