errno

docker manifest inspect fails with "permission denied" on /etc/docker/certs.d

· tested on Docker CE on Linux, private registry with CA in /etc/docker/certs.d, non-root deploy user

Symptom

A deployment script verifies that the requested tag exists before it touches a running stack. Under the deploy user it fails:

$ docker manifest inspect registry.example.com/backend:1.42
error: open /etc/docker/certs.d/registry.example.com/ca.crt: permission denied

Under root on the same node the identical command returns the manifest. Pulling images works for the deploy user too, which makes the failure look arbitrary.

Why root works and your user does not

docker manifest inspect is one of the CLI commands that talks to the registry from the client process, not through the daemon. So it needs to read the registry CA itself — and /etc/docker/certs.d/... is intentionally readable only by root and the daemon.

docker pull, by contrast, is executed by the daemon, which runs as root and can read the CA. That is the whole asymmetry: adding your user to the docker group grants access to the daemon socket, not to root-owned files on disk.

Three tempting fixes and why I rejected them:

Fix: ask the registry API directly

An existence check does not need Docker at all. The registry v2 manifest endpoint answers a HEAD:

image="backend"; tag="1.42"; reg="registry.example.com"

if curl -sfk -X HEAD "https://${reg}/v2/${image}/manifests/${tag}" \
        -H 'Accept: application/vnd.docker.distribution.manifest.v2+json'; then
  echo "tag exists"
else
  echo "tag missing — aborting deploy" >&2
  exit 1
fi

Notes from making this work in a real pipeline:

Keep the pre-check honest

Whatever mechanism you use, make the failure loud and make it stop the deploy:

set -euo pipefail
image_exists "$reg" "$image" "$tag" || { echo "refusing to deploy missing tag" >&2; exit 1; }
docker stack deploy -c "docker-compose-${stack}.yml" "$stack"

A silent pre-check that fails open is worse than no pre-check: the stack update proceeds, the service cannot pull, and you get a rolling failure instead of a clean abort.

docker registry swarm tls