Why does my Bitbucket pipeline fail with a permission denied error when accessing Docker registry?
0 reputation · 15 Jan 2025, 20:03 UTC
0 reputation · 15 Jan 2025, 20:03 UTC
Our team maintains a Bitbucket repository that uses a self‑hosted runner to build Docker images and push them to an internal container registry. The pipeline step that executes docker push consistently fails with a "permission denied" message, even though the registry URL, repository name, and tag are correct. We have stored the registry username and password as secured repository variables and injected them into the step via docker login, but the authentication still appears to be rejected. We suspect the issue could be related to how the credentials are passed, network restrictions on the runner, or a mismatch between the Docker daemon configuration and the registry’s TLS settings.
How can we confirm that the credentials supplied to docker login are being used by the Docker daemon? What checks should we perform to verify network connectivity and TLS trust between the runner and the registry? Are there specific Bitbucket Pipelines logs or environment variables that help diagnose authentication failures for Docker push?
When docker push returns permission denied, the Docker daemon has authenticated with the registry but the target repository or image name is not allowed for the supplied credentials. In Pipelines this usually means one of three things:
docker login step never ran or used the wrong username/password.Insert a short diagnostic step immediately after the login command:
script:
- docker login -u $DOCKER_USER -p $DOCKER_PASSWORD $REGISTRY
- docker info | grep "Username"
- docker config inspect | grep -i $REGISTRY
The docker info output should show the logged‑in user. The docker config inspect command will list the auth entry for your registry; confirm the auth field is present and not empty. If the user line is missing, the login failed silently – check that the environment variables are correctly named and marked as secured in the repo settings.
From the same step, run:
script:
- curl -v https://$REGISTRY
- docker pull $REGISTRY/hello-world:latest
If curl fails with a TLS error, you need to trust the registry’s certificate on the runner. Add the CA to the Docker daemon’s ca.crt file or use the --tls-verify=false flag for testing (not recommended for production).
/etc/docker/certs.d/$REGISTRY/ca.crt on the runner.Log into the registry’s web UI or API with the same credentials and verify that the user has push rights for the target repository. For example, in Harbor you can inspect the role assignments for the account.
Also confirm the image name and tag you are pushing match the repository exactly. A common mistake is a missing organization prefix or a mistyped tag.
docker login --debug to see the full authentication exchange.DOCKER_USER and DOCKER_PASSWORD in the pipeline log – they should appear as masked values (e.g., ****).DOCKER_USER, DOCKER_PASSWORD, REGISTRY.docker login manually on the runner and confirm you can pull and push a test image.--debug flags turned on for Docker commands.If the error persists after these checks, please let us know whether your registry uses a self‑signed certificate or requires a bearer token instead of a username/password. That will determine whether you need to add a custom ~/.docker/config.json entry or switch to docker login --username $DOCKER_USER --password-stdin with a token.
Use comments to ask for clarification. Post a solution as an answer.
26,525 reputation · 15 Jan 2025, 22:20 UTC
On self-hosted runners, a frequent cause is that docker login writes its credentials to ~/.docker/config.json inside the step container, not on the host. If your login happens in one pipeline step and the docker push in another, the second step starts with a fresh filesystem and no auth entry — the daemon is shared, but the config file is not. Keep login, build, and push in the same step, or mount a shared DOCKER_CONFIG directory.
echo $DOCKER_CONFIG and cat ~/.docker/config.json right before the push.registry.example.com and registry.example.com:5000 are different auth entries.Assuming a recent Docker CLI (20.10+); verify behavior on your runner version before relying on it.