Home › DevOps › Docker Cache Key Error: Fix COPY Not Found Fast
Intermediate 7 min · September 23, 2026

Docker Cache Key Error: Fix COPY Not Found Fast

Docker failed to compute cache key on COPY? Fix missing context files, .dockerignore, build directory, and --from stage errors.

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.

Follow
✓ Production
production tested
October 31, 2026
last updated
2,099
articles · all by Naren
Before you start⏱ 09 min
  • ✓Docker 23+ with BuildKit available
  • ✓A Dockerfile containing at least one COPY instruction
  • ✓Basic comfort with shell directories and paths
 ● Production Incident 🔎 Debug Guide
⚡Quick Answer
  • failed to compute cache key means the COPY source isn't in the build context — the file the daemon received, not your repo
  • Check your shell directory first: the final docker build argument defines the context, and running from a subfolder excludes everything above it
  • Grep .dockerignore for the failing filename — broad rules silently hide files from COPY as well as caching
  • COPY paths can't escape with ../ and --from must name a real AS stage — renames orphan both silently
  • Standardize on BuildKit and pass context roots explicitly so laptop and CI ship identical file sets
✦ Definition~90s read
What is Docker Failed to Compute Cache Key Fix?

A Docker build context is the directory tree the CLI packages up and sends to the daemon before any instruction executes — every COPY, ADD, and RUN --mount=type=bind reads from that transferred set, never from your working directory directly. When a COPY names app/server.js, the daemon looks inside the received tarball for app/server.js, hashes the bytes it finds there into a cache key, and reuses a cached layer when the hash matches a previous build.

★
Imagine you ask a print shop to copy a photo, but you hand them the wrong envelope — your photo sits on your desk at home.

Failed to compute cache key means step zero failed: the file wasn't in the tarball, so there was nothing to hash and no layer to build or reuse.

Three inputs shape the tarball: the context path argument (the directory shipped), the .dockerignore rules (the files subtracted), and the builder's resolution semantics (how -f and paths anchor). Get any of the three wrong and COPY fails identically — file present in the repo, absent in the build.

Multi-stage builds add a parallel lookup where --from names resolve against stage labels instead of the context, with their own orphan and shift failure modes. None of this involves caches being corrupt, daemons being broken, or Dockerfiles being wrong in isolation.

In production, context bugs scale with repo complexity: monorepos with nested Dockerfiles, shared libraries outside the context root, and CI directories that drift from local habits. The fix pattern is always comparison — context versus repo, ignore rules versus COPY sources, stage names versus --from targets — and the prevention pattern is always assertion: CI checks that fail the build when its own inputs don't line up, before the daemon ever gets involved.

Plain-English First

Imagine you ask a print shop to copy a photo, but you hand them the wrong envelope — your photo sits on your desk at home. The clerk says your photo's missing, and you insist it's in your house. You're both right: the file exists, just not in the envelope you handed over. The Docker build context is that envelope. Wrong directory, ignore rules, and bad paths all mean the file never made it into the envelope, so no copy can happen.

You run docker build, and instead of layers you get failed to compute cache key for COPY app/server.js not found. The file is right there — you can cat it, your editor shows it, Git tracks it. So you rebuild, you restart the daemon, you prune the builder cache, and the error doesn't move. That's because the error was never about the file. It's about the build context: the slice of your filesystem the daemon actually received, which doesn't contain your file at all.

This failure blocks everything downstream. No image, no test run, no deploy — and the message points at the Dockerfile line instead of the real culprits: a wrong build directory, a .dockerignore rule, or a COPY path that escapes the context root. Teams lose hours editing Dockerfiles that were correct while the context quietly ships the wrong file set.

This guide fixes it in context order. You'll learn how BuildKit computes cache keys from transferred files, why missing COPY sources really mean missing context, how .dockerignore silently eats files, what the build directory and -f flag actually control, how multi-stage --from references break, and how the legacy builder and BuildKit report the same underlying causes with different wording — so search for both messages and pin the Docker version in CI for reproducible builds. By the end you'll diagnose any cache-key failure with two checks and structure contexts that stop causing them.

BuildKit computes a cache key for each COPY by hashing the source files as they exist inside the transferred context — not your repo, not your disk, but the file set the CLI actually sent the daemon. When that hash can't be computed because no matching file arrived, the build stops before creating any layer. The phrase cache key misleads everyone into cache-pruning; the real sentence is the second half: not found. Nothing to hash, nothing to build.

