← Back to Tutorials

How to Fix the “failed to compute cache key” Docker Build Error

dockerbuildkitdockerfileci-cdbackend

How to Fix the “failed to compute cache key” Docker Build Error

Docker builds are usually deterministic: given the same Dockerfile and build context, Docker should compute the same cache keys and reuse layers. When Docker can’t even calculate the cache key for a step, it stops early with an error like:

This tutorial explains what the error actually means, how Docker’s cache keys are computed, and a systematic way to diagnose and fix the most common root causes—using real commands and reproducible examples.


Table of Contents


What the error means

A Docker build step can be cached only if Docker can compute a cache key for it. The cache key is derived from:

If Docker says “failed to compute cache key”, it typically means:

  1. Docker tried to enumerate files used by a step (often a COPY), but
  2. It couldn’t read them, find them, or traverse them, so
  3. It couldn’t hash them, so
  4. It couldn’t decide whether the step can be cached, so the build aborts.

This is different from errors that happen during execution (like RUN npm ci failing). Cache-key failures happen before a step runs.


How Docker computes cache keys (BuildKit)

Modern Docker builds use BuildKit by default in most installations. You can verify:

docker version
docker buildx version

BuildKit changes the build pipeline in important ways:

When you see errors like:

that’s BuildKit complaining while trying to compute the digest of input files.

You can explicitly toggle BuildKit to compare behavior:

# BuildKit enabled (common default)
DOCKER_BUILDKIT=1 docker build -t test .

# BuildKit disabled (legacy builder)
DOCKER_BUILDKIT=0 docker build -t test .

If disabling BuildKit “fixes” it, don’t stop there—your build is likely relying on undefined behavior or accidentally copying excluded/missing files. The correct fix is to make inputs consistent.


Quick triage checklist

Before diving deep, run these quick checks:

  1. Are you in the correct directory?

    pwd
    ls -la
  2. Are you using the right build context?

    docker build -t myimg .
    # The dot matters: it defines the context.
  3. Print the exact failing step

    docker build --progress=plain -t myimg .
  4. Search for the referenced path in the Dockerfile

    grep -nE 'COPY|ADD|--mount' Dockerfile
  5. Check .dockerignore

    cat .dockerignore
  6. Confirm the file exists in the context

    ls -la path/from/error

Common causes and fixes

1) Missing files in the build context

Symptom

You see something like:

Why it happens

COPY and ADD can only use files that are inside the build context. The build context is the final argument to docker build (often .). Docker sends that directory (minus .dockerignore exclusions) to the builder.

If your Dockerfile says:

COPY package.json package-lock.json ./

but package-lock.json is not present in the context directory, BuildKit fails while computing the checksum.

Fix

Ensure the file exists and is spelled correctly:

ls -la package.json package-lock.json

If the file is generated (e.g., by CI), generate it before building:

npm install --package-lock-only
docker build -t myapp .

If the file is in a subdirectory, adjust COPY paths or context:

# If Dockerfile is at repo root but files are in ./frontend
COPY frontend/package.json frontend/package-lock.json ./

Or build with a different context:

docker build -t myapp -f frontend/Dockerfile frontend

2) .dockerignore excluding required files

Symptom

The file exists on disk, but Docker still says it’s not found or fails checksum computation.

Why it happens

Docker applies .dockerignore patterns to the context before sending it to the builder. If you accidentally ignore a file, it will not exist inside the build context, even though it exists locally.

A common mistake is ignoring too broadly:

# .dockerignore (problematic)
node_modules
*.json

Now package.json is excluded.

Fix

Inspect what you ignore:

cat .dockerignore

Remove or narrow patterns. Example:

node_modules
npm-debug.log
.git
Dockerfile*

If you need to ignore many files but keep a specific one, use negation:

*.json
!package.json
!package-lock.json

Then rebuild:

docker build --progress=plain -t myapp .

3) Wrong build context (building from the wrong directory)

Symptom

Everything looks correct, but Docker can’t find files referenced by COPY.

Why it happens

You might be running docker build from a directory that doesn’t contain the files you expect. The build context is not “where the Dockerfile is”—it’s the path you pass as the last argument.

Fix

Be explicit about both Dockerfile and context:

# Dockerfile is in ./docker/Dockerfile, but context must be repo root
docker build -f docker/Dockerfile -t myapp .

If your context should be a subfolder:

docker build -f Dockerfile -t myapp ./frontend

To confirm what Docker is sending, look for the “transferring context” line:

docker build --progress=plain -t myapp .

If it says transferring context: 2B, you’re probably ignoring almost everything or pointing at an empty directory.


4) COPY/ADD path mistakes and glob surprises

Symptom

Errors involving globs or directories:

Why it happens

