## TL;DR
`COPY failed` in a multi-stage Docker build almost always means the source path does not exist in the build context or in the named stage you are copying from. Check the exact failing `COPY` line, verify the source path relative to the right context (build context for plain COPY, the `--from` stage for cross-stage COPY), and watch for `.dockerignore` excluding the file.

## Error / query
```text
Docker multi-stage build "COPY failed" troubleshooting
```

## Use this skill when
- `docker build` fails with `COPY failed: no such file or directory` or `file not found in build context`
- A cross-stage `COPY --from=[stage]` fails while the stage built fine
- The build works locally but fails in CI (or vice versa)
- You renamed stages or reorganized the Dockerfile recently

## Not for this skill when
- The COPY succeeds but the file has wrong permissions at runtime (permission fix, not COPY)
- The failure is `ADD` with a URL (network fetch, different handling)
- The build fails at a `RUN` step after COPY (the copy worked; debug the RUN)
- You are debugging layer cache misses (caching, not COPY failures)

## Steps

### Step 1: Read the exact failing COPY instruction
```bash
docker build --progress=plain -t debug . 2>&1 | grep -B 8 "COPY failed"
```
Expected: the full `COPY` line and which stage it is in. Multi-stage Dockerfiles have several COPYs; you need the exact one, including any `--from=` flag.

### Step 2: Verify the source exists in the right context
```bash
grep -n "^FROM\|^COPY" Dockerfile
ls [source-path-from-failing-copy]
```
Expected: for a plain `COPY`, the source must exist relative to the build context root (where you run `docker build`), not relative to the Dockerfile. For `COPY --from=[stage]`, the source must exist in that stage's filesystem at that path.

### Step 3: Check .dockerignore is not excluding the source
```bash
cat .dockerignore
docker build --progress=plain . 2>&1 | grep -i "transferring context" 
```
Expected: the failing source path is not matched by `.dockerignore` patterns. A broad pattern like `*` with too-narrow exceptions silently removes files from the context; the COPY then fails with "not found".

### Step 4: Confirm the --from stage name or index is correct
```bash
grep -n "AS [a-z-]*" Dockerfile
```
Expected: the `--from=` value matches a stage name defined earlier in the file (or a numeric index). A renamed stage (`AS builder` changed to `AS build`) breaks every later `COPY --from=builder`; the error names the missing stage.

### Step 5: Test the COPY in isolation with a minimal build
```bash
docker build --progress=plain --target [stage-before-failing-copy] -t stage-test .
docker run --rm stage-test ls -la [expected-source-dir]
```
Expected: building up to the earlier stage and listing its filesystem shows whether the file was ever created there. If the producing stage never made the file, the bug is upstream of the COPY.

## Variant phrasings

### "docker copy no such file or directory"
The source is missing from the context. Steps 2-3: check the path relative to context root and .dockerignore.

### "COPY --from not working docker"
The stage reference is wrong or the file does not exist in that stage. Steps 4-5.

### "docker build works locally but COPY fails in ci"
The CI checkout has a different directory layout, or CI sets a different build context (e.g. a subdirectory). Compare `ls` of the context root in both.

## Why it happens
COPY has two different source namespaces and people mix them up: without `--from`, the source is the client-side build context (filtered by .dockerignore); with `--from`, it is the filesystem of a previous stage. Add in stage renames and context-root confusion, and the file the COPY asks for simply is not where Docker looks.

## Edge cases and pitfalls
- BuildKit and classic builder resolve contexts slightly differently; test with the same builder CI uses.
- `COPY` with a trailing-slash vs without changes directory-vs-contents semantics; a missing trailing slash can make it look like files vanished.
- Symlinks in the context: COPY follows them at build time; a symlink pointing outside the context breaks the copy.
- Windows line endings or BOM in .dockerignore can make patterns not match; keep the file clean.
- `--from` also accepts an external image name; a typo there pulls (or fails to find) an image instead of using your stage.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_iYK2WBvLhdpdnfmRV_cKqQ