The transfer is the part nobody visualizes. The CLI tars up the context directory, applies .dockerignore, streams it to the daemon, and only then resolves COPY sources against that tarball. A 400 MB context with node_modules excluded down to 40 MB is normal and good — but every exclusion is also a promise that no COPY needs those files. Break the promise and the daemon reports the file missing, because from its side of the stream it genuinely is. Your local ls is irrelevant; the tarball is the truth.

Internalize this and every later section gets easier: debugging means comparing the context against the repo, never the Dockerfile against your memory. Rebuild with --progress=plain and read which files the step attempted. The gap between ls in your shell and the daemon's file list is the entire bug class, and closing it is a matter of directory, ignore rules, and paths — the next three sections in that order.

cache-key-diagnosis.shBASH
1
2
3
4
5
6
7
# See what the daemon actually attempts, in plain order
export DOCKER_BUILDKIT=1
docker build --progress=plain -t debug:ctx . 2>&1 | grep -B 3 -A 8 'cache key'

# Compare: what you have vs what COPY wants
grep -n '^COPY' Dockerfile
ls -la app/server.js package.json 2>&1
⚠ Pruning won't fix a missing file
Don't prune builder caches for cache-key errors. The failure happens during key computation, before any cache lookup — pruning deletes good layers and changes nothing about the missing file. Check the context first.
📊 Production Insight
The Monday team ran docker builder prune 11 times across 8 runners chasing the word cache. The tarball never contained app/server.js once — one --progress=plain read would have shown it in the first build.
🎯 Key Takeaway
Cache keys hash transferred context files — not found means the tarball lacks the file, so compare context against repo before touching caches.

The most common context bug is also the simplest: the COPY source path doesn't exist relative to the context root. COPY app/server.js ./ means <context>/app/server.js, full stop. A typo (app/sever.js), a case mismatch that macOS forgives but Linux builders don't (App/server.js), or a move that relocated the file without updating the Dockerfile all produce the identical not-found error. The Dockerfile is a contract against a directory layout, and layouts drift.

Case sensitivity deserves special attention because it creates works-on-my-machine ghosts. macOS default filesystems treat App and app as the same directory, so the build passes on a MacBook and fails in Linux CI with the same repo and the same Dockerfile. Trailing-slash semantics are consistent: COPY app/ dest/ and COPY app dest both copy the directory's contents, so neither nests differently. The legacy builder and BuildKit share the same causes and fixes here; only the wording differs — legacy prints 'COPY failed: file not found in build context or excluded by .dockerignore: stat app/server.js: file does not exist', while BuildKit prints 'failed to compute cache key: ... not found'. Search for both messages, and pin the Docker version in CI for reproducible builds.

Verify paths mechanically, not by reading. List the exact source under the context root with ls, check case with a case-sensitive glob, and keep a repo-root convention: all COPY sources relative to one root, one Dockerfile location, one build command. When files live outside the root, stage them in with a build script instead of clever relative paths — cleverness is how ../ escapes sneak in, and those fail by design in the next section's way.

DockerfileDOCKERFILE
1
2
3
4
5
6
7
8
9
10
11
# Dockerfile (context root = repo root)
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

FROM node:20-alpine AS release
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY app/server.js ./app/server.js
CMD ["node", "app/server.js"]
📊 Production Insight
App versus app passed on every MacBook and failed on all 8 Linux runners. The team debated daemon versions for 2 hours before one ls on a case-sensitive filesystem ended it.
🎯 Key Takeaway
COPY sources are contracts against the context layout — verify with ls under the root and watch for case drift between macOS and Linux.

The .dockerignore file decides which files never enter the context tarball, and its rules apply to COPY with equal force. That's the intended deal — smaller contexts, faster transfers, secrets kept out — but every line is also a loaded footgun pointed at future COPY instructions. A rule like *.log added to trim junk also swallows build/manifest.log when someone later copies it. A dist exclusion from the pre-bundler era silently eats the compiled assets the new pipeline needs. The error never mentions the ignore file; it just says the file wasn't found.

Wildcard scope is where teams get bitten. Patterns like * embrace more than intended (they match dotfiles in some positions, subdirectories in others), and negation rules (!keep.me) only re-include files when no parent directory is excluded — exclude build/ and a !build/keep.me rule won't resurrect anything. Engineers add a broad rule, verify the build still passes that day, and the breakage arrives months later when a new COPY reaches for an ignored path. The person who added the rule and the person who hit it are never the same engineer.

