From 2 November 2020 Docker Hub limits how many image pulls an account or address can make in a six-hour window. Builds that quietly pulled node or nginx hundreds of times a day can now fail with toomanyrequests: You have reached your pull rate limit. This post explains the rules and the fixes that work in practice.
The limits announced by Docker.
| Who pulls | Limit per 6 hours |
|---|---|
| Anonymous (not logged in) | 100 pulls, counted per source IP address |
| Free Docker account (logged in) | 200 pulls, counted per account |
| Pro or Team subscription | No rate limit |
A “pull” is a request for an image manifest. Requests that only check whether an image has changed (HEAD requests) do not count, and layer downloads do not count separately. Docker has also said that official-image publishers and verified open-source programme images can be exempt, so check the current Docker documentation for your case.
Where it hurts
flowchart TB
subgraph CI[CI runners behind one NAT IP]
J1[Job 1] --> NAT
J2[Job 2] --> NAT
J3[Job N] --> NAT
end
subgraph K8S[Kubernetes nodes]
N1[Node 1 pulls] --> NAT
N2[Node 2 pulls] --> NAT
end
NAT[One public IP] -->|anonymous pulls count against 100| HUB[Docker Hub]
HUB -->|429 toomanyrequests| FAIL[Failed build or ImagePullBackOff]
Shared outbound addresses (a corporate NAT, a cloud NAT gateway, hosted CI runners) pool every anonymous pull together, so the 100-pull allowance disappears fast. Kubernetes with imagePullPolicy: Always on many pods or nodes is another heavy user.
Check your remaining pulls
TOKEN=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | jq -r .token)
curl -s --head -H "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest | grep -i ratelimit
# ratelimit-limit: 100;w=21600
# ratelimit-remaining: 97;w=21600
The value after w= is the window in seconds (21600 = 6 hours). For an authenticated check, pass your credentials to the token request with -u user:password.
Fixes, from least to most effort
-
Log in everywhere.
docker loginon build servers and nodes moves you from 100 per IP to 200 per account. Use a dedicated service account (not a person) and store its token in the CI secret store. In Kubernetes, create animagePullSecret:kubectl create secret docker-registry dockerhub --docker-server=https://index.docker.io/v1/ \ --docker-username=ci-bot --docker-password="$DH_TOKEN" --docker-email=ops@example.com kubectl patch serviceaccount default -p '{"imagePullSecrets":[{"name":"dockerhub"}]}' -
Pay for what you rely on. A Pro or Team plan removes the limit for the account used for pulls, which is often cheaper than engineering time.
-
Stop pulling what you already have. Cache layers in CI, use
imagePullPolicy: IfNotPresentwith pinned tags, and avoiddocker pullat the start of every job. -
Mirror the images you need into a registry you control. Copy base images to Amazon ECR, GitHub Container Registry, GitLab Registry or Harbor, and point Dockerfiles and manifests at the copy:
docker pull nginx:1.19.4 docker tag nginx:1.19.4 111111111111.dkr.ecr.ap-south-1.amazonaws.com/mirror/nginx:1.19.4 aws ecr get-login-password --region ap-south-1 | docker login --username AWS --password-stdin 111111111111.dkr.ecr.ap-south-1.amazonaws.com docker push 111111111111.dkr.ecr.ap-south-1.amazonaws.com/mirror/nginx:1.19.4 -
Run a pull-through cache (the open-source
registry:2image supports it, as does Harbor):# config.yml of the registry proxy: remoteurl: https://registry-1.docker.io username: ci-bot password: <token>Then set
"registry-mirrors": ["https://mirror.internal:5000"]in/etc/docker/daemon.jsonon each Docker host. Mirrors only help for images from Docker Hub’s default namespace.
Monitor and prevent surprises
- Alert on
toomanyrequestsin CI logs and onImagePullBackOffevents (kubectl get events -A | grep -i pull). - Keep a list of base images used across projects (
grep -r "^FROM" .) and mirror them centrally. - Pin tags and rebuild on a schedule, instead of pulling
lateston every run. - Treat the mirror like any other dependency: scan it, keep it patched, and document who owns it.
Also note the other change announced alongside these limits: Docker plans to expire images that have been inactive for six months on free accounts, so do not rely on a Hub-hosted image that nobody pulls. Check the dates on Docker’s announcement for the final schedule.