Skip to content
DevOps2026-09-086 min read

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

ErrorMeaningGuide
pull access deniedrepo missing or no permissionthis post
manifest ... not foundrepo exists, tag is wrongmanifest unknown
toomanyrequestsrate limitedlog in or wait
i/o timeout / EOFnetwork unreachablemirror 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:

  1. Compare docker pull with and without the proxy env vars (HTTP_PROXY / HTTPS_PROXY)
  2. Confirm the mirror allows anonymous pulls for the namespace you need
  3. 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

  1. Full image path check: official = no prefix, personal = with username, private = with hostname
  2. Does the tag exist? Verify on the Hub Tags page — never assume latest
  3. docker login against the right registry? See ~/.docker/config.json
  4. Is the error access denied or rate limit? The latter means login/wait
  5. The docker pull hello-world fork test: network or permission?
  6. 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.