When a COPY fails on a missing file, the cause is the same under the legacy builder and BuildKit — only the wording differs: legacy prints 'COPY failed: file not found in build context or excluded by .dockerignore: stat app/server.js: file does not exist', while BuildKit prints 'failed to compute cache key: ... not found'. Search for both messages, and pin the Docker version in CI for reproducible builds.

Treat .dockerignore like code: commented, reviewed, and tested. Every rule gets a trailing comment naming what it excludes and why. Before adding a rule, grep the Dockerfile for COPY sources it could match. When a cache-key failure strikes, grep the ignore file for the failing filename first — it's the fastest confirmation in this whole guide. And keep a canary: a CI step that asserts each COPY source survives the ignore rules catches tomorrow's broad wildcard today.

ignore-audit.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
# Which rule eats your file? Check each suspect
cat .dockerignore
grep -n 'server\|app/\|dist\|\*' .dockerignore

# Comment suspects and rebuild without cache
# (edit .dockerignore, prefix the rule with #)
docker build --no-cache --progress=plain -t debug:ignore . 2>&1 | tail -8

# Good ignore file: narrow, commented, reviewed
# node_modules   # deps reinstall via npm ci
# .git           # history never ships
# dist/maps      # source maps stay local
📊 Production Insight
A dist rule added to slim contexts swallowed the compiled bundle a later stage needed. Three releases failed before anyone connected the ignore file — which the error never names — to the missing asset.
🎯 Key Takeaway
.dockerignore hides files from COPY, not just caching — comment every rule and grep it first on any not-found failure.

The final argument to docker build is the context, and the -f flag only locates the Dockerfile — mixing these up is the classic monorepo failure. Running docker build -f docker/web/Dockerfile docker/ sets the context to docker/, so COPY app/server.js resolves to docker/app/server.js regardless of where the Dockerfile lives. Your repo root can hold every file perfectly while the build sees only the subdirectory. Local builds from the root work; CI builds from the nested directory fail; everyone argues about the Dockerfile, which is identical in both.

The confusion is historical. Both the legacy builder and BuildKit use the final docker build argument as the context and resolve -f relative to the current directory; they do not resolve contexts differently. What differs is the message: legacy prints 'COPY failed: file not found in build context or excluded by .dockerignore: stat app/server.js: file does not exist', while BuildKit prints 'failed to compute cache key: ... not found'. The causes and fixes are identical under both, so search for both messages. BuildKit is the default since Docker Engine 23, so pin the Docker version in CI for reproducible builds.

Standardize one pattern and never deviate: context is always the repo root, -f points at the nested Dockerfile. docker build -f docker/web/Dockerfile . reads naturally once you know the last dot is the context doing the real work. Wrap it in a build script so nobody improvises directories under deadline pressure, and assert in CI that every COPY source exists under the declared context root. Directory discipline eliminates this failure class permanently.

context-directory-fix.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Wrong: context is docker/ — app/ is invisible
# docker build -f docker/web/Dockerfile docker/

# Right: context is repo root, -f only locates the file
docker build -f docker/web/Dockerfile -t web:latest .

# Prove what each context contains before building
pwd
ls docker/ | head
ls app/server.js package.json

# CI guard: every COPY source must exist under the context
for f in $(grep '^COPY' docker/web/Dockerfile | awk '{print $2}' | grep -v -- '--from'); do
  test -e "$f" || echo "MISSING FROM CONTEXT: $f"
done
📊 Production Insight
CI built from docker/ for months after the restructure moved sources to packages/web/. The Dockerfile never changed, so nobody reviewed the build step — the context silently shrank while the COPY lines stayed ambitious.
🎯 Key Takeaway
The last build argument is the context — always pass the root explicitly and point -f at the nested Dockerfile.

Multi-stage builds add a second namespace where cache-key failures hide: the --from reference. COPY --from=builder /out/app ./ copies from a previous stage's filesystem, not from your context — so the failure means the stage name doesn't resolve, not that a file is missing. Renames orphan references silently: someone tidies builder into compile, three downstream COPY lines still say --from=builder, and the build fails on a name that no longer exists. Numeric indexes are worse — --from=0 shifts meaning every time a stage is inserted or removed above it.

Conditional and target-scoped builds add subtler variants. A stage behind a build-arg condition that evaluates false never materializes, so --from pointing at it fails only in the configurations that skip it — green default builds, red matrix entries. Building with --target mid-Dockerfile similarly truncates later stages; any --from naming a stage past the target fails even though the full build works. These config-dependent failures look flaky because they are: same repo, different flags, different stage graphs.

