ECR Pull Through Cache for Docker Hub, GHCR and Quay: Setup, Image URIs, Permissions, Terraform, EKS, Cost and Every Error (Fix "toomanyrequests")
ECR pull through cache lets you pull public images such as nginx from Docker Hub, GitHub Container Registry, Quay or the Kubernetes registry through your own private Amazon ECR registry. You create a pull through cache rule with a repository prefix (for example docker-hub), then pull ACCOUNT.dkr.ecr.REGION.amazonaws.com/docker-hub/library/nginx:latest; on the first pull ECR creates the repository, fetches the image from upstream and keeps a copy, and later pulls come from your account. ECR checks upstream for a newer version of that tag at most once every 24 hours. Docker Hub, GitHub, Azure, GitLab and Chainguard need credentials stored in Secrets Manager under a name starting with ecr-pullthroughcache/; ECR Public, Quay and the Kubernetes registry do not. The identity that pulls needs ecr:BatchImportUpstreamImage and, for the first pull, ecr:CreateRepository. The feature itself is free: you pay normal ECR storage and data transfer. It is the standard fix for Docker Hub's "toomanyrequests" rate limit in CI and EKS. Below: setup in the console, CLI and Terraform, the image URI for each registry, permissions, EKS and ECS, cross-account ECR, private subnets, costs and every error.
The shop's deployment pipeline failed three times one Monday with the same message from Docker Hub: "toomanyrequests: You have reached your pull rate limit." Nothing in the code had changed. The build servers, the EKS nodes and a test cluster were all pulling the same public base images from Docker Hub, all from AWS addresses, and Docker Hub had had enough. Ethan's fix took twenty minutes and one rule in ECR. The images now come from the shop's own registry, and Docker Hub only hears from them about once a day per image.
New to ECR or Secrets Manager? The cards below cover both in plain English, plus the image pull errors this feature often fixes.
How ECR pull through cache works
Amazon ECR is AWS's container registry, the shelf where your images live (our plain-English ECR guide covers the basics). A pull through cache rule turns part of that shelf into a mirror of another registry:
- You create a rule that maps a prefix in your registry, say
docker-hub, to an upstream registry, say Docker Hub. - Something pulls
ACCOUNT.dkr.ecr.REGION.amazonaws.com/docker-hub/library/nginx:1.27. - The first time, ECR creates a repository called
docker-hub/library/nginx, fetches that image from Docker Hub (using your stored credentials if the upstream needs them), stores it, and returns it. - Later pulls of the same tag come straight from your registry. If more than 24 hours have passed since ECR last checked, it asks upstream whether the tag now points at a newer image and updates the cache if so.
- If upstream is unreachable or refuses, ECR still serves the last cached copy, so an outage at Docker Hub does not stop your deployments.
Cached repositories are ordinary ECR repositories: image scanning, replication and lifecycle policies all work on them. Multi-architecture images are cached in full (the manifest list and each architecture); pull by an architecture-specific tag or digest if you only want one.
Jake: "So it's a local photocopy of the public images?"
Ethan: "A photocopy that checks the original once a day. And when the library is closed, we still have our copy."
Set up pull through cache for Docker Hub, step by step
1. Create a Docker Hub access token. In Docker Hub, go to your account settings and create a personal access token with read-only access. Even a free Docker Hub account gives authenticated pulls a higher limit than anonymous ones.
2. Store it in Secrets Manager, in the same account and Region as the rule, with a name that starts with ecr-pullthroughcache/:
aws secretsmanager create-secret \
--name ecr-pullthroughcache/docker-hub \
--secret-string '{"username":"shopbuilds","accessToken":"dckr_pat_EXAMPLE"}' \
--region us-east-1
The console's pull through cache page can create this secret for you with the right name and format. Our Secrets Manager explainer covers what the service does with it.
3. Create the rule. In the ECR console: Private registry, Settings (or Feature & Settings), Pull through cache, Add rule, choose Docker Hub, pick the secret, and set the repository prefix. With the CLI:
aws ecr create-pull-through-cache-rule \ --ecr-repository-prefix docker-hub \ --upstream-registry-url registry-1.docker.io \ --credential-arn arn:aws:secretsmanager:us-east-1:111122223333:secret:ecr-pullthroughcache/docker-hub-AbCdEf \ --region us-east-1 # Check the rule can reach upstream with those credentials aws ecr validate-pull-through-cache-rule --ecr-repository-prefix docker-hub --region us-east-1
4. Pull through it. Log in to your ECR registry and pull using the new path. Official Docker Hub images live under library/:
aws ecr get-login-password --region us-east-1 | \ docker login --username AWS --password-stdin 111122223333.dkr.ecr.us-east-1.amazonaws.com docker pull 111122223333.dkr.ecr.us-east-1.amazonaws.com/docker-hub/library/nginx:1.27 docker pull 111122223333.dkr.ecr.us-east-1.amazonaws.com/docker-hub/grafana/grafana:latest
5. Change your image references in Dockerfiles (FROM lines), Kubernetes manifests, ECS task definitions and CI files to the ECR paths. That is the step that actually stops the Docker Hub rate limit, because nothing talks to Docker Hub directly any more.
Upstream registries and their image URIs
| Upstream | Upstream URL | Credentials | Example pull (prefix in bold) |
|---|---|---|---|
| Docker Hub | registry-1.docker.io | Secrets Manager | .../docker-hub/library/nginx:1.27 |
| Amazon ECR Public | public.ecr.aws | None | .../ecr-public/amazonlinux/amazonlinux:2023 |
| Kubernetes registry | registry.k8s.io | None | .../k8s/pause:3.9 |
| Quay | quay.io | None | .../quay/prometheus/prometheus:latest |
| GitHub Container Registry | ghcr.io | Secrets Manager (GitHub token) | .../ghcr/ORG/IMAGE:TAG |
| Azure Container Registry | NAME.azurecr.io | Secrets Manager | .../azure/IMAGE:TAG |
| GitLab Container Registry (GitLab.com only) | registry.gitlab.com | Secrets Manager | .../gitlab/GROUP/PROJECT/IMAGE:TAG |
| Chainguard Registry | cgr.dev | Secrets Manager | .../chainguard/IMAGE:TAG |
| Another Amazon ECR registry | ACCOUNT.dkr.ecr.REGION.amazonaws.com | IAM role (cross-account) | .../central/REPO:TAG |
"..." stands for ACCOUNT.dkr.ecr.REGION.amazonaws.com. The prefix is your choice; the names above are just a readable convention. Everything after the prefix is the path the image has upstream, which is why Docker Hub's official images need library/ in the middle.
Permissions: what the pulling identity needs
A normal pull needs the usual ECR read permissions. A pull through a cache rule needs two more, because the first pull creates a repository and imports the image:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PullThroughCache",
"Effect": "Allow",
"Action": [
"ecr:BatchImportUpstreamImage",
"ecr:CreateRepository",
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchCheckLayerAvailability"
],
"Resource": "arn:aws:ecr:us-east-1:111122223333:repository/docker-hub/*"
},
{
"Sid": "Login",
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
}
]
}
ecr:BatchImportUpstreamImagelets the pull fetch from upstream into the cache; without it, a pull of an image not cached yet fails.ecr:CreateRepositoryis needed only when the repository does not exist yet. If you pre-create the repositories, you can leave it out.- ECR uses its own service-linked role, created automatically with the first rule, to read the Secrets Manager secret and push the cached image, so you do not grant secret access to your build or node roles.
- To give many identities these rights at once, a registry permissions policy on your private registry can allow them for the whole account or organization.
For CI systems, EKS nodes and ECS task execution roles, add these to whichever role does the pulling. AWS's read-only ECR managed policies were written for ordinary pulls, so check that the role you use includes the import action rather than assuming it.
Terraform
resource "aws_secretsmanager_secret" "dockerhub" {
name = "ecr-pullthroughcache/docker-hub"
}
resource "aws_secretsmanager_secret_version" "dockerhub" {
secret_id = aws_secretsmanager_secret.dockerhub.id
secret_string = jsonencode({ username = var.dockerhub_user, accessToken = var.dockerhub_token })
}
resource "aws_ecr_pull_through_cache_rule" "dockerhub" {
ecr_repository_prefix = "docker-hub"
upstream_registry_url = "registry-1.docker.io"
credential_arn = aws_secretsmanager_secret.dockerhub.arn
}
resource "aws_ecr_pull_through_cache_rule" "k8s" {
ecr_repository_prefix = "k8s"
upstream_registry_url = "registry.k8s.io"
}
The rule needs no explicit dependency on the secret version, but the secret must have a value before the first pull, so apply both together. Pass the Docker Hub token as a sensitive variable from your CI secrets rather than writing it into the code, and mark the variable sensitive = true so it stays out of plan output. To control the settings of repositories the cache creates (scanning, encryption with your KMS key, a lifecycle policy, tag mutability), add an aws_ecr_repository_creation_template whose prefix matches the rule's prefix.
Repository creation templates: lifecycle, scanning, encryption
Repositories created by the cache start with plain defaults: mutable tags, AES256 encryption, no repository policy, no lifecycle policy and no tags. A repository creation template with the same prefix lets you set these up front:
- A lifecycle policy, so cached images that nobody pulls any more expire instead of piling up; our guide to ECR lifecycle policies covers the rules.
- Encryption with your own KMS key, if your policies require it.
- A repository policy letting other accounts in your organization pull the cached images.
- Tags for cost allocation.
Templates apply only when a repository is created; existing cached repositories keep their settings. And leave tag immutability off for cache repositories: with immutable tags, ECR cannot update a cached tag such as latest when upstream changes it.
Using it with EKS, ECS and CI
- EKS: change image references in your manifests or Helm values to the ECR paths, and give the node role (or the pod's role) the permissions above. Many Helm charts have an
image.registryorglobal.imageRegistryvalue that switches every image at once. If pods then fail with ImagePullBackOff, our EKS ImagePullBackOff guide walks through the checks. - ECS: use the ECR path in the task definition's
imageand add the permissions to the task execution role. Errors at task start show as CannotPullContainerError; our CannotPullContainerError guide covers them. - CI (CodeBuild, GitHub Actions on AWS, Jenkins on EC2): change
FROMlines in Dockerfiles to the ECR path and log in to ECR before the build. - Lambda: not supported. Lambda container images cannot be pulled through a cache rule; push them to an ordinary ECR repository.
Moving an existing EKS cluster onto the cache, in order:
- Create the rules for every upstream your cluster uses (Docker Hub,
registry.k8s.io, Quay, ECR Public), plus a creation template with a lifecycle policy. - List the images in use:
kubectl get pods -A -o jsonpath="{..image}"shows every image reference across namespaces. - Grant the node role (or the IRSA or Pod Identity role) the import and create permissions for the cache prefixes.
- Pull one image by hand through each rule from a machine in the VPC, to prove permissions and routing before anything depends on it.
- Change image references one workload at a time (manifests, Helm values, add-on settings) and watch the rollout; keep the old reference ready to switch back.
Private subnets. If your nodes reach ECR only through interface VPC endpoints (PrivateLink) with no internet route, the first pull of each image can fail, because ECR may need a route to the internet to fetch from upstream on your behalf. Give the VPC an internet path (a NAT gateway or public subnet route) for those first pulls; later pulls from the cache do not need it. Our VPC endpoint guide explains the endpoint setup itself.
Jake: "So after the first pull, the nodes never talk to Docker Hub again?"
Ethan: "Only ECR does, once a day per tag, with our login. That's what got us under the limit."
ECR to ECR: a central registry for many accounts
Pull through cache can also use another Amazon ECR private registry as the upstream, which suits organizations with a central "golden images" account. Each workload account creates a rule pointing at the central registry and pulls through its own prefix. When the two registries are in different accounts, you create an IAM role that ECR uses to read from the upstream registry, and the upstream repositories' policies must allow it. The cached copies then live in each workload account, so a workload keeps running even if the central account's repository is changed or unavailable for a while.
The alternative for the same goal is ECR replication, which pushes every new image from the central registry to the others automatically. Replication copies everything up front; pull through cache copies only what each account actually uses, on first pull.
What pull through cache costs
- The feature: no charge.
- Storage: cached images are billed at normal ECR storage rates per GB-month. A lifecycle policy (through a creation template) keeps this small.
- Data transfer: the normal ECR rates; pulls within the same Region to AWS compute are typically free of transfer charges, and pulls through a NAT gateway add NAT processing charges.
- Secrets Manager: a small monthly charge per secret, one per authenticated upstream.
Against that, you save failed builds, Docker Hub paid plans bought only for rate limits, and NAT gateway data for repeated large pulls from the internet. For a cluster that pulls the same few base images hundreds of times a day, the cached copy is also faster: the image comes from ECR in your own Region instead of across the internet.
Jake: "So the cache costs us a few cents of storage a month?"
Ethan: "A few cents, against a broken pipeline every Monday. Easiest trade we've made."
ECR pull through cache not working: every error
| Symptom | Cause | Fix |
|---|---|---|
| "repository ... does not exist" on first pull | Prefix typed wrong, or the identity lacks ecr:CreateRepository / ecr:BatchImportUpstreamImage | Check the prefix against the rule; add both permissions |
| "not authorized to perform: ecr:BatchImportUpstreamImage" | The pulling role has only ordinary read permissions | Add the action for the prefix's repositories |
| Rule creation fails on the secret | Secret name lacks the ecr-pullthroughcache/ prefix, or is in another Region or account | Recreate the secret with the prefix, beside the rule |
| Validation fails, or pulls fail with an authentication error | Wrong username or token, or an expired token | Update the secret's value; run validate-pull-through-cache-rule |
| Docker Hub image "not found" via the cache | Missing library/ for official images | Use .../docker-hub/library/nginx |
| First pull times out in a private subnet | No internet route for the upstream fetch | Add a NAT route for first pulls |
| Cached tag never updates | Tag immutability on, or less than 24 hours since the last check | Turn immutability off; wait, or pull by digest |
| Pushed image not served | Pushing into a cache repository is not supported | Push to a normal repository; delete and re-pull to refresh |
| Lambda cannot use the image | Lambda does not support pull through cache | Push the image to an ordinary repository |
| Still hitting "toomanyrequests" | Some images still reference docker.io directly | Search manifests, charts and Dockerfiles for docker.io paths |
| "no basic auth credentials" or 401 from ECR | Not logged in to ECR, or an expired login | Run aws ecr get-login-password again (valid 12 hours) |
Two more things worth knowing: the first pull through a FIPS endpoint is not supported (later pulls are), and for upstreams without authentication, the first pull comes from AWS addresses, not yours. If an ECR login error persists, our guide to "authorization token has expired" covers ECR logins in CI.
Frequently asked questions
What is ECR pull through cache?
A feature that caches images from an upstream registry, such as Docker Hub or Quay, in your private Amazon ECR registry, fetched on first pull and refreshed at most once a day.
How do I use ECR pull through cache with Docker Hub?
Store a Docker Hub token in Secrets Manager under ecr-pullthroughcache/, create a rule with a prefix such as docker-hub, then pull ACCOUNT.dkr.ecr.REGION.amazonaws.com/docker-hub/library/IMAGE:TAG.
Does ECR pull through cache fix the Docker Hub rate limit?
Yes, once your images reference the ECR paths: only ECR contacts Docker Hub, authenticated, and only when refreshing a cached tag.
How often does ECR update cached images?
When an image is pulled, ECR checks upstream for a newer version of that tag if more than 24 hours have passed since the last check.
What permissions does pull through cache need?
The pulling identity needs ecr:BatchImportUpstreamImage and, for the first pull, ecr:CreateRepository, plus normal ECR pull permissions.
How much does ECR pull through cache cost?
The feature is free; you pay ECR storage for cached images, data transfer, and Secrets Manager for any stored credentials.
Which registries does ECR pull through cache support?
ECR Public, the Kubernetes registry, Quay, Docker Hub, GitHub Container Registry, Azure Container Registry, GitLab.com, Chainguard and other ECR registries.
What must the Secrets Manager secret be named?
Its name must start with ecr-pullthroughcache/ and it must be in the same account and Region as the rule.
Can I use ECR pull through cache with Lambda?
No. Lambda does not support pulling images through a cache rule; use an ordinary ECR repository.
Can I use pull through cache across AWS accounts?
Yes. A rule can use another ECR registry as upstream, with an IAM role when the registries are in different accounts.
How do I set up ECR pull through cache in Terraform?
Use aws_ecr_pull_through_cache_rule with ecr_repository_prefix, upstream_registry_url and, for authenticated upstreams, credential_arn.
Why does the first pull fail in a private subnet?
The first fetch from upstream may need an internet route; add a NAT path for first pulls even if you use ECR VPC endpoints.
Can I push images into a pull through cache repository?
It is not a supported pattern and can serve stale images; push to a normal repository instead.
What happens if Docker Hub is down?
ECR serves the last cached copy of the image, so pulls of cached tags keep working.
How do I control lifecycle and encryption of cached repositories?
Create a repository creation template with the same prefix, setting lifecycle policy, encryption, scanning and tags.
What is the image path for official Docker Hub images?
Add library/, for example ACCOUNT.dkr.ecr.REGION.amazonaws.com/docker-hub/library/nginx:1.27.
Is ECR pull through cache the same as ECR replication?
No. Replication copies every new image to other registries; pull through cache copies only the images each registry actually pulls.
For readers preparing for an AWS certification
We have been covering AWS certifications alongside our fixes, so here is a short extra for readers studying for one. If you only came for the fix, you already have it; feel free to skip this part.
Exams that test this topic: Developer – Associate (DVA-C03 from December 1, 2026) • CloudOps Engineer – Associate (SOA-C03) • Solutions Architect – Associate (SAA-C03).
Here are the exam-style questions this topic tends to raise, with the reasoning you need to answer them:
1. Builds and EKS nodes hit Docker Hub pull rate limits. Which ECR feature caches public images in your own registry?
ECR pull through cache rules.
2. Where must Docker Hub credentials for an ECR pull through cache rule be stored?
In AWS Secrets Manager, in a secret whose name starts with ecr-pullthroughcache/.
3. Which IAM action lets a pull import an image from the upstream registry into the cache?
ecr:BatchImportUpstreamImage (plus ecr:CreateRepository for the first pull).
4. Which compute service cannot pull container images through an ECR pull through cache rule?
AWS Lambda.
5. How can you set lifecycle policies and encryption on repositories that pull through cache creates automatically?
With a repository creation template matching the rule's prefix.
6. What is the difference between ECR replication and pull through cache?
Replication copies every new image to other registries; pull through cache copies only images that are pulled.
7. Which AWS service stores secrets such as registry credentials and rotates them?
AWS Secrets Manager.
Exam codes and dates change often. Our AWS certification changes page keeps track of what is current.
A month later, the shop's builds have not seen a single Docker Hub rate limit. The cluster's images all come from docker-hub/, k8s/ and ecr-public/ prefixes in the shop's own registry, a creation template expires cached images nobody has pulled in 90 days, and the Docker Hub token sits in Secrets Manager where only ECR reads it. Jake's verdict: "We stopped borrowing from the library every five minutes and started keeping our own copies."
📌 If you keep one line from this page
Store upstream credentials as ecr-pullthroughcache/NAME in Secrets Manager, create a pull through cache rule with a prefix, point every image at ACCOUNT.dkr.ecr.REGION.amazonaws.com/PREFIX/..., and give the pulling role ecr:BatchImportUpstreamImage and ecr:CreateRepository.
Free feature · refresh at most daily · library/ for official images · not for Lambda · first pull needs internet.
Revision note. Written October 19, 2026. A rate limit from a public registry is usually a sign you need your own copy; pull through cache makes that copy for you.