The 'toomanyrequests: Rate exceeded for anonymous users' error occurs when Docker Hub throttles image pulls from unauthenticated users. Anonymous users are limited to 100 pulls per 6 hours per IP address. Authenticate with Docker Hub or use a registry mirror to resolve this issue.
This error indicates that your IP address has exceeded Docker Hub's rate limit for anonymous (unauthenticated) image pulls. Docker Hub implemented pull rate limits in November 2020 to manage infrastructure costs and ensure fair usage. When you pull images without authenticating to Docker Hub, all requests from your IP address are counted against a shared anonymous quota. This becomes problematic in scenarios where multiple users, CI/CD pipelines, or Kubernetes clusters share the same public IP address (such as behind a corporate NAT or using a cloud provider). **Rate Limits by User Type:** | User Type | Pull Rate Limit | |-----------|-----------------| | Anonymous (unauthenticated) | 100 pulls per 6 hours per IP | | Authenticated (free Docker account) | 200 pulls per 6 hours per user | | Docker Pro, Team, Business | Unlimited pulls | Every `docker pull` command counts against this limit, including pulls that verify an image is up-to-date (though version checks don't count). Multi-architecture image pulls count as one pull per architecture.
The simplest fix is to authenticate with Docker Hub, which doubles your rate limit from 100 to 200 pulls per 6 hours.
Create a Docker Hub account:
1. Go to [hub.docker.com](https://hub.docker.com) and sign up for a free account
2. Create a Personal Access Token (PAT) under Account Settings > Security
Login to Docker Hub:
# Interactive login
docker login
# Using environment variables (for CI/CD)
echo "$DOCKER_HUB_TOKEN" | docker login -u "$DOCKER_HUB_USERNAME" --password-stdinVerify you're authenticated:
# Check remaining pulls (authenticated users only)
TOKEN=$(curl --silent "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | jq -r .token)
curl --head -H "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest 2>&1 | grep -i ratelimit
# Look for: ratelimit-remaining: 199;w=21600Ensure your CI/CD system authenticates before pulling images.
GitHub Actions:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
run: docker build -t myimage .GitLab CI:
docker-build:
image: docker:latest
services:
- docker:dind
before_script:
- echo "$DOCKER_HUB_TOKEN" | docker login -u "$DOCKER_HUB_USERNAME" --password-stdin
script:
- docker build -t myimage .Jenkins (Declarative Pipeline):
pipeline {
agent any
environment {
DOCKER_CREDENTIALS = credentials('dockerhub-creds')
}
stages {
stage('Login') {
steps {
sh 'echo $DOCKER_CREDENTIALS_PSW | docker login -u $DOCKER_CREDENTIALS_USR --password-stdin'
}
}
}
}For Kubernetes clusters, create a secret and reference it in your deployments.
Create the secret:
kubectl create secret docker-registry dockerhub-secret \
--docker-server=https://index.docker.io/v1/ \
--docker-username=YOUR_USERNAME \
--docker-password=YOUR_TOKEN \
[email protected]Reference in your deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
imagePullSecrets:
- name: dockerhub-secret
containers:
- name: app
image: myuser/myimage:latestOr set a default for the namespace:
kubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name": "dockerhub-secret"}]}'A pull-through cache reduces Docker Hub requests by caching images locally after the first pull.
Deploy a registry mirror with Docker Compose:
# docker-compose.yml
version: "3.8"
services:
registry-mirror:
image: registry:2
ports:
- "5000:5000"
environment:
REGISTRY_PROXY_REMOTEURL: https://registry-1.docker.io
REGISTRY_STORAGE_CACHE_BLOBDESCRIPTOR: inmemory
volumes:
- registry-data:/var/lib/registry
volumes:
registry-data:Configure Docker to use the mirror:
Edit /etc/docker/daemon.json:
{
"registry-mirrors": ["http://localhost:5000"]
}Restart Docker:
sudo systemctl restart dockerNow all pulls will check your local cache first, significantly reducing Docker Hub requests.
Several cloud providers offer mirrors of popular Docker Hub images without rate limits.
Google Container Registry Mirror:
# Instead of:
docker pull nginx:latest
# Use Google's mirror:
docker pull mirror.gcr.io/library/nginx:latestAmazon ECR Public Gallery:
# Many popular images are available on ECR Public
docker pull public.ecr.aws/docker/library/nginx:latestMicrosoft Container Registry:
# For Microsoft images
docker pull mcr.microsoft.com/dotnet/aspnet:8.0Quay.io:
docker pull quay.io/prometheus/prometheus:latestYou can also copy frequently-used images to your own private registry (AWS ECR, GCR, Azure ACR) to avoid Docker Hub entirely.
For critical images, copy them to your own private registry to eliminate Docker Hub dependency.
Using docker pull/tag/push:
# Pull from Docker Hub
docker pull nginx:1.25
# Tag for your private registry
docker tag nginx:1.25 your-registry.com/nginx:1.25
# Push to your registry
docker push your-registry.com/nginx:1.25Using crane (efficient for automation):
# Install crane
go install github.com/google/go-containerregistry/cmd/crane@latest
# Copy directly without pulling locally
crane copy nginx:1.25 your-registry.com/nginx:1.25Using skopeo:
skopeo copy docker://nginx:1.25 docker://your-registry.com/nginx:1.25Then update your Dockerfiles and deployments to reference your private registry instead of Docker Hub.
Reduce unnecessary pulls by adjusting Kubernetes image pull policies.
Avoid imagePullPolicy: Always unless necessary:
spec:
containers:
- name: app
image: myimage:v1.2.3 # Use specific tags
imagePullPolicy: IfNotPresent # Only pull if not cachedUse specific image tags instead of :latest:
# Bad - always pulls to check for updates
image: nginx:latest
imagePullPolicy: Always
# Good - pulls once, then uses cache
image: nginx:1.25.3
imagePullPolicy: IfNotPresentPre-pull images to nodes:
# Use a DaemonSet to pre-pull images to all nodes
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: image-prepull
spec:
selector:
matchLabels:
name: image-prepull
template:
metadata:
labels:
name: image-prepull
spec:
initContainers:
- name: prepull
image: nginx:1.25.3
command: ["sh", "-c", "exit 0"]
containers:
- name: pause
image: gcr.io/google_containers/pause:3.2
EOFFor organizations with heavy Docker Hub usage, a paid subscription removes rate limits entirely.
Docker Hub Subscription Plans:
| Plan | Rate Limit | Price |
|------|------------|-------|
| Personal (Free) | 200 pulls/6hrs | Free |
| Pro | Unlimited | $5/month |
| Team | Unlimited | $9/user/month |
| Business | Unlimited | $24/user/month |
When to consider a subscription:
- Your team pulls more than 200 images per 6-hour period
- CI/CD pipelines frequently hit rate limits despite authentication
- Deploying to multiple Kubernetes clusters with frequent image pulls
- You need features like vulnerability scanning and image access management
Get started:
Visit [hub.docker.com/pricing](https://hub.docker.com/pricing) to compare plans.
### Check Your Current Rate Limit Status
Before your pulls fail, you can check how many pulls you have remaining:
# For anonymous users (check by IP)
TOKEN=$(curl --silent "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | jq -r .token)
curl --head -H "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest 2>&1 | grep -i ratelimitResponse headers to look for:
- ratelimit-limit: Your total limit (e.g., 100;w=21600 means 100 per 21600 seconds/6 hours)
- ratelimit-remaining: Pulls remaining in current window
### Understanding How Pulls Are Counted
Not all Docker operations count against your rate limit:
| Operation | Counts as Pull? |
|-----------|-----------------|
| docker pull (image not cached) | Yes |
| docker pull (image already cached, checking for update) | No (version check only) |
| docker build (pulling base image) | Yes |
| Kubernetes pod start (pulling image) | Yes |
| Multi-arch manifest (each architecture) | Yes (1 per arch) |
### Enterprise Pull-Through Cache with Authentication
For authenticated pulls through a cache:
# registry-config.yml
version: 0.1
proxy:
remoteurl: https://registry-1.docker.io
username: your-dockerhub-username
password: your-dockerhub-tokenRun the registry with this config:
docker run -d -p 5000:5000 \
-v $(pwd)/registry-config.yml:/etc/docker/registry/config.yml \
-v registry-data:/var/lib/registry \
registry:2### Kubernetes Solutions for Large Clusters
Spegel - Stateless Cluster Local OCI Mirror:
Spegel allows Kubernetes nodes to share images peer-to-peer, reducing external pulls.
Harbor with Proxy Cache:
Harbor can act as a proxy cache with additional features like vulnerability scanning:
helm install harbor harbor/harbor \
--set proxy.httpProxy=http://proxy:3128 \
--set trivy.enabled=true### Monitoring and Alerting
Set up monitoring to detect rate limit issues before they impact production:
# Prometheus alert rule
groups:
- name: docker-hub
rules:
- alert: DockerHubRateLimitLow
expr: docker_hub_rate_limit_remaining < 20
for: 5m
labels:
severity: warning
annotations:
summary: "Docker Hub rate limit nearly exhausted"image operating system "linux" cannot be used on this platform
How to fix 'image operating system linux cannot be used on this platform' in Docker
manifest unknown: manifest unknown
How to fix 'manifest unknown' in Docker
cannot open '/etc/passwd': Permission denied
How to fix 'cannot open: Permission denied' in Docker
Error response from daemon: failed to create the ipvlan port
How to fix 'failed to create the ipvlan port' in Docker
yaml: line X: found character that cannot start any token
How to fix 'found character that cannot start any token' in Docker Compose