Pin stage names like API contracts. Declare every AS name in a comment block at the top of multi-stage Dockerfiles, grep --from targets against FROM lines in CI, and ban numeric indexes in review. When a --from failure hits, the diagnosis is one command: list the stages, list the references, diff them. Names that appear in references but not in stages are your answer, and the fix is a rename in one direction or the other — never a cache prune.

DockerfileDOCKERFILE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Declare the contract: every stage named up top
# stages: base -> deps -> build -> release
FROM node:20-alpine AS base
WORKDIR /app

FROM base AS deps
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

FROM deps AS build
COPY app/ ./app/
RUN npm run build

FROM base AS release
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
CMD ["node", "dist/server.js"]
📊 Production Insight
A cleanup commit renamed builder to compile and orphaned three --from references. The diff looked cosmetic so it merged without a build check — then blocked the release branch for 4 hours until someone diffed stage names against references.
🎯 Key Takeaway
Stage names are contracts — declare them once, grep --from targets in CI, and diff references against FROM lines on failure.

BuildKit and the legacy builder report context failures with different wording, so identical commands can look like different problems per machine. Both use the final docker build argument as the context and resolve -f relative to the current directory; the causes and fixes are identical under both. Legacy prints 'COPY failed: file not found in build context or excluded by .dockerignore: stat app/server.js: file does not exist', while BuildKit prints 'failed to compute cache key: ... not found'. The same repo, the same command, two messages — and the failure follows the builder's wording, not the code.

Mixed fleets make this miserable. Laptops on Docker 24 with BuildKit default, CI runners on Docker 20 with legacy, a specialty builder somewhere in between — each words edge cases its own way, so failures correlate with where the build ran instead of what changed. Engineers bisect commits while the variable is the runner image. The --progress=plain output differs too, which means even the debugging evidence looks inconsistent across machines.

End the confusion by standardizing. Enable BuildKit everywhere — DOCKER_BUILDKIT=1 or Docker 23+, where it's default — pin the Docker version in CI images for reproducible builds, and search for both messages when diagnosing context failures. Then re-test all Dockerfiles under the standard builder once, because the stricter reporting surfaces latent sins (escapes, ignored files, sloppy -f usage) that legacy tolerated. One builder, one context convention, zero machine-dependent mysteries.

builder-standardize.shBASH
1
2
3
4
5
6
7
8
9
10
11
# Pin the builder, then compare evidence apples to apples
docker buildx version
echo "DOCKER_BUILDKIT=$DOCKER_BUILDKIT"

# Force BuildKit with plain, greppable output
export DOCKER_BUILDKIT=1
docker build --progress=plain -f docker/web/Dockerfile -t web:std . 2>&1 | tee /tmp/buildkit.log
grep -B 2 -A 6 'cache key\|not found' /tmp/buildkit.log || echo 'clean build under BuildKit'

# CI: fail when the builder isn't what you standardized
# test "$(docker buildx version | head -1)" = "expected" || exit 1
📊 Production Insight
Failures tracked the builder, not the commit: the same causes produce different wording—legacy prints 'COPY failed: file not found in build context or excluded by .dockerignore: stat app/server.js: file does not exist', BuildKit prints 'failed to compute cache key: ... not found'—so search for both messages. BuildKit is the default since Docker Engine 23; pin the Docker version in CI for reproducible builds. Standardizing the fleet turned flaky red builds into one honest failure with a clear fix.
🎯 Key Takeaway
One builder everywhere — standardize on BuildKit, pin it in CI, and pass explicit contexts so evidence matches across machines.
● Production incidentPOST-MORTEMseverity: high

Wrong Context Directory Blocked 23 PRs for 7 Hours

