Docker "failed to solve: lstat /var/lib/docker: permission denied" in CI
The daemon stores layers and overlay state under /var/lib/docker, a directory owned by root and unreadable to the build. When the build context root or a bind mount resolves into that directory, BuildKit cannot lstat it and fails with "permission denied".
What this error means
A docker build/buildx build fails while preparing the context with failed to solve: lstat /var/lib/docker/...: permission denied. It often happens when the build context is set to / or a parent of the daemon root.
ERROR: failed to solve: lstat /var/lib/docker/overlay2: permission deniedDiagnose it: build context, cache, or platform?
A Dockerfile that builds locally and fails in CI usually differs in one of three ways: the build context contains different files, the layer cache is cold or poisoned, or the runner architecture does not match what the base image provides.
# what is actually being sent as build context (dockerignore applies)
docker build --no-cache --progress=plain -t probe . 2>&1 | head -40
# what platform are you on, and what does the base image support?
docker version --format '{{.Server.Arch}}'
docker buildx imagetools inspect <base-image> | grep -i platform
# prove it is not a cache artefact
docker build --no-cache .Common causes
The build context includes the daemon root
Running docker build . from / or another directory that contains /var/lib/docker makes BuildKit try to walk root-owned overlay state.
A bind mount pointing into /var/lib/docker
A volume or --mount=type=bind,source= aimed at the daemon root exposes paths the build user cannot read.
A rootless build reaching a root-owned path
A rootless builder running as an unprivileged user cannot lstat root-owned daemon directories.
How to fix it
Scope the build context to your project
- Run the build from your source directory, not
/or the daemon root. - Pass an explicit context path so BuildKit never walks system directories.
cd /home/runner/work/app/app
docker build -t myorg/app:ci .Exclude the daemon root from the context
- If the context legitimately sits above the project, add the daemon path to
.dockerignore. - Better, restructure so the context never overlaps
/var/lib/docker.
# .dockerignore
var/lib/docker
**/docker/overlay2Keep the build context small and deterministic
- A missing
.dockerignoresendsnode_modules,.git, and build output to the daemon, which is slow and can change layer hashes between environments. - A
COPYof a path that exists locally but is gitignored will fail in CI, because the runner only has what the checkout produced. - Multi-arch builds need
buildxand QEMU set up explicitly; a plaindocker buildon an ARM runner silently produces an ARM image.
How to prevent it
- Always build from a tight, project-scoped context directory.
- Never set the build context to
/or a parent of the Docker daemon root.