The anonymous registry trap
It is the default almost no one thinks about: a container image starts from a base pulled anonymously off a large public registry. It works on your laptop, so it looks fine. Then the build moves to shared infrastructure — a fleet of continuous-integration runners sharing a pool of addresses — and the anonymous pulls start hitting rate limits that a single developer never would. Deploys fail, not because anything in the code changed, but because too many neighbours drew from the same well at once. We watched exactly this break real deploys before we fixed it.
There is a quieter problem underneath the loud one. An anonymously pulled tag is not a fixed thing; the same tag can point at different bytes over time, and you are trusting a supply chain you neither control nor inspect. A build whose inputs can change out from under it is not reproducible, and a dependency you fetch blind is an attack surface you did not choose to have.
A registry you control
So we mirror. Every external base image is copied once into a registry we control, and every build pulls from that mirror rather than from the open internet. Official library images come through a stable public mirror; anything else we bring in is copied deliberately into our own namespace before a single Dockerfile is allowed to reference it. Adding a new external image is a conscious step — mirror it, then point at the mirror — not something that happens implicitly at build time.
The payoff is threefold. Rate limits stop mattering, because we are pulling from our own store on our own terms. The supply chain narrows to images we have chosen and can inspect. And the inputs stop moving, which is the precondition for everything in the next section. A guard in the build catches any reference that tries to reach past the mirror, so the rule holds even when someone forgets it.
Determinism as a habit
Reproducibility is not one trick; it is a habit made of small refusals. Refuse to pull from anywhere uncontrolled. Refuse to reference a moving tag when a pinned one will do. Refuse to let build caches drift into the artifact. Do these consistently and a build becomes a function — same inputs, same output — which is what lets you trust that the thing you tested is the thing you shipped.
This sits alongside the way we treat infrastructure generally; the companion piece on infrastructure you can read and reproduce is the same instinct applied one layer up. When we build and deploy for a client, this is the baseline, not an upgrade — the API and services we hand over build the same way on your machine as on ours.