Docker Compose "pull access denied for <image>" in CI
Compose could not pull an image one of its services references. pull access denied for <image>, repository does not exist or may require docker login means the service image is private (or misnamed) and the job has no registry authentication.
What this error means
A docker compose up/pull fails with pull access denied for myorg/internal, repository does not exist or may require 'docker login'. A docker login for that registry before the compose command fixes it.
Error response from daemon: pull access denied for myorg/internal-worker,
repository does not exist or may require 'docker login': denied: requested access to the resource is deniedDiagnose it: read the resolved config, not the file you wrote
Compose merges override files, interpolates variables, and applies defaults before it does anything. Most Compose failures in CI are visible in the resolved configuration and invisible in the source file, because the value you are debugging came from an unset variable that quietly became an empty string.
# the fully merged, interpolated configuration Compose will actually run
docker compose config
# fail loudly on unset variables instead of silently interpolating empty
docker compose --env-file .env config --quiet || echo "invalid"
# which override files were picked up
docker compose config --services
Common causes
A private service image with no login
A service image: pointing at a private repository needs docker login for that registry; without it Compose cannot pull.
A wrong image name or missing registry prefix
A typo or an image that defaults to Docker Hub when it lives on GHCR/ECR makes Compose look where the repo does not exist.
How to fix it
Log in before the compose command
Authenticate to each registry the services pull from.
echo "${{ secrets.GHCR_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
docker compose pull
docker compose up -dUse fully-qualified image references
Include the registry host in each service image.
services:
worker:
image: ghcr.io/myorg/internal-worker:1.4.2Bind mounts behave differently on a runner
- A relative bind source is resolved against the compose file location, not the working directory of the shell that invoked it.
- The host path must exist before
up. Compose creates missing directories for named volumes but not for bind mounts, and the failure surfaces as a mount error rather than a missing-path error. - On a CI runner the workspace path differs from your machine, so any absolute host path in a compose file is a portability bug waiting for its first CI run.
- Prefer named volumes for anything that does not genuinely need to be read from the host. They remove the whole class of problem.
How to prevent it
- Log in to all service registries before
docker compose pull/up. - Use fully-qualified image references in services.
- Store registry tokens as CI secrets, not in the compose file.