Docker Hub pull-through cache
Mirror Docker Hub (and other public registries) through ECR in your own account so runners pull images over the VPC instead of the public internet — no more 429 rate limits.
CI jobs that docker pull public images eventually hit Docker Hub’s anonymous pull-rate limit (429 Too Many Requests / toomanyrequests). Because every runner in a stack shares one NAT egress IP, a busy organization trips that limit quickly.
RunsOn can mirror Docker Hub through an ECR pull-through cache ↗ in your own AWS account. The first pull of an image fetches it from Docker Hub into ECR; every later pull — across all your runners — is served from ECR over the VPC. That removes the rate limit and speeds up pulls.
Requirements
The pull-through cache is configured through the Terraform / OpenTofu module and works on Fleet and Flex-on-Terraform stacks. It is not available on the CloudFormation install path. Linux only (it is a no-op on Windows).
Two things must be in place:
- An ECR pull-through cache rule for the upstream registry, created in your AWS account.
- The
ecr-pull-throughrunner extra, so the agent logs the runner into ECR (and, for Docker Hub, configures the daemon mirror) before your job starts.
1. Create the pull-through rule
RunsOn references existing pull-through cache rules; it does not create them (creating one needs ecr:CreatePullThroughCacheRule, which the runner role intentionally does not hold). Create the regional rule once, outside the RunsOn module:
resource "aws_ecr_pull_through_cache_rule" "docker_hub" { ecr_repository_prefix = "ROOT" # special prefix that enables transparent mirroring upstream_registry_url = "registry-1.docker.io"}The ROOT prefix paired with registry-1.docker.io is what unlocks the transparent Docker Hub mirror below. Other upstreams (ECR Public, GHCR, Quay, registry.k8s.io, …) work too, but only Docker Hub is mirrored transparently — for the rest you reference images through the ECR path explicitly.
2. Reference the rule in the RunsOn stack
Pass the rule to the Fleet or Flex module via ecr_pull_through_cache_rules:
module "runs_on" { source = "runs-on/runs-on/aws//modules/fleet" # ...
ecr_pull_through_cache_rules = { docker_hub = { ecr_repository_prefix = "ROOT" upstream_registry_url = "registry-1.docker.io" upstream_repository_prefix = "" } }}This grants runners the EcrPullThroughCacheAccess IAM policy (ecr:GetAuthorizationToken, BatchImportUpstreamImage, BatchGetImage, CreateRepository, …) so ECR can lazily create the cache repository and import the upstream image on first pull. See ecr_pull_through_cache_rules in the configuration reference.
3. Enable the extra on runners
Add ecr-pull-through to the runner’s extras.
jobs: build: runs-on: runs-on=${{ github.run_id }}/runner=2cpu-linux-x64/extras=ecr-pull-through steps: - uses: actions/checkout@v6 - run: docker pull node:22 # transparently served from your ECR mirror - run: docker build . # FROM docker.io/... images are mirrored too# Fleet: bake it into the runner definitionrunners = { linux-docker = { cpu = 8 ram = 16 family = ["c8i"] image = "ubuntu24-full-x64" extras = ["s3-cache", "ecr-pull-through"] }}For Docker Hub, no image references change. The agent writes a registry-mirrors entry into /etc/docker/daemon.json pointing at your ECR registry, so a plain docker pull node:22 or a FROM node:22 in a Dockerfile is served from the mirror automatically.
Environment variables
When the extra is active, two variables are available to your job:
RUNS_ON_ECR_PULL_THROUGH_CACHE— the ECR registry host (e.g.123456789012.dkr.ecr.us-east-1.amazonaws.com). Use it to build an explicit mirror path for non–Docker Hub upstreams:${RUNS_ON_ECR_PULL_THROUGH_CACHE}/<ecr_repository_prefix>/<image>.RUNS_ON_ECR_PULL_THROUGH_CACHE_DOCKER_HUB_MIRROR— set totrueonly when the transparent Docker Hub mirror is configured, so a script can detect it.
Limitations
- Anonymous upstream pulls. RunsOn authenticates the runner to your ECR, not to the upstream registry. The transparent mirror already removes Docker Hub’s anonymous limit because pulls come from ECR; to pull private upstream images or get authenticated Docker Hub limits, attach a credential to the pull-through rule itself (
CredentialArn) when you create it. - Docker daemon only. Transparency is implemented through the Docker daemon’s
registry-mirrors, so it applies todocker pulland Buildx builds using the default Docker driver. A non-Docker buildx driver or a containerd-only setup will not inherit the mirror. - No automatic expiry. Cached upstream images accumulate in the ECR cache repositories. Add your own ECR lifecycle policy ↗ if you want them pruned.
- Private networking. If runners launch in private subnets, they need a route to ECR (ECR + S3 endpoints, via NAT or interface VPC endpoints) the same as any other ECR access.