Docker build "failed to compute cache key: … not found": why COPY can't see your file
Fix "failed to compute cache key … not found" and "COPY failed": the file is outside the build context, excluded by .dockerignore, or missing in CI.
by OhSudo ··
Part of a learning path:Docker: from first container to production
Your docker build stops at a COPY line, and the file is right there in your project folder:
=> ERROR [build 4/6] COPY package-lock.json ./ 0.0s------ > [build 4/6] COPY package-lock.json ./:------Dockerfile:6-------------------- 4 | WORKDIR /app 5 | COPY package.json ./ 6 | >>> COPY package-lock.json ./--------------------ERROR: failed to solve: failed to compute cache key: failed to calculate checksum of ref 7c0f2…::q1x…: "/package-lock.json": not foundThe wording changes a little between Docker versions — older ones say "/package-lock.json" not found: not found, the legacy builder says COPY failed: file not found in build context or excluded by .dockerignore — but the meaning is always the same: the file is not in the build context. COPY never reads your disk directly. It reads a copy of one folder, the context, that the Docker client sends to the builder before the build starts. If the file isn’t in that copy, COPY can’t see it — no matter where it is on your machine.
- Your project folderpackage-lock.json is heresent as
- Build contextthe folder given to docker build, filtered by .dockerignorelooks inside it(fails)
- COPY package-lock.jsonnot found in the context(breaks here)
TL;DR
docker build -t myapp . # the "." is the build context: COPY paths start therecat .dockerignore # is the file excluded?docker build --progress=plain --no-cache -t myapp . 2>&1 | grep -i "transferring context"git ls-files package-lock.json # in CI: is the file in git at all?1. Know your build context
The last argument of docker build is the context — usually ., the current folder. Every source path in COPY and ADD is relative to the context, not to the Dockerfile and not to the folder you’re in:
docker build -t myapp . # context: current folderdocker build -t myapp -f docker/Dockerfile . # Dockerfile in docker/, context still the rootdocker build -t myapp docker/ # context: docker/ — the rest of the project is invisibleThe third line is the usual trap: with the Dockerfile in a subfolder, docker build docker/ sends only that subfolder, and COPY package.json ./ fails although package.json sits one folder up. Build from the project root with -f instead.
COPY ../shared /app/shared doesn’t work either: the context is a sandbox, and .. can’t leave it. BuildKit quietly treats the path as /shared inside the context and reports it as not found. Move the build context up a level (docker build -f app/Dockerfile . from the parent), or in Compose set context: .. and dockerfile: app/Dockerfile.
2. .dockerignore excludes it
The second most common cause, and the hardest to spot, because the file is in the folder. .dockerignore (in the root of the context) removes files from the context before it is sent:
node_modulesdist*.lock**/*.jsonHere *.lock removes yarn.lock, and **/*.json removes every package.json and package-lock.json — and COPY package-lock.json ./ fails. Look for patterns that match the file in the error, including broad ones like *, **/*.json or a whole folder.
Two rules make .dockerignore different from .gitignore:
- Every pattern starts at the root of the context.
node_modulesexcludes only./node_modules; to exclude it in every folder, write**/node_modules. !re-includes. An “ignore everything, then allow” file is fine, but every file the build needs must be allowed again:
*!package.json!package-lock.json!src/The .gitignore & .dockerignore Generator writes a .dockerignore that follows Docker’s rules — and it is checked with a real docker build for every stack.
Warning
A .dockerignore next to a Dockerfile that is not in the root of the context is not read. Docker reads <context>/.dockerignore, or a file named like the Dockerfile itself — docker/Dockerfile.dockerignore for -f docker/Dockerfile.
3. Builds locally, fails in CI
If the build works on your machine and fails in GitHub Actions or GitLab CI, the CI checkout is different from your folder — it contains only what is in git:
git ls-files package-lock.json # no output: the file isn't in the repositorygit check-ignore -v package-lock.jsonTypical cases:
- The lock file was never committed, or it’s in
.gitignore. Commitpackage-lock.json(orpoetry.lock,go.sum…): it makes builds repeatable anyway. - The Dockerfile copies build output (
COPY dist/ …) that you built locally withnpm run build— anddist/is (rightly) in.gitignore. Build it inside the image instead, in a build stage. That’s the multi-stage pattern the Dockerfile Generator writes, and the CI/CD Pipeline Generator writes a pipeline that builds it. - Case.
COPY Package.json ./works on macOS and Windows, whose filesystems ignore case, and fails on the Linux CI runner.
4. The file comes from another stage
In a multi-stage build, COPY --from=build /app/dist ./dist reads from the build stage’s filesystem, not from the context. “not found” there means the previous stage didn’t produce that path:
ERROR: failed to solve: failed to compute cache key: failed to calculate checksum of ref …: "/app/dist": not foundCheck where the build really writes: vite build writes to dist/, but Create React App writes to build/, Next.js to .next/, and tsc to whatever outDir says in tsconfig.json. To look inside a stage, build only that stage and open a shell in it:
docker build --target build -t myapp-build .docker run --rm -it myapp-build sh -c "ls -la /app"5. See exactly what’s in the context
When nothing above explains it, list what the builder actually receives. This throwaway Dockerfile copies the whole context into a tiny image and prints it:
docker build --no-cache --progress=plain -f - . <<'EOF'FROM busyboxCOPY . /ctxRUN find /ctx -maxdepth 2 | sortEOFIf the file isn’t in that list, it’s excluded (.dockerignore) or outside the context. The same output also shows things that shouldn’t be there — .git, node_modules, .env — which make the context slow to send and can leak into your image.
Shell, we have a problem!
Similar, but different: failed to read dockerfile: open Dockerfile: no such file or directory means the Dockerfile itself isn’t where Docker looks — in the root of the context, unless you pass -f path/to/Dockerfile. And "/app": not found after COPY --from=… with an image name (COPY --from=nginx:1.27 …) means that path doesn’t exist in that image.
Found this useful? Share it.
Found a mistake? Let us know at contact@ohsudo.com.