Symptom
Monday 9 AM: all 8 CI runners failed every frontend build with failed to compute cache key for COPY app/server.js not found. Local builds from the repo root worked fine. The team burned 7 hours pruning caches and restarting daemons across the fleet, blocking 23 pull requests and the weekly release train until 4 PM.
Assumption
The team assumed a corrupted builder cache because the error mentioned cache keys, so they pruned builders on every CI runner and pinned the Dockerfile. Nobody questioned the build directory because the pipeline YAML hadn't changed in months — the repo layout around it had, when the frontend was restructured, and no build ever validated that the context still contained the COPY sources.
Root cause
Two stacked faults. A monorepo restructure moved app/ and package.json into packages/web/, but CI still ran docker build from docker/ with a nested Dockerfile — so the context contained only CI scripts while COPY asked for app/server.js. Separately, a .dockerignore cleanup had added dist and *.map to shrink contexts, which swallowed the compiled assets a later stage needed. The team misread cache key as cache corruption and ran docker builder prune 11 times across 8 runners over 7 hours before checking what the context actually contained.
Fix
First, the pipeline's build step was changed to pass the repo root as context with -f pointing at the nested Dockerfile, restoring app/ and package.json to the context. Second, the .dockerignore was rewritten with comments and narrowed rules after an audit found dist and *.map swallowing needed assets. Third, CI gained a pre-build check that greps COPY sources from the Dockerfile and asserts each exists under the context root — 6 stale references caught in the first month.
Key lesson
  • Cache-key errors describe the context, not the cache — pruning builders 11 times cost a day while the context stayed wrong.
  • Build pipelines must assert their own inputs: a 5-line COPY-source check would have failed the first broken commit, not the 40th.
  • Monorepo restructures must update build contexts alongside code moves, because the Dockerfile keeps pointing at paths the move deleted.
Production debug guideFive checks that compare the context against the repo — run them before editing the Dockerfile.5 entries
Symptom · 01
COPY names a file you can see in the repo
→
Fix
Confirm your shell location with pwd and list the COPY source relative to it: ls -la app/server.js. If it's missing here, you're in the wrong directory — cd to the context root and rebuild. Then echo the exact context argument from your build command; docker build . means this directory, docker build ./docker means that one.
Symptom · 02
File exists in the directory but the build can't see it
→
Fix
Grep the ignore file with grep -n 'server\|app/\|\*' .dockerignore and check for broad rules matching your file. Temporarily comment suspects with # and rebuild with --no-cache. If the build passes, narrow the rule to the exact junk path instead of the wildcard.
Symptom · 03
COPY path contains ../ or looks clever
→
Fix
Inspect the failing COPY line with grep -n '^COPY' Dockerfile and check every source for ../ escapes or absolute paths. Rewrite sources relative to context root — COPY app/server.js not COPY ./docker/../app/server.js — and restage shared files under the context root before building.
Symptom · 04
COPY --from fails right after a Dockerfile edit
→
Fix
List declared stages with grep -n '^FROM' Dockerfile and compare each --from value from grep -n 'COPY --from' Dockerfile against them. Fix the name to match an AS label exactly, or restore the deleted stage. Prefer names over numeric indexes, which shift silently.
Symptom · 05
Same repo builds on one machine but not another
→
Fix
Print the builder with docker buildx version and echo $DOCKER_BUILDKIT, then rebuild forcing BuildKit: DOCKER_BUILDKIT=1 docker build --progress=plain . 2>&1 | grep -B 2 -A 5 'cache key'. The causes and fixes are identical under both builders; only the wording differs, so search for both messages (legacy: 'COPY failed: file not found in build context or excluded by .dockerignore: stat app/server.js: file does not exist'; BuildKit: 'failed to compute cache key: ... not found'). Pin the Docker version in CI for reproducible builds.
Cache Key Failures at a Glance
Root CauseHow to ConfirmFixPrevention
COPY source missing from contextFile exists in repo but build says not foundFix the path or run from the right directoryKeep COPY paths relative to context root
Excluded by .dockerignoreFile missing from context listingNarrow the ignore rule or relocate the fileComment every rule; grep ignores for COPY sources
Wrong build directory as contextContext files differ from repo layoutPass context root explicitly with -fOne build script per image with fixed context
Bad COPY --from stage referenceStage name missing from FROM linesCorrect the name or restore the stagePin stage names; CI-grep --from targets
BuildKit versus legacy resolution: both the legacy builder and BuildKit use the final docker build argument as the context and resolve -f relative to the current directory, so they do not resolve contexts differently. What differs is the message: legacy prints COPY failed: file not found in build context or excluded by .dockerignore: stat app/server.js: file does not exist, while BuildKit prints failed to compute cache key: ... not found. The causes and fixes are identical under both, so search for both messages. BuildKit is the default since Docker Engine 23; pin the Docker version in CI for reproducible builds.Same repo builds differently per machineStandardize on BuildKit everywherePin builder version in CI
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
cache-key-diagnosis.shexport DOCKER_BUILDKIT=1bash configuration
DockerfileFROM node:20-alpine AS depsdockerfile configuration
ignore-audit.shcat .dockerignorebash configuration
context-directory-fix.shdocker build -f docker/web/Dockerfile -t web:latest .bash configuration
DockerfileFROM node:20-alpine AS basedockerfile configuration
builder-standardize.shdocker buildx versionbash configuration

