The blocking question was where a container image comes from, and the version that blocked assumed the machine might have no network. That assumption came from the LAB: a scenario is a closed address space by design, which is what lets two scenarios hold the same addresses without meeting. Production is not sealed — a machine being adopted has a network, and one that does not is a machine where very little works anyway. So substrate.lock carries references, not payload: an image name and a digest, fetched at apply time. A first node pulls from upstream because no mesh registry exists yet; every node after that pulls from the mesh's own. The lab is the exception and places images itself, the way it already places the host binary — a property of a test environment, and letting it dictate the production design would be the tail wagging the dog. Pinned by DIGEST rather than tag. Reproducibility comes from pinning the identity of a thing, not from carrying its bytes, which is what makes fetching acceptable rather than a compromise. ADR 0041 survives untouched, which was the point. "Copy it onto a machine and run it" stays literally true — one binary, a few megabytes, which then fetches what it was told to. Carrying images would have quietly redefined the property that decision rests on. Costs accepted and named: an apply can now fail because something is unreachable, which a self-contained artifact could not, so it must fail legibly — naming what it could not fetch and from where. And the lab needs a way to place images into a machine that also has no container runtime, both of which are lab-installation concerns and neither solved here. Research 012's build-time-versus-apply-time reframing narrows accordingly: it still holds for what a tailored installer contains, and no longer has to hold for images.
3.8 KiB
status, date, deciders, reconstructed, extends
| status | date | deciders | reconstructed | extends |
|---|---|---|---|---|
| accepted | 2026-08-26 | jochen | false | 0038-a-node-joins-by-linking-first.md |
46. The installer fetches what it pins
Context
Stage 2 of the node host needs to raise the substrate, and a substrate service is a container, and a container needs an image. Where the image comes from had been blocking implementation.
The blocking version of the question assumed the machine might have no network, which produced a bad trilemma: carry every image inside the artifact, fetch at apply time, or have something else place them first. Carrying them makes a three-megabyte binary into a multi-hundred-megabyte one and strains ADR 0041.
The assumption was wrong, and it came from the lab. A scenario is a closed address space by design — that is what lets two scenarios hold the same addresses without meeting. Production is not: a machine being adopted has a network, and one that does not is a machine where very little works anyway.
Decision
The installer fetches what the bundle pins.
substrate.lock carries references, not payload — an image name and a digest. At apply time
the host fetches them.
| Situation | Fetched from |
|---|---|
| a first node, no mesh yet | upstream, wherever the image ordinarily lives |
| every node after that | the mesh's own registry |
| the lab | nowhere — the lab places them first |
Pinned by digest, not by tag. A tag moves; a digest does not. Reproducibility comes from pinning the identity of the thing, not from carrying its bytes — which is what makes fetching acceptable rather than a compromise.
The lab is the exception, and it is the lab's problem. A sealed scenario cannot reach a registry, so the lab places images into a machine the same way it already places the host binary. That is a property of a test environment, and letting it dictate the production design would be the tail wagging the dog.
Consequences
- ADR 0041 survives untouched. Copy it onto a machine and run it remains literally true: one binary, a few megabytes, which then fetches what it was told to fetch. The alternative would have quietly redefined the property that decision rests on.
- The bundle stays small and reviewable. A list of pinned references is something a person can read and check. A bundle containing images is not.
- An apply can fail because something is unreachable, which a self-contained artifact could not. That is the cost, it is accepted, and it must fail legibly — naming what it could not fetch and from where, not "install failed".
- The lab needs a way to place images, and the machine it places them into needs a container runtime, which a sealed scenario cannot install either. Both are lab-installation concerns and neither is solved here.
- The build-time-versus-apply-time reframing in research 012 narrows. It still holds for what a tailored installer contains — the missing pieces for a given machine — but it does not have to hold for images, because fetching them is available and cheap. Recorded because the reframing was general and is now qualified.
- Nothing here says what happens when a fetch is impossible on a real node. An air-gapped machine is not a case the mesh has, and if one appears this decision is what it revisits.
References
- ADR 0041 — the property this preserves.
- ADR 0038 — the bundle this fills in.
07-the-substrate.md— what the bundle pins.- Research 012 — the reframing this qualifies.