Happy Dussehra! May good always win. Happy Diwali! Wishing you a bright year ahead. Merry Christmas! Warm wishes from Vizag. Happy New Year! Here’s to a great year ahead.

Archive · Docker

Docker Hub Pull Limits Start Today

Docker Hub's pull rate limits take effect on 2 November 2020. How the limits work, how to check your remaining pulls, and practical fixes for CI, servers and clusters.

3 min read

  • Docker Hub
  • Docker Engine 19.03.13
  • registry-mirrors
  • Amazon ECR

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

  1. Log in everywhere. docker login on 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 an imagePullSecret:

    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"}]}'
  2. 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.

  3. Stop pulling what you already have. Cache layers in CI, use imagePullPolicy: IfNotPresent with pinned tags, and avoid docker pull at the start of every job.

  4. 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
  5. Run a pull-through cache (the open-source registry:2 image 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.json on each Docker host. Mirrors only help for images from Docker Hub’s default namespace.

Monitor and prevent surprises

  • Alert on toomanyrequests in CI logs and on ImagePullBackOff events (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 latest on 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.