Intro
Docker BuildKit is the default builder engine in modern Docker versions, and it brings faster parallel builds, better caching, and improved security. However, it also introduces its own set of error messages and failure modes that can frustrate developers, DevOps engineers, and platform teams. This article is a practical guide to the most common BuildKit errors and the specific fixes that resolve them. It covers diagnostics, recovery, and prevention with concrete commands and real-world examples.
The goal is operational safety: observe before changing, limit the blast radius, use placeholders instead of secrets, verify the result, and document how to recover if the expected state is not reached. Whether you are debugging a failed docker build in CI or optimizing a local build, the workflows below will help you move from symptom to solution quickly.
Version and Environment Inventory
Before troubleshooting any BuildKit error, confirm what you are working with. Run the following read-only commands to establish the current state:
docker version
# Check the client and server versions. BuildKit is included in the server (Engine) version.
# Example output snippet:
# Client: Docker Engine - Community
# Version: 24.0.7
# Server: Docker Engine - Community
# Engine:
# Version: 24.0.7
docker buildx version
# Shows buildx version and the default builder. Buildx is the client interface to BuildKit.
# Example output:
# github.com/docker/buildx v0.12.0 1234abcd
# Default build driver: docker
docker info --format '{{.Builder}}'
# Indicates whether BuildKit is the default builder. Output should be "buildkit" if enabled.
If BuildKit is not enabled, you can enable it by setting the environment variable DOCKER_BUILDKIT=1 for a single command, or by updating the Docker daemon configuration file (/etc/docker/daemon.json) with "features": { "buildkit": true } and restarting Docker. However, for modern Docker versions (23.0+), BuildKit is enabled by default via the docker driver in buildx.
When data or state is involved, confirm where files are stored before changing containers. A named volume such as app_data:/var/lib/app is managed by Docker and is usually easier to reuse across container rebuilds. A bind mount such as ./data:/var/lib/app maps a host directory directly and is useful for local development, but it can expose permission, portability, and backup problems if the same path does not exist on another machine.
A small production-like local test should include a restart test: stop the container, recreate it, and confirm the application still sees the expected files. If the data disappears, the service was probably writing to the container filesystem instead of a volume or mount.
Safe Configuration Path
Configuring BuildKit safely means making one change at a time and verifying its effect. A common mistake is editing the daemon configuration or Dockerfile in multiple places simultaneously, which makes it impossible to attribute a fix or regression.
Example: Adjusting BuildKit resource limits
Suppose your builds are being killed by the OOM (out-of-memory) killer. You might want to increase the BuildKit memory limit. Instead of guessing, observe first:
docker buildx inspect --bootstrap
# This starts the builder and shows its current configuration, including resources.
# Example output snippet:
# Platforms: linux/amd64, linux/arm64
# Driver: docker-container
# ...
To change resource limits, you would create a new builder with adjusted limits, because the default builder's configuration is fixed:
docker buildx create --name mybuilder --driver docker-container \
--driver-opt env.BUILDKIT_STEP_LOG_MAX_SIZE=10485760 \
--driver-opt env.BUILDKIT_STEP_LOG_MAX_SPEED=1048576
docker buildx use mybuilder
docker buildx inspect --bootstrap
# Verify the new builder is active and note its resources
After the change, run a representative build and monitor memory usage:
docker buildx build --progress=plain -t myapp:test . 2>&1 | tee build.log
# The plain progress output is easier to scan for errors and memory issues.
If the build succeeds and memory stays within limits, the change was effective. If not, revert to the previous builder or adjust values incrementally.
Verification and Diagnostics
When a build fails, BuildKit often prints a verbose error log. Use --progress=plain and --no-cache to get the full picture without cached artifacts masking the problem:
docker buildx build --progress=plain --no-cache -t myapp:debug . 2>&1 | tee build-debug.log
Then inspect the log for specific error patterns. Here are some common ones and what they indicate:
failed to solve: executor failed running [...]: exit code: 100- a RUN command failed; the exit code and preceding output will show why.failed to compute cache key: ...- usually a problem with the Dockerfile context or a missing file in .dockerignore.error: failed to get status: rpc error: code = Unavailable desc = connection error- BuildKit daemon connection issue; check if the builder is running (docker buildx ls) and restart if needed.ERROR: unable to prepare context: path "..." not found- the build context path is incorrect or you are running the command from the wrong directory.
To dig deeper into a specific failed container, use docker ps -a to list exited containers and docker logs <container_id> to see the full output from the build step. For example, if a test fails during build, the container that ran the test will be in an exited state, and its logs will contain the test output.
Failure Modes and Recovery
Mode 1: BuildKit daemon not running or unreachable
Symptom: You see errors like Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? or error: failed to get status: rpc error: code = Unavailable.
Cause: The Docker daemon or a specific BuildKit builder is not running.
Fix:
- Check the Docker daemon status:
systemctl status docker # on systemd systems
# or
service docker status
If not running, start it: sudo systemctl start docker.
- If using a docker-container builder (separate from the daemon), check its status:
docker buildx ls
# Look for the builder's state, e.g., "running" or "stopped"
If it's not running, start it:
docker buildx create --name mybuilder --driver docker-container --use
docker buildx inspect --bootstrap
Mode 2: Cache misses causing slow builds or incorrect results
Symptom: Builds take longer than expected, or you notice repeated downloads of dependencies despite no changes.
Cause: Inefficient Dockerfile ordering or incorrect cache invalidation.
Fix: Optimize the Dockerfile layer order to maximize cache hits. Put frequently changing instructions (like COPY . .) last, and separate dependency installation from source code copying:
# Bad: invalidates cache on any source change
FROM node:18
WORKDIR /app
COPY . .
RUN npm install
# Good: separate dependency installation
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
Also, use a .dockerignore file to exclude unnecessary files from the build context, which reduces context size and prevents spurious cache invalidations.
Mode 3: Build fails with exec format error on multi-arch builds
Symptom: When building for a different architecture (e.g., arm64 on an amd64 host), the build fails with exec format error during a RUN command.
Cause: The RUN command tries to execute a binary for the target architecture on the host, but without proper emulation or buildx setup.
Fix: Use the docker-container driver with buildx, which supports multi-arch builds out of the box:
docker buildx create --name multiarch --driver docker-container --use
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:multiarch .
Alternatively, if you must use the default docker driver, you need QEMU emulation registered on the host.
Mode 4: SSH agent forwarding not working during build
Symptom: You need to clone private repositories during a build, but SSH authentication fails.
Cause: By default, the build container does not have access to the host's SSH agent.
Fix: Use the --ssh flag to forward the SSH agent:
docker build --ssh default=$SSH_AUTH_SOCK -t myapp .
In the Dockerfile, install the SSH client and use it in a RUN command:
FROM alpine
RUN apk add --no-cache openssh-client
RUN --mount=type=ssh git clone [email protected]:example/repo.git
The --mount=type=ssh ensures the agent is available only during that RUN command.
Mode 5: Secrets leaking into the image
Symptom: You pass secrets via build args or environment variables, and they end up in the image layers, posing a security risk.
Cause: Using build args for secrets is insecure because they are persisted in the image history.
Fix: Use the --secret flag to mount secrets at build time without baking them into the image:
docker build --secret id=mysecret,src=/path/to/secret.txt -t myapp .
In the Dockerfile:
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
The secret is available only during that RUN command and is not included in the final image.
Mode 6: BuildKit context size too large
Symptom: The build fails with an error like ERROR: failed to send context: context too large.
Cause: The build context (the directory and files sent to the daemon) exceeds a limit, often because of large or many files.
Fix: Use a .dockerignore file to exclude unnecessary files and directories. For example:
.git
node_modules
*.log
If the context must be large, consider using a remote context or a dockerfile specified via URL.
Common Pitfalls and How to Avoid Them
- Not using .dockerignore - Sending the entire project directory, including
.git,node_modules, and build artifacts, slows down the context transfer and can cause cache misses. Always include a.dockerignorefile with at least.gitand common heavy directories.
- Relying on
latesttag in base images - UsingFROM ubuntu:latestmakes builds non-reproducible because the base image changes over time. Pin specific versions or digests:FROM ubuntu:22.04.
- Ignoring exit codes in RUN commands - A RUN command may fail but the build continues if the command is a shell script that returns success. Use
set -ein scripts or chain commands with&&to ensure failures stop the build.
- Mixing architecture mismatches - If your host is amd64 and you try to run an arm64 binary without proper setup, you will see
exec format error. Use buildx with the docker-container driver for cross-platform builds.
- Overlooking BuildKit cache mounting - BuildKit offers
--mount=type=cacheto speed up package manager caches. Not using it can make builds slower. For example:
RUN --mount=type=cache,target=/var/cache/apt \
apt-get update && apt-get install -y ...
Operations Checklist
Before and after any BuildKit-related change, run through this checklist to ensure you have observed, documented, and verified:
- [ ] Confirm Docker and BuildKit versions:
docker versionanddocker buildx version. - [ ] Identify the active builder:
docker buildx ls. - [ ] Reproduce the build failure with
--no-cache --progress=plainand capture output to a file. - [ ] Check Docker daemon logs for build-related errors:
journalctl -u docker(systemd) or/var/log/docker.log. - [ ] Review the Dockerfile for layer ordering and secret handling.
- [ ] Ensure
.dockerignoreis present and excludes unnecessary files. - [ ] If using SSH or secrets, verify that
--sshor--secretflags are used correctly and not exposed in image layers. - [ ] Apply one fix at a time and rerun the build to verify.
- [ ] Document the fix and rollback plan.
Conclusion
Docker BuildKit errors are often rooted in configuration, Dockerfile structure, or environment issues. By systematically gathering version information, observing failure modes, and applying targeted fixes with verification, you can reduce build failures and improve build performance. Remember to use the native BuildKit features like cache mounts, secrets, and SSH forwarding to keep builds fast and secure. Always test changes in a controlled manner and document the recovery path for future incidents.
As a next step, pick one common error you have encountered, reproduce it with the diagnostic commands from this article, and apply the corresponding fix. Share your findings with your team to build collective troubleshooting knowledge.