docker pull access denied: 5 Reasons Image Pulls Get Rejected (and the Fix)
Symptom: the pull gets rejected
pull access denied for myapp/api, repository does not exist or may
require 'docker login': denied: requested access to the resource is denied
One-line diagnosis: the registry (usually Docker Hub) refuses anonymous access to this image. Only two things cause that: "this repository doesn't exist", or "it exists but you lack permission". The error is deliberately vague (for security, it doesn't distinguish the two) — debugging means eliminating each cause.
Five causes, ranked by frequency
1. Misspelled image name (most common)
First, get the image-name parsing rules straight:
nginx → docker.io/library/nginx (official image)
bitnami/nginx → docker.io/bitnami/nginx (user/org image)
registry.example.com/app → image on a private registry
Two frequent mistakes: pulling a personal image as if it were official (docker pull myapp resolves to library/myapp, which doesn't exist), or adding/dropping a path segment. Check the full path and spelling on the Docker Hub page first.
2. The tag doesn't exist
manifest for myapp/api:v1.2 not found is the sibling error — the repo exists but the tag is wrong. The assumption that every image has a latest tag is the classic trap: official images maintain latest, personal images often only ever pushed version tags. Check the Tags list on the Hub page; don't write tags from memory.
3. Not logged in to the private registry (or wrong account)
docker login # Docker Hub by default
docker login registry.example.com # private / self-hosted registry
cat ~/.docker/config.json # which registries, which accounts
Common traps: logged in with the company account while pulling a personal repo (or vice versa); expired CI credentials; logged in, then switched docker contexts. Re-login to the right registry.
4. Docker Hub rate limits (different error, same neighborhood)
Anonymous pulls are capped at 100 per 6 hours (per IP). The over-limit error is toomanyrequests: You have reached your pull rate limit — a different thing from access denied, but frequently confused with it. A logged-in free account gets 200, which is usually plenty.
5. Mainland China networks: configure a registry mirror
Direct connections to Docker Hub from mainland China often time out or get reset, producing a zoo of error shapes (timeout, EOF, unexpected status). The standard fix is a mirror in /etc/docker/daemon.json:
{
"registry-mirrors": ["https://docker.m.daocloud.io"]
}
Then sudo systemctl restart docker. Watch out: daemon.json must not have trailing commas — break it and the daemon won't start; see Cannot connect to the Docker daemon for that diagnosis.
The triage flow (one minute)
# 1. What's the full error? (access denied / not found / rate limit point in different directions)
docker pull myapp/api:v1.2
# 2. Login state
docker login
# 3. Try an official image — if it pulls, the problem is name/permissions; if not, it's the network
docker pull hello-world
# 4. Network case: is the mirror active?
docker info | grep -A3 "Registry Mirrors"
Step 3 is the fork: hello-world fails too → network/rate limit (causes 4/5); only your image fails → name/permissions (causes 1/2/3).
Telling the neighboring errors apart
| Error | Meaning | Guide |
|---|---|---|
| pull access denied | repo missing or no permission | this post |
| manifest ... not found | repo exists, tag is wrong | manifest unknown |
| toomanyrequests | rate limited | log in or wait |
| i/o timeout / EOF | network unreachable | mirror config (cause 5) |
Registry authentication: the four credential sources that conflict
Docker pulls authenticate from four places: docker login credentials per-registry (~/.docker/config.json), credential helpers (osxkeychain/wincred — per-registry helper config), imagePullSecrets in Kubernetes (namespace-scoped — CI pods in the wrong namespace see no creds), and instance roles (ECR IAM, GCP SA — ambient auth where the helper resolves per-URL). The conflicts: a stale docker login overriding a working instance role (delete the registry entry from config.json), a credential helper mismatch after a workstation change, and K8s nodes with both an attached role and a conflicting secret. The diagnostic order when auth fails: docker pull from the same machine with the same user that CI uses — isolates credential source from pipeline config in one command.
Private registry patterns: ECR/GHCR/Harbor specifics
ECR: tokens expire every 12 hours (docker login needs refreshing in CI — aws ecr get-login-password | docker login; cron or per-pipeline), and the region prefix in the URL is mandatory (missing region = the classic denied-with-valid-credentials). GHCR: usernames are case-sensitive (the token works, the repo path casing wrong = denied), and PAT scopes must include read:packages even for public images via API. Self-hosted Harbor: the project name is part of the path and case-sensitive, and anonymous pull depends on project publicity — misconfigured projects deny everything except with a login. Each registry has one dominant denied-caused-by-X — knowing yours saves an afternoon.
Supply-chain hygiene: pinning what you pull
Beyond getting the pull working: what you pull matters. Pin by digest (image@sha256:... — immutable, survives tag retargeting; the hash verifier article covers the integrity model) rather than mutable tags (latest, v1.2 — the same pull tomorrow can be a different image), verify signatures where the registry supports them (cosign on GHCR/ECR — the supply-chain gate), and scan in CI before deploy (trivy/grype against every digest bump — catching CVEs at pull time, not incident time). The pull-access fight is operations; the pull-integrity layer is security — the same docker pull command, two different failure domains.
When “access denied” is actually a mirror or proxy
Corporate HTTP proxies and insecure registry mirrors sometimes rewrite 401/403 into a generic denied page, or strip Authorization on the second hop. If docker login succeeds but docker pull still denies only on the VPN:
- Compare
docker pullwith and without the proxy env vars (HTTP_PROXY/HTTPS_PROXY) - Confirm the mirror allows anonymous pulls for the namespace you need
- Prefer digest pulls once credentials work so a later tag move does not look like a new auth failure
Keep a known-public image (hello-world) as the control case so you do not debug auth while the network path is the real problem.
Checklist
- Full image path check: official = no prefix, personal = with username, private = with hostname
- Does the tag exist? Verify on the Hub Tags page — never assume latest
docker loginagainst the right registry? See~/.docker/config.json- Is the error access denied or rate limit? The latter means login/wait
- The
docker pull hello-worldfork test: network or permission? - Mainland networks: configure registry-mirrors
Triage notes
The five causes are ranked by probability, but the real structure lies in the error's deliberate vagueness — for security reasons it does not distinguish "repository doesn't exist" from "no permission", so triage can only proceed by elimination. Step 3's use of hello-world as a watershed is designed: if even official images won't pull, it is network or rate limiting (causes 4/5); only when just your image fails is it name or permissions (causes 1/2/3). Rate limiting earns its own line in the checklist because toomanyrequests and access denied surface differently yet are routinely conflated.
Provided by ToolVault. Related tools: Dockerfile Generator, Run to Compose, Linux Cheatsheet. Related reading: manifest unknown, Docker daemon unreachable. See the homepage for more developer tools.
Related Tools
Related Articles
Linux Crontab in Practice: Commands, Logs, and Debugging
Crontab beyond syntax: three must-know commands, /etc/cron.d vs user crontabs, five-step debugging, PATH/timezone/% escaping, MAILTO, and what to do when you need second-level scheduling.
docker run to docker-compose: The Complete Migration Guide (with Flag Map)
Migrate docker run commands to docker-compose.yml: five advantages of compose over raw run, a flag-by-flag mapping table (ports, volumes, env, restart, networks), the classic pitfalls (bare -e passthrough, anonymous volumes, --rm semantics), and an online converter that runs locally in your browser.
Permission denied (publickey): 6 Reasons Git Push Fails Over SSH (and the Fix for Each)
git clone or push rejected with Permission denied (publickey) fatal: Could not read from remote repository? Covers missing keygen, key not loaded in the agent, public key not added to GitHub/GitLab, multi-account key routing with ~/.ssh/config, deploy key limits, and wrong remote URLs — with ssh -v diagnostics.