ADR-0032: Publish one lockstep OCI image after the coordinated release succeeds
| Field | Value |
|---|---|
| Status | accepted |
| Date | 2026-08-26 |
| Review by | 2027-02-26 |
| Schema version | 0.1.0 |
| Reversibility | two-way-door |
| Blast radius | org |
| Scope | org |
| Tags | architecture, packaging, distribution, container, supply-chain |
| Deciders | @mbeacom |
| Authored by | agent-drafted |
| Ratified by | @mbeacom |
| Review tier | arb |
| Review reason | Adds a public distribution channel and its release, rollback, provenance, and compatibility obligations. |
| Relates to | 0006, 0007, 0010, 0014, 0016, 0017, 0018, 0028 |
| Affects | path:Containerfile, path:.dockerignore, path:.github/workflows/container-release.yml, path:.github/workflows/ci.yml, path:scripts/container-*, path:README.md, path:docs/RELEASING.md |
| Source | docs/adr/0032-publish-one-lockstep-oci-image-after-the-coordinated-release-succeeds.md |
Status: accepted. Agent-drafted and ratified by
@mbeacomthrough the explicit request to add GitHub Container Registry publication.Publication-order amendment: ADR-0036 changes clause 2’s trigger from successful Release-workflow completion to publication of the stable GitHub release, after that workflow has published npm and created the draft.
Context
Section titled “Context”The CLI and MCP server are published Node packages, while the two CI entry points are committed GitHub Action bundles. A local container can make all four executables available without installing a runtime into the consuming repository, and gives MCP clients a uniform stdio command.
Building a Containerfile is not the consequential decision. Publishing it is: the registry name, tags, architecture set, provenance, update order, and rollback behavior become a fifth distribution contract. Publishing directly on a tag push would race the existing Release workflow and could advertise a container version whose coordinated npm publication failed.
Dedicated images for every executable would narrow SBOMs, but would also create five registry packages with separate visibility, retention, provenance, and rollback operations. The behavior is already selectable inside one image.
Decision
Section titled “Decision”Publish one all-in-one OCI image at ghcr.io/mbeacom/adrkit as part of the
lockstep release, only after the coordinated Release workflow succeeds.
- The image version is the lockstep package version. It does not have an independent manifest or release tag.
- A successful stable
vX.Y.ZRelease workflow creates a draft after npm publication. Publishing that stable GitHub release triggers container publication. Adapter releases and failed Release runs publish no container. - Registry tags are immutable
vX.Y.Z, movingvX, andlatest. Promotions are serialized. A release movesvXonly when it is newest in that major and moveslatestonly when it is newest overall; historical recovery cannot roll either tag backward. Automation should pin the immutable tag or digest. - The published artifact is the all-in-one
adrkittarget.cli,mcp,ci, andqueue-actionremain isolated local build targets and CI subjects, not separate registry packages. - Bun 1.3.14 installs and bundles the CLI and MCP sources for Node. The final image runs Node 24 as a non-root user and contains the committed Action bundles. Both base image indexes are pinned by digest.
- The image is published for
linux/amd64andlinux/arm64. Buildx pushes the content-addressed digest first, GitHub attests it, and only then are the public release tags promoted to that digest. - MCP examples and CI run it with no network, a read-only root filesystem, and a read-only repository mount. Writing CLI commands opt into a writable mount.
- A manual recovery dispatch accepts only an existing stable GitHub release
with a successful
Releaseworkflow for the exact tag/SHA, whose tag commit is onmain, and whose version matches the repository. It refuses to change an existing immutable tag to a different digest.
Options considered
Section titled “Options considered”Option A: One lockstep all-in-one image after Release (chosen)
Section titled “Option A: One lockstep all-in-one image after Release (chosen)”Pros: one discoverable package; no version drift; coordinated ordering; multi-architecture; one attestation and rollback surface.
Cons: the all-in-one image carries more executable code than a consumer using only one selector. Dedicated local targets mitigate policy inspection but are not published.
Option B: Publish one package per executable
Section titled “Option B: Publish one package per executable”Pros: smallest runtime and narrowest SBOM for each consumer.
Cons: five registry packages and five moving-tag/provenance/visibility surfaces for four entry points that already share one repository and release.
Option C: Publish on every tag push
Section titled “Option C: Publish on every tag push”Pros: simpler workflow trigger and fastest availability.
Cons: races the coordinated npm release, so the same version can exist in GHCR while its package release is failing.
Option D: Keep the Containerfile local only
Section titled “Option D: Keep the Containerfile local only”Pros: no registry or release obligation.
Cons: every consumer rebuilds the same image, MCP configuration cannot pin a maintainer-built digest, and local builds lack registry provenance.
Trade-offs
Section titled “Trade-offs”- Container publication is downstream of already-public npm packages and the newly published GitHub release. If it fails, those artifacts cannot be rolled back; the image is recovered by rerunning publication for the existing tag.
- Digest-pinned bases are reproducible but require reviewed maintenance updates for Node, Alpine, and Bun patches.
- The all-in-one image is larger than a dedicated target and exposes selectors a given consumer may not use.
- The first GHCR package requires a one-time human visibility decision before unauthenticated users can pull it.
Consequences
Section titled “Consequences”- Easier: users can run CLI and MCP without installing Node or Bun; MCP clients can pin one stable stdio command; releases carry verifiable registry provenance.
- Harder: release operations now include a downstream workflow, GHCR package settings, base digest maintenance, multi-architecture builds, and a second recovery path.
- How we would know this was wrong: if users consistently require dedicated published images for policy/SBOM acceptance, or if the downstream publish fails on more than one of the next three lockstep releases.
- Revisit if: a registry consumer requires a different cadence from the npm surface, or GHCR provenance/retention cannot satisfy the supported workflow.
Action items
Section titled “Action items”- Add isolated CLI, MCP, governing-decisions, queue, and all-in-one targets.
- Pin build/runtime base indexes and run final images as non-root.
- Exercise CLI, both MCP protocol eras, and both Action failure boundaries without runtime network access.
- Publish multi-architecture lockstep images to GHCR after Release success.
- Attach registry provenance and document first-publish visibility, immutable tags, and recovery.