A registry rate limit hit every weekday at nine, and three options of which the cheapest was not the one we tried first.
# a pull-through cache, which is one container
services:
registry-cache:
image: registry:2
environment:
REGISTRY_PROXY_REMOTEURL: https://registry-1.docker.io
volumes: ['cache:/var/lib/registry']
# and on the runners
# /etc/docker/daemon.json
{ "registry-mirrors": ["http://registry-cache.internal:5000"] }
A mirror is transparent — no image references change, no pipeline edits — which is what makes it the right first answer rather than the third. It also becomes a dependency on the critical path for every build, so it needs a health check and a documented fallback to the upstream registry, and the fallback is one line in the daemon configuration that somebody has to know exists.