Files
hq/02-DECISIONS/0142-the-mesh-delivers-its-own-components-as-binaries.md
T
jschoubben 346e613995 ADR 0142: the mesh delivers its own components as binaries, not container images
Measured: the host is a binary somebody copied to four machines, owned by no
package and built by nothing, while the controller, catalogue, builder and vault
are container images publishing no ports at all. Same language, same project,
same kind of work, delivered two ways — and the difference is not a judgement
about either, it is that images are the only delivery that works.

What it costs: genesis must raise a container runtime before the control plane
can exist; updating the control plane goes through a registry the control plane
runs; a host change cannot be rolled out at all; and compiling the language the
mesh is written in is not a capability of the builder, so the controller is built
from a hand-written Dockerfile — the incantation the bundle toolchain exists to
abolish.

Third-party software stays a container: the store, the registry, the broker are
somebody else's build. The container runtime stays on the machine for modules.
What changes is that the control plane no longer needs it to exist.

The receiving half is already built and tested (ADR 0141). Staged: compile Go, an
artifact names its target, deliver a binary, the host first, then the rest, genesis
last.
2026-09-29 00:51:42 +02:00

9.9 KiB

topic, status, date, deciders, reconstructed, extends
topic status date deciders reconstructed extends
the mesh accepted 2026-09-29 jochen false 02-DECISIONS/0141-the-host-delivers-its-own-successor.md

142. The mesh delivers its own components as binaries, not as container images

Context

Measured on the control-node, 2026-09-29:

what how it runs publishes
host a binary on the machine —
controller, catalogue, builder, vault containers nothing
store, registry, broker containers ports

The mesh's own software is delivered two ways, and the difference is not a property of the software. The host and the controller are both written in the same language, both the mesh's own, both doing the mesh's own work. One is an image fetched from a registry. The other is a file somebody copied to four machines, owned by no package, built by nothing (issue 142).

The reason is not a judgement about either, it is that images are the only delivery that works. There is no way to put a binary on a machine. The host is hand-copied because of that, and the controller is an image because of that. Neither was chosen on its merits.

What it costs, all of it measured rather than argued:

  • Genesis must raise a container runtime before the control plane can exist. The bundle carries three images and one of them is the controller, "in the bundle for the same reason they are: there is nothing to fetch it with yet" (design 07). So the hardest moment in the mesh's life has a prerequisite that the thing being started does not need.
  • Updating the control plane depends on the control plane. Its image is fetched from the registry, which is a container the controller manages.
  • A change to the host cannot be rolled out at all. Every machine here runs a byte-identical hand-copied binary. A change merged yesterday reached none of them.
  • Compiling the language the mesh is written in is not a capability of the builder. The bundle toolchains are typescript — real, with a registered base module — and python, which is named in the list and absent from the catalogue. The controller is built as an image from a Dockerfile, which is the per-repository incantation the bundle toolchain exists to abolish (design 18).

The half that receives a binary safely is already built and tested (ADR 0141): versions side by side in directories named for them, the newest run, the running one standing aside between reconciles, retirement keeping the predecessor, and a rollback that chooses a directory. What is missing is everything that puts one there.

Considered Options

  1. Leave it as it is. Rejected: it is not a design, it is the reach of one mechanism. And it is what makes a host change undeliverable.
  2. Containerise the host too, so everything is delivered one way. Rejected: the host is what starts the container runtime and what applies containers. A host in a container is the bootstrap problem made total, and the machine would have no way back from a bad one.
  3. Deliver the mesh's components as operating-system packages. Rejected for the reason ADR 0141 rejected it for the host: a package and a trusted repository per operating system, three of each, and the package resource asserts presence and deliberately never a version.
  4. Binaries for the mesh's own components, containers for third-party software. Adopted.

Decision

The mesh's own components are delivered as binaries on the machine. The host, the controller, the catalogue, the builder, the vault — the software this project writes. They are delivered by the mechanism ADR 0141 built: an archive, fetched by digest, unpacked into a directory named for its version, with the running one standing aside between reconciles and a rollback that chooses the predecessor.

Third-party software stays a container. The store, the registry, the broker. They are somebody else's build, they are already adopted as modules (ADR 0078), and an image is the right way to carry somebody else's software. The container runtime remains required — modules use it — so this removes a dependency from the control plane, not from the machine.

The builder compiles the languages the mesh is written in. A toolchain for Go, with a base module providing the compiler, exactly as typescript has. The obligation the toolchain list warns about — an SDK carrying the broker client, the envelope and tool serving — attaches to a module written in a language, not to the language being compilable. None of these components is a module in that sense; the host is what applies modules.

An artifact says what it targets. A compiled binary is per operating system, pinned at link time (ADR 0005), and a toolchain deliberately takes nothing from the module, because anything a module could override there it would be writing a Dockerfile to override. So the target is a property of the artifact rather than of the recipe, and one artifact declared per target is one build each.

A component's version comes from where it sits, not from its linker. It is unpacked into a directory named for its version, so it can read its own version from its path. The stamp goes, and with it the need for a build to know what it will be called.

Genesis carries a binary reference where it carried an image reference. The principle does not change — the bundle names a thing by digest and the host fetches it, pinned because nothing can resolve a version when no mesh exists — and the container runtime stops being a prerequisite for the control plane. It stays a prerequisite for the store and the broker, which is where it belongs.

The order is staged, and each step stands alone. Compiling Go; an artifact naming its target; delivering a binary; the host as the first component delivered; the controller, catalogue, builder and vault out of their containers; genesis last. Genesis is last for the reason it is always last: it matters for a machine nobody has yet, and every earlier step is provable on a mesh that exists.

Consequences

  • One delivery for the mesh's own software, so a change to the host ships the way a change to the controller does, and neither is copied by hand.
  • The control plane stops depending on a container runtime and on its own registry. Both remain on the machine for other reasons; neither gates the control plane's own life any more.
  • Replaced(), the known-good record and the launcher's rollback stop being dead code. They were written for this and have been called by nothing but their tests.
  • Four more components gain a rollback they do not have. Today a bad controller image is recovered by an operator; under this it is recovered the way a bad host is.
  • Two versions of each component occupy disk. Around nine megabytes each. The predecessor is what a rollback needs.
  • Genesis gets smaller, not larger. One fewer image to carry and one fewer runtime to raise before the control plane.
  • This does not make the components smaller or simpler. They are the same programs; what changes is how they arrive. A reader expecting the containers to have been hiding complexity will not find any.
  • What got harder: the builder gains a language, artifacts gain a target, and the mesh gains a second kind of thing it must deliver correctly — one where getting it wrong takes the control plane down rather than a module. That is why the host is first: it is the component whose recovery is already built and tested.

How it is checked

  • A component is delivered and runs, with nothing copied by hand. A bed builds the host from its repository, delivers it to a machine running an older one, and the machine reports the new version. This fails today at the first step, because nothing builds it.
  • Each target is built once and only the matching one is delivered. Asserted by declaring an artifact per operating system and checking that a machine is offered the one it can run — a host built for another is what ADR 0005's link-time pin exists to refuse.
  • A component reads its version from its path, asserted by unpacking the same bytes into two differently named directories and seeing each report its own.
  • A bad component is rolled back without an operator, for the host first: a version that will not start is replaced by its predecessor once, and the second failure halts naming the machine.
  • The control plane comes up with no registry reachable, which is the dependency this removes — asserted by raising it with the registry stopped.
  • Genesis raises a control plane with no container runtime running, and raises the store and the broker afterwards. Last, and on a machine with nothing on it.
  • A published port count that does not change. The mesh's own components publish nothing today, so moving them out of containers must not open anything — asserted on the machine's reachable set before and after, which the converge preview already reads.

References

  • ADR 0141 — the receiving half, already built
  • ADR 0005 — the host, its supervision, and one binary per operating system
  • ADR 0078 — why third-party software stays a container
  • ADR 0006 — what genesis must raise, and in what order
  • issue 142 — the measurement that started this
  • design 07 — the bundle's three images, one of them the controller