Key takeaways

1
COPY sees only the transferred context
the repo having a file means nothing if the context lacks it.
2
Paths are relative to the context root; ../ escapes are rejected by design, always.
3
.dockerignore hides files from COPY too
grep it for every failing COPY source first.
4
The -f flag locates the Dockerfile; the final path argument defines the context
don't confuse them.
5
--from must name a real AS stage; renames orphan references and index shifts hide silently.
6
Standardize on BuildKit and pass context roots explicitly so laptop and CI ship identical file sets.

Common mistakes to avoid

6 patterns
×

Running docker build from a subdirectory

Symptom
COPY app/server.js fails with the file present in the repo — the build ran from docker/ so the context root excluded app/ entirely.
Fix
Run builds from the directory containing the referenced files, or pass the right context path explicitly: docker build -f docker/app/Dockerfile . keeps context at the root so COPY app/ works.
×

Adding a broad .dockerignore rule and forgetting it

Symptom
A build that worked for months breaks after someone adds *.log or dist to .dockerignore, silently excluding files the Dockerfile still needs.
Fix
Grep .dockerignore for every COPY source before theorizing. Keep one ignore file per build, comment each rule, and test changes with a plain build, not a cached one.
×

Using parent-directory paths in COPY

Symptom
COPY ../shared/config.json fails every build — the daemon rejects context escapes by design, and no flag overrides it.
Fix
COPY paths relative to context root only, never ../anything. Restructure with a root-level Dockerfile or a build script that stages files into one context directory.
×

Referencing a renamed or deleted build stage

Symptom
COPY --from=builder fails after someone renames the stage to build — the index silently shifted and now points at the wrong stage or none.
Fix
Name every stage and reference the exact name in --from. Grep the Dockerfile for FROM ... AS lines and confirm the --from value appears among them before building.
×

Assuming legacy and BuildKit resolve contexts identically, the causes and fixes are the same under both builders; only the wording differs, so search for both messages. Legacy prints 'COPY failed: file not found in build context or excluded by .dockerignore: stat app/server.js: file does not exist', while BuildKit prints 'failed to compute cache key: ... not found'. BuildKit is the default since Docker Engine 23, so pin the Docker version in CI for reproducible builds.

Symptom
Builds pass on one machine and fail on another with the same repo — the builders differ and -f context resolution changed between them.
Fix
Standardize on BuildKit everywhere (DOCKER_BUILDKIT=1 or Docker 23+, where it is the default) and pin the Docker version in CI for reproducible builds. The causes and fixes for context and -f issues are identical under the legacy builder and BuildKit; only the wording differs, so search for both messages ('COPY failed: file not found in build context or excluded by .dockerignore: stat app/server.js: file does not exist' and 'failed to compute cache key: ... not found'). Test legacy Dockerfiles under BuildKit in CI before upgrading builders.
×

Treating stage renames as cosmetic cleanup

Symptom
A tidy-up commit renames builder to compile and breaks three downstream --from references that nobody greps for.
Fix
Pin stage names in a comment block at the top of multi-stage Dockerfiles and review renames like API changes. CI can grep that --from targets match declared AS names.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What is the Docker build context and why does COPY depend on it?
Q02JUNIOR
A file exists in the repo but COPY can't see it. Where do you look first...
Q03SENIOR
How do the -f flag and the context path interact?
Q04SENIOR
COPY --from=builder broke after a refactor. What happened?
Q05SENIOR
Why does the COPY error message differ between BuildKit and legacy? Both...
Q01 of 05JUNIOR

What is the Docker build context and why does COPY depend on it?

ANSWER
The daemon hashes the COPY sources found inside the transferred context, not the repo. If the file isn't in the context — wrong path, wrong directory, ignored — hashing fails before any layer builds. I'd check context contents and .dockerignore first.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
What does failed to compute cache key actually mean?
02
Can COPY reference files outside the build context?
03
Does .dockerignore affect COPY or just caching?
04
Why does COPY --from fail after a Dockerfile cleanup?
05
Why does the same build work on my machine but fail in CI?
06
How do I see what files are in the build context?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.

Follow
✓ Verified
production tested
October 31, 2026
last updated
2,099
articles · all by Naren
🔥

That's Docker. Mark it forged?

7 min read · try the examples if you haven't

←
Previous
Docker Exec Format Error Fix
26 / 26 · Docker
Next
Ansible SSH Connection Failed Fix
→