A `docker pull` of an image from your company's internal registry fails with `pull access denied for myapp, repository does not exist or may require 'docker login'`. What does that error actually mean, and how do you work through it?
answer
- 401 not 404 — existence hidden on purpose
- No host in reference → docker.io/library
- Login is per registry host
- ECR token expires in 12 h
- manifest unknown = auth was fine
basics
~20 sThe registry answered with an auth error, and the client cannot tell "no such repository" from "you are not allowed". Check the image reference includes the registry hostname, run docker login <registry> with valid credentials, and confirm your account has pull rights on that repository.
solid answer
~50 sThat message is Docker's catch-all for an unauthenticated or unauthorized response on the manifest request. Registries deliberately return 401 rather than 404 for repositories you cannot see, so "does not exist" and "no permission" collapse into one message. I work through it in order: 1. **Check the reference.** `docker pull myapp:1.2` with no host resolves to `docker.io/library/myapp` — a public Hub repo, not our registry. It must be `registry.corp.example.com/team/myapp:1.2`. 2. **Check I am logged in to *that* host.** `docker login registry.corp.example.com`; credentials are per-registry-host, so being logged in to Hub does nothing here. 3. **Check the credential is still valid.** Cloud registry tokens expire (ECR's is 12 hours); a stale entry in `~/.docker/config.json` gives the same error. 4. **Check permissions on the exact repository path.** Access is usually scoped per repository or project, so I may be able to pull `team/other` and not `team/myapp`. 5. **Check *where* it fails** — a CI runner or a server has its own credential store; my laptop working proves nothing.
code
bash · 15 lines# Wrong: resolves to docker.io/library/myapp
docker pull myapp:1.2
# Right: explicit registry host
docker pull registry.corp.example.com/team/myapp:1.2
# Authenticate against that specific host
docker login registry.corp.example.com -u ci-bot --password-stdin < token.txt
# Which hosts do I currently hold credentials for?
cat ~/.docker/config.json
# Amazon ECR: password is a short-lived token, username is literally 'AWS'
aws ecr get-login-password --region eu-west-1 \
| docker login --username AWS --password-stdin 111122223333.dkr.ecr.eu-west-1.amazonaws.comgo deeper
Know the message means an auth failure, that references without a hostname go to Docker Hub, and that docker login <registry> is the first fix.
Explain why registries return 401 rather than 404, that credentials are keyed per host, and that cloud tokens expire.
Separate authentication from authorization quickly, reason about where the pull actually runs (runner, node, user), and point at repository-scoped grants.
Frame it as an identity and credential-distribution problem: which identities exist, how they are minted and rotated, and why humans and machines should not share them.
## What the error really is When you run `docker pull`, the daemon asks the registry for the image manifest: `GET /v2/<repository>/manifests/<tag>`. If the registry answers `401 Unauthorized` (or `403 Forbidden`) instead of the manifest, the CLI prints `pull access denied for <name>, repository does not exist or may require 'docker login'`. The wording is vague on purpose. A registry that returned `404 Not Found` for private repositories would leak which repositories exist to anyone who probes it. So most registries — Docker Hub included — answer *unauthenticated* requests for both "missing" and "private but real" repositories the same way. The client cannot distinguish them, so it prints both possibilities. ## The image reference is the first suspect A Docker image reference has the shape `[registry-host[:port]/]namespace/name[:tag|@digest]`. When the first path component contains no dot or colon and is not `localhost`, Docker does **not** treat it as a hostname — it defaults the registry to Docker Hub (`docker.io`) and, if there is no namespace, prefixes `library/`. - `myapp:1.2` → `docker.io/library/myapp:1.2` - `team/myapp:1.2` → `docker.io/team/myapp:1.2` - `registry.corp.example.com/team/myapp:1.2` → your registry (the dot makes it a host) So the single most common cause of this error is a reference that silently points at Docker Hub, where the repository genuinely does not exist. ## Credentials are per registry host `docker login` takes a registry host and stores an entry keyed by that host. `docker login` with no argument targets Docker Hub. Being authenticated to Hub grants nothing on `registry.corp.example.com`, and vice versa. `docker logout <host>` removes the entry. Some registries also require a specific username convention or a token rather than your interactive password: Docker Hub wants a personal access token when 2FA is on, GitHub Container Registry wants a PAT with `read:packages`, and Amazon ECR wants the literal username `AWS` with a short-lived password produced by `aws ecr get-login-password`. ## Expiry Cloud registry credentials are usually time-limited. An ECR authorization token lasts 12 hours; after that, the stored entry is still present in the config file but rejected by the registry, producing exactly the same denial message. "It worked this morning" is a strong hint that the credential expired rather than that permissions changed. The durable fix is a credential helper that mints a fresh token on every request rather than a one-off login. ## Permissions are scoped per repository Even when authenticated, authorization is granted per repository (or per project/namespace). Harbor grants roles inside a project; ECR combines IAM policies with repository policies; Artifact Registry uses IAM roles per repository. So `docker pull registry/team-a/svc` can succeed while `registry/team-b/svc` fails for the same user. The error text is identical, which is why checking the exact repository path against the access model matters. ## Where the pull runs matters The credential store belongs to the user account on the machine running the Docker client. Your laptop, a CI runner, and a production host each have their own `~/.docker/config.json`. A pull that works locally and fails in CI almost always means the pipeline never logged in, logged in as a different identity, or ran as a different OS user than the one that holds the config file. Container orchestrators solve this with their own credential objects rather than a shell login — that is orchestrator territory, but the underlying registry auth is the same. ## A practical checklist 1. Print the full reference you are pulling; add the registry host if missing. 2. `docker login <host>` and watch for `Login Succeeded`. 3. Re-pull. If it still fails, you have an authorization (not authentication) problem: ask for pull rights on that specific repository. 4. Confirm the tag exists — a valid repository with a wrong tag gives `manifest unknown`, a *different* error, which is itself useful evidence that auth is fine. 5. Reproduce on the machine that actually failed, as the user that actually runs Docker.
- The same pull works on your laptop but fails on a CI runner. What do you check?The runner has its own credential store under the user that executes the job, so the login step either never ran, ran as a different OS user, or used a different identity. I check that the pipeline performs a registry login before the pull, that the secret it uses is present and non-expired in that environment, and that the login host string matches the image reference exactly. Ephemeral runners lose `~/.docker/config.json` between jobs, so the login must be part of every job.
- You are logged in successfully but the pull is still denied. What is happening?Authentication succeeded, authorization did not: the identity is valid but has no pull permission on that particular repository. Registries scope grants per repository, project, or namespace, so I verify the exact repository path against the access model and request the read grant for it. If the registry issues scoped bearer tokens, the returned token simply lacks the `pull` action for that repository scope.
It is like a building where the receptionist says "no such tenant, or you're not on the list" for every name they won't confirm — the answer is identical whether the office is empty or you simply lack a badge.
saying these in an interview costs you the question
- Reading "repository does not exist" literally and concluding the image was deleted
- Believing one `docker login` authenticates you to every registry
- Pulling `myapp:1.2` and expecting it to hit the company registry
- Assuming an old successful login stays valid forever on cloud registries
- Fixing it by adding the registry to `insecure-registries`, which addresses TLS, not authorization