skip to content

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.

level: middleimportance: should knowfreq 42%

answer

  1. registry:2 with proxy.remoteurl
  2. daemon.json registry-mirrors = docker.io ONLY
  3. one upstream per proxy, pushes refused
  4. give the proxy upstream credentials
  5. containerd uses hosts.toml, mirrors any registry

basics

~20 s

A 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
json
{
  "registry-mirrors": ["https://mirror.internal:5000"]
}

go deeper

for a junior

Know that a mirror is a local cache of images and that it is configured in daemon.json and shown by docker info.

for a middle

Explain proxy mode, the docker.io-only scope, single upstream, no pushes, and why the proxy needs upstream credentials.

for a senior

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.

for a principal

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.

context