skip to content

What does adding a registry host to the `insecure-registries` list in the Docker daemon's `daemon.json` actually change, and what would you do instead for a production internal registry?

level: seniorimportance: should knowfreq 34%

answer

  1. daemon.json → skip cert verify + HTTP fallback
  2. Per node, needs daemon reload
  3. Basic credential exposed on the wire
  4. Tag→digest lookup is the MITM point
  5. Fix: /etc/docker/certs.d/<host>:<port>/ca.crt

basics

~20 s

It tells the daemon to accept plain HTTP or an untrusted TLS certificate for that host, disabling verification. Credentials and images then travel unauthenticated and tamperable. In production, run the registry with real TLS and install your internal CA certificate on every node instead.

solid answer

~50 s

`insecure-registries` in `/etc/docker/daemon.json` lists hosts (or CIDRs) for which the daemon will skip certificate verification and, failing HTTPS, fall back to plain HTTP. It requires a daemon restart and must be set on **every** node that pulls. What you give up is real: the `docker login` credential is sent as HTTP Basic to the token endpoint, so on plain HTTP it is readable on the wire; image content can be substituted in transit; and there is no proof you are talking to the registry you think you are. "It's only the internal network" is not a control. The correct fix for a self-signed or internal-CA registry is to trust the CA rather than disable verification: drop the CA certificate at `/etc/docker/certs.d/<registry-host>:<port>/ca.crt` (per-host, no daemon restart), or add it to the system trust store. Better still, issue a real certificate from your internal PKI or a public CA. Also worth flagging: this setting configures **Docker's daemon only**. Other container runtimes have their own registry configuration and are unaffected.

code

json · 3 lines
json
{
  "insecure-registries": ["registry.internal:5000"]
}

go deeper

for a junior

Know it disables TLS verification and permits plain HTTP for the listed registry, and that it is not a fix for permission errors.

for a middle

Explain the concrete exposures — Basic credentials on the wire, tag-to-digest substitution — and that the setting is per node and needs a daemon reload.

for a senior

Push the correct remedy: install the internal CA at /etc/docker/certs.d or in the system trust store, and reserve the exception for local/air-gapped cases.

for a principal

Treat it as a fleet-wide trust decision: internal PKI ownership, certificate lifecycle, and the fact that a per-node exception is an unauditable configuration liability.

## What the setting does ```json { "insecure-registries": ["registry.internal:5000", "10.4.0.0/16"] } ``` For any registry in this list the Docker daemon will: 1. Attempt HTTPS **without verifying** the server certificate — expired, self-signed, wrong-hostname all pass. 2. If HTTPS is unavailable, **fall back to plain HTTP**. Entries are matched by host and port, or by CIDR for IP-addressed registries. The file lives at `/etc/docker/daemon.json` (Linux) and changes take effect only after `systemctl reload docker` or a daemon restart. `docker info` prints the effective list, which is the quickest way to confirm what a node actually believes. Crucially, this is **per node**. A cluster of fifty machines needs the setting on all fifty, applied by configuration management — which is one practical reason it ages badly. ## What you actually lose TLS gives three things: confidentiality, integrity, and server authentication. Turning verification off surrenders all three for that host. - **Credential exposure.** Registry auth sends the username and secret as HTTP Basic to the token endpoint. Over plain HTTP that is a plaintext credential on the wire; with unverified TLS, any machine that can intercept the connection presents its own certificate and reads it. - **Image substitution.** The content-addressable design protects you *after* you know the digest you want, but a `pull` by tag asks the server which digest the tag points to. A man-in-the-middle answers with its own manifest, and the client happily verifies the layers against *that* manifest's digests. The chain of trust starts at the connection, and you just removed it. - **No server authentication.** DNS spoofing, ARP poisoning, a rogue host on the VLAN, or a typo'd hostname that someone else registered are all sufficient. "It's internal" assumes the internal network is trusted, which is precisely the assumption modern network design abandons. A secondary annoyance: the setting is invisible in your image references, so a pull that works on a configured node fails on a fresh one with a certificate error, and the failure looks like a registry outage. ## What to do instead **Trust the CA, don't disable verification.** If the registry uses a certificate from an internal CA or a self-signed one, install the issuing certificate rather than switching checking off: - Per-registry, Docker-specific: place the PEM at `/etc/docker/certs.d/<host>:<port>/ca.crt`. Docker reads this directory per pull, so no daemon restart is needed. The directory name must match the registry reference exactly, port included. - System-wide: add the CA to the OS trust store (`/usr/local/share/ca-certificates` + `update-ca-certificates` on Debian-family, `/etc/pki/ca-trust/source/anchors` + `update-ca-trust` on RHEL-family). This also fixes every other tool on the host. The same directory can hold a client certificate and key (`client.cert` / `client.key`) when the registry uses mutual TLS. **Give the registry a real certificate.** Internal PKI, an ACME-issued certificate from a public CA for a resolvable name, or a load balancer terminating TLS with a managed certificate. This is usually less work than maintaining an exception on every node forever. **Bound the blast radius when you cannot avoid it.** Legitimate temporary uses exist: a `registry:2` container on a laptop for local experiments, an air-gapped lab, a throwaway CI fixture. Keep the exception to `localhost`/`127.0.0.1` (which Docker already treats as insecure-capable) or a single lab host, never a CIDR covering a production network, and never combined with real credentials you also use elsewhere. ## Things candidates commonly get wrong - **It is not an authentication switch.** It does not bypass `docker login`; an insecure registry can still require credentials, and a secure one can be anonymous. Access-denied errors are never fixed by this setting. - **It is not enough on its own for HTTP.** Some clients and mirrors need matching configuration too, and a registry served over HTTP behind an HTTPS-only proxy will still fail. - **It configures the Docker daemon only.** Other runtimes read their own configuration files, and orchestrators that use a different runtime will not honour `daemon.json` at all. Migrating a node from Docker to another runtime silently loses the exception. - **Restart required.** Editing the file without reloading the daemon produces confusing "I already fixed that" reports.

  • Images are content-addressable by digest — doesn't that protect a pull even over plain HTTP?
    Only partially. Digest verification proves the layers match the manifest you received, but a pull by tag first asks the server to resolve the tag to a digest. An attacker in the middle returns their own manifest and matching layers, and every digest check passes. The integrity guarantee is anchored in trusting the connection or in signature verification, neither of which you have over unverified transport.
  • You add the registry to `insecure-registries` and pulls still fail on some nodes. What are the likely causes?
    Either the daemon was never reloaded on those nodes, or the entry does not match the reference exactly — host and port must match, and an IP-addressed registry needs the right CIDR. It is also possible those nodes do not use the Docker daemon at all: another container runtime reads its own registry configuration and ignores `daemon.json` entirely.

saying these in an interview costs you the question

  • Treating it as a fix for "pull access denied" authorization errors
  • Claiming plaintext transport is fine because the network is internal
  • Believing digest verification alone makes an unverified connection safe
  • Editing daemon.json and expecting it to apply without reloading the daemon
  • Assuming the setting applies to every container runtime on the host

context