COPY supports limited wildcard behavior, but it’s easy to write patterns that match nothing, especially when .dockerignore removes files. BuildKit then can’t compute checksums for expected inputs.

Example:

COPY dist/*.js /app/dist/

If dist/ doesn’t exist yet (because it’s built later) or is ignored, the pattern matches nothing.

Fix options

  1. Ensure the files are generated before docker build (build artifacts on host), or
  2. Generate them inside the image using multi-stage builds.

Preferred: multi-stage build

# Stage 1: build
FROM node:20 AS build
WORKDIR /src
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: runtime
FROM nginx:alpine
COPY --from=build /src/dist/ /usr/share/nginx/html/

Build:

docker build --progress=plain -t mysite .

This avoids relying on host-side dist/ existing.


5) Case sensitivity differences (Windows/macOS vs Linux)

Symptom

Build works on one machine but fails on another with missing file errors.

Why it happens

Windows and default macOS filesystems are often case-insensitive, while Linux is case-sensitive. You might have:

On a case-insensitive filesystem, this may appear to work locally but fail in Linux-based CI or remote builders.

Fix

Normalize naming and update Dockerfile references:

# On macOS/Linux, rename safely:
git mv Config.json config.json

Then rebuild.


Symptom

Errors like:

Why it happens

If your context contains symlinks pointing to files that don’t exist (or exist outside the context in a way the builder can’t resolve), BuildKit may fail while walking the directory tree and hashing.

Fix

Find broken symlinks:

# From the context root
find . -xtype l -print

Either remove them, fix the link targets, or exclude them via .dockerignore:

path/to/broken-link

Then rebuild.


7) Permission problems (especially on Linux)

Symptom

Errors mentioning permission denied while walking files:

Why it happens

BuildKit needs to read files to hash them. If your context includes directories/files that your user can’t read (or that Docker can’t read due to ACLs), it can’t compute checksums.

Common culprits:

Fix

Check permissions:

namei -l path/from/error
ls -la path/from/error

Fix ownership/permissions (example):

sudo chown -R "$USER":"$USER" .
chmod -R u+rwX .

If the files should not be in the build context at all (e.g., secrets), exclude them:

id_rsa
*.pem
.env

Then rebuild.


8) BuildKit --mount=type=bind referencing missing paths

Symptom

You use BuildKit syntax like:

RUN --mount=type=bind,source=.,target=/src \
    make build

and get cache-key failures or checksum errors.

Why it happens

A bind mount in BuildKit still relies on the build context (or other sources). If source=... points to a path that doesn’t exist in the build context, BuildKit can’t compute the key for that RUN step.

Fix

Example correction:

WORKDIR /src
COPY . .
RUN make build

If you need --mount=type=cache (safe and common), that’s different and usually not the cause:

RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

9) --mount=type=secret / --mount=type=ssh misconfiguration

Symptom

Errors occur around steps using secrets/ssh:

Why it happens

Secrets and SSH mounts are not part of the build context, but they affect how BuildKit computes the build graph. If you reference a secret that isn’t provided at build time, BuildKit can fail.

Fix: secrets

Dockerfile:

# syntax=docker/dockerfile:1.6
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    npm ci

Build command must provide the secret:

docker build --secret id=npmrc,src=$HOME/.npmrc -t myapp .

Fix: ssh

Dockerfile:

# syntax=docker/dockerfile:1.6
RUN --mount=type=ssh git clone git@github.com:org/private-repo.git

Build with SSH forwarding:

docker build --ssh default -t myapp .

Also ensure your SSH agent is running:

ssh-add -l

10) Remote contexts and Git submodules not present

Symptom

Building from a Git URL or CI checkout fails, but local build works.

Examples:

docker build https://github.com/org/repo.git#main

or CI checks out without submodules, then Dockerfile does:

COPY vendor/somelib /app/vendor/somelib

Why it happens

Remote contexts may not include submodules unless explicitly fetched. CI providers often do shallow clones or omit submodules by default. BuildKit can’t hash what isn’t there.

Fix

If you rely on submodules, ensure they’re present before building.

In CI:

git submodule update --init --recursive
docker build -t myapp .

If using remote Git contexts, consider switching to a local context in CI after checkout, or ensure the remote context includes everything you need.


11) Dockerfile location and -f confusion

Symptom

You specify -f and Dockerfile references paths that don’t exist.

Why it happens

Paths in COPY are always relative to the build context, not the Dockerfile’s directory.

Example structure:

repo/
  docker/
    Dockerfile
  app/
    package.json

Dockerfile:

COPY app/package.json /app/

Correct build:

docker build -f docker/Dockerfile -t myapp .
# context is repo root

Incorrect build:

docker build -f docker/Dockerfile -t myapp docker
# context is ./docker, so app/ doesn't exist

Fix

Always choose the context that contains all needed files.


12) Corrupted BuildKit state / builder issues

Symptom

You’ve verified paths and .dockerignore, but builds still fail inconsistently, especially after upgrades or disk issues.

Why it happens

BuildKit stores state (cache, metadata, intermediate refs). Rarely, the builder’s state can get into a bad condition.

Fix options

  1. Prune build cache:
docker builder prune

More aggressive:

docker builder prune --all --force
  1. Prune system-wide unused data (careful: this removes unused images/containers/networks):
docker system prune --all
  1. If using buildx with a named builder, recreate it:
docker buildx ls
docker buildx rm mybuilder
docker buildx create --name mybuilder --use
docker buildx inspect --bootstrap

Then rebuild with plain progress:

docker buildx build --progress=plain -t myapp .

A step-by-step debugging workflow

When you hit the error, use this workflow to converge quickly.

Step 1: Get the full error and the failing instruction

docker build --progress=plain -t debugimg .

Look for the step number and instruction, e.g.:

#7 [3/8] COPY package.json package-lock.json ./
#7 ERROR: failed to compute cache key: "/package-lock.json": not found

Now you know it’s a COPY input issue, not a runtime error.

Step 2: Confirm the file exists relative to the context root

If the build command is docker build ... ., then run:

ls -la package-lock.json

If it’s missing, fix your repo or Dockerfile. If it exists, continue.

Step 3: Check .dockerignore for exclusions

cat .dockerignore

If you see patterns that might match the file, test quickly by temporarily moving .dockerignore away:

mv .dockerignore /tmp/dockerignore.bak
docker build --progress=plain -t debugimg .
mv /tmp/dockerignore.bak .dockerignore

If the build succeeds without .dockerignore, your ignore patterns are the cause. Fix them properly (don’t leave .dockerignore removed—large contexts slow builds and may leak secrets).

Step 4: Verify you’re using the correct context and Dockerfile

Print your working directory and build command:

pwd

If Dockerfile is elsewhere:

docker build --progress=plain -f path/to/Dockerfile -t debugimg path/to/context

Remember: COPY sources are relative to the context, not the Dockerfile.

Broken symlinks:

find . -xtype l -print

Permissions:

# Replace with the path mentioned in the error
ls -la path
namei -l path

Fix or exclude problematic paths.

Step 6: Compare BuildKit vs legacy builder (diagnostic only)

DOCKER_BUILDKIT=0 docker build --progress=plain -t debugimg .

If legacy builder works but BuildKit fails, treat it as a sign your build inputs are ambiguous or your context has traversal issues. Fix the underlying file/path/ignore issue rather than disabling BuildKit permanently.


Best practices to prevent cache-key failures

Keep the build context small and intentional

A huge context increases the chance of permission/symlink/odd-file issues and slows builds.

Use .dockerignore to exclude:

.git
node_modules
dist
target
*.log
.env
*.pem

Then explicitly COPY only what you need.

Prefer multi-stage builds over host-generated artifacts

Instead of copying dist/ that may or may not exist:

This removes a whole class of “file not found” cache-key failures.

Avoid copying secrets; use BuildKit secrets instead

Don’t do:

COPY .env /app/.env

Do:

# syntax=docker/dockerfile:1.6
RUN --mount=type=secret,id=env,target=/run/secrets/env \
    some-command-that-reads-it

Build:

docker build --secret id=env,src=.env -t myapp .

Be explicit with paths and naming

Pin the Dockerfile frontend if using advanced syntax

If you use --mount=type=cache|secret|ssh, add:

# syntax=docker/dockerfile:1.6

at the top of the Dockerfile. This ensures the builder interprets the syntax correctly.


Reference: useful commands

Build with maximum readable output

docker build --progress=plain -t myimg .

Show builders and BuildKit info

docker buildx ls
docker buildx inspect --bootstrap

Prune build cache

docker builder prune
docker builder prune --all --force
find . -xtype l -print

Identify what’s huge in your context (helps decide .dockerignore)

du -sh ./*

Confirm which files are present

ls -la
ls -la path/to/thing

Summary of the most common fixes

If you want the highest-probability solutions in order:

  1. Run: docker build --progress=plain ... and identify the exact failing COPY/ADD/mount.
  2. Verify the file exists in the build context path.
  3. Fix .dockerignore (often the real culprit).
  4. Fix the build context (build from repo root, not a subfolder).
  5. Fix case sensitivity and broken symlinks.
  6. Fix permissions or exclude unreadable paths.
  7. Prune/recreate builder if everything is correct but BuildKit state is corrupted.

If you paste the exact error line and the relevant Dockerfile snippet (especially the failing COPY/RUN --mount step) and your build command, I can map it to the specific root cause and give a targeted fix.