Explain the Docker daemon's `registry-mirrors` setting in `/etc/docker/daemon.json`: what a pull-through cache registry does, which pulls it actually affects, and how you would stand one up.
answer
- registry:2 with proxy.remoteurl
- daemon.json registry-mirrors = docker.io ONLY
- one upstream per proxy, pushes refused
- give the proxy upstream credentials
- containerd uses hosts.toml, mirrors any registry
basics
~20 sA pull-through cache is a registry running in proxy mode: on a miss it fetches from upstream, stores the layers, and serves later requests locally. The daemon's registry-mirrors list only redirects Docker Hub (docker.io) pulls; other registries are unaffected and need per-registry configuration.
solid answer
~50 s`registry-mirrors` tells the Docker daemon to try a mirror host first for **Docker Hub** pulls. The mirror is normally the `registry:2` image run with `proxy.remoteurl=https://registry-1.docker.io` — a **pull-through cache**: on a miss it fetches the manifest and blobs from upstream, stores them, and serves subsequent requests from local disk. The important limitations: - It applies **only to docker.io**. Pulls from GHCR, ECR, Quay or a private registry ignore it entirely; those need their own mirror/proxy configuration. - A `registry:2` instance in proxy mode has **one** upstream and rejects pushes. - Give the proxy **upstream credentials** so its own pulls are authenticated against a paid account, otherwise the cache itself gets rate-limited. - On a cache miss the mirror still does a real upstream pull, so quota is reduced roughly to one pull per image per cache, not zero. Containerd-based hosts do not read `daemon.json`; they use `hosts.toml` mirror config instead.
code
json · 3 lines{
"registry-mirrors": ["https://mirror.internal:5000"]
}go deeper
Know that a mirror is a local cache of images and that it is configured in daemon.json and shown by docker info.
Explain proxy mode, the docker.io-only scope, single upstream, no pushes, and why the proxy needs upstream credentials.
Operate it: TLS trust, disk sizing and retention, hit-ratio metrics, fallback behaviour on mirror failure, and the containerd hosts.toml equivalent for cluster nodes.
Decide the fleet-wide topology — bundled proxy versus Harbor/Artifactory/ECR pull-through, one cache per region versus central, and how it fits an internal-registry-of-record policy.
## The problem it solves A fleet of build agents or cluster nodes pulling the same handful of base images repeatedly is wasteful in three ways: upstream rate limit consumption, egress bandwidth, and latency. A **pull-through cache** (also called a mirror) sits inside your network, serves cached content locally, and only reaches upstream on a miss. ## What `registry-mirrors` does — and does not do Adding `"registry-mirrors": ["https://mirror.internal:5000"]` to `/etc/docker/daemon.json` and restarting dockerd makes the daemon attempt the mirror **before** `registry-1.docker.io` for images whose reference resolves to Docker Hub. That is the whole scope: it is a **Docker-Hub-only** mechanism. `docker pull ghcr.io/org/app` is untouched, because the reference names its registry explicitly. Teams are regularly surprised that half their pulls still hit the internet after configuring a mirror. If the mirror is unreachable or returns an error, the daemon falls back to upstream — good for availability, but it means a silently broken mirror looks like 'the mirror isn't working' only in your rate-limit bill. ## Standing one up The reference implementation is the `registry:2` distribution image in **proxy mode**. Set `REGISTRY_PROXY_REMOTEURL` (plus `REGISTRY_PROXY_USERNAME`/`REGISTRY_PROXY_PASSWORD`) and it becomes read-only proxy for exactly one upstream. Two properties matter operationally: - **One upstream per instance.** You cannot proxy Hub and Quay from the same container; run one instance per upstream, or use a product that models multiple remotes. - **Pushes are refused.** A proxy cache is not your private registry; keep the two separate. Giving the proxy authenticated upstream credentials is the step people skip. Without them the cache pulls anonymously and, being a single IP doing all the fleet's misses, it is the *first* thing to hit the anonymous per-IP limit. ## TLS and the trust chain Clients talk to the mirror over HTTPS. Either terminate TLS with a certificate the daemons trust, or (lab only) mark it insecure via `insecure-registries`. Note the mirror serves content addressed by digest, so a compromised or misconfigured mirror cannot silently substitute content when clients pull by digest — but it certainly can when they pull by mutable tag. ## Beyond the bundled proxy Production fleets usually use something richer: **Harbor** proxy-cache projects, **JFrog Artifactory** remote repositories, **Sonatype Nexus** proxy repos, or **AWS ECR pull-through cache rules**. These add multi-upstream support, retention/quotas, auth integration, vulnerability scanning and, importantly, metrics so you can see hit ratio. ## Runtime differences you must not conflate Kubernetes nodes overwhelmingly run **containerd**, not dockerd. Containerd ignores `daemon.json`; mirrors are configured per-registry host in `/etc/containerd/certs.d/<host>/hosts.toml`, which — unlike `registry-mirrors` — *can* mirror any registry, not just Hub. Mentioning this distinction reliably reads as production experience. ## Verifying it works After configuring, `docker info` lists `Registry Mirrors`. Then pull a fresh image on two hosts and watch the mirror's logs or metrics: the first should show an upstream fetch, the second a local hit. Measuring hit ratio is what turns 'we have a mirror' into 'the mirror is saving us N pulls a day'.
- We configured a mirror but pulls from ghcr.io still go straight to the internet. Why?Because `registry-mirrors` in `daemon.json` is scoped to Docker Hub only — any reference that names its own registry host bypasses it. To cache other registries you either run a proxy per upstream and rewrite image references to point at it, or move mirror configuration to containerd's `certs.d/<host>/hosts.toml`, which supports per-registry mirrors.
- Does a mirror eliminate Docker Hub rate limiting entirely?No. Cache misses are still real upstream pulls, so you go from one pull per agent to roughly one pull per image per cache — a large reduction, not zero. And because all misses now come from one IP, the mirror must authenticate with a paid account or it will hit the anonymous per-IP limit on its own.
A pull-through cache is a branch library: the first person to ask for a book makes it order a copy from the central library, everyone after that borrows the local copy — but it only has an account with one central library.
saying these in an interview costs you the question
- Believing `registry-mirrors` mirrors every registry rather than only docker.io.
- Thinking you can push your own images into a `registry:2` instance running in proxy mode.
- Running the proxy anonymously and being surprised when the cache itself gets rate-limited.
- Assuming the same `daemon.json` setting configures Kubernetes nodes, which usually run containerd.
- Claiming a mirror makes upstream outages irrelevant, ignoring cold-cache misses.