nix module: default container.defaultImage to a digest-pinned buildpack-deps:stable-scm #230

Closed
opened 2026-07-25 16:29:11 +02:00 by dominik.polakovics · 0 comments

Problem

Container spawns on a stock module deployment refuse with:

no dev image for this repo — set a dev image in the repo's Runner settings or configure a server default with --container-image

because container.defaultImage defaults to null (nix/module.nix) and the production host config never sets it. ADR-0053 made the global image optional; this issue keeps that demotion but makes the module ship a working default so a fresh deployment can spawn container sessions out of the box.

Decision

The NixOS module defaults container.defaultImage to a digest-pinned docker.io/library/buildpack-deps:stable-scm@sha256:…. That image satisfies the dev-image contract as-is (shell, coreutils, git, openssh-client, curl, ca-certificates; anonymous pull from docker.io). The Go binary's --container-image flag default stays empty — lab-the-binary still ships no image of its own; the module is the opinionated deployment layer, exactly as it already is for toolsImages.

null remains a valid explicit value (opt-out: per-repo-only deployments per ADR-0053 stay supported), and a repo's own image_ref still overrides the global.

Work

  1. nix/module.nix — change container.defaultImage's default from null to the pinned ref, written as a literal. Resolve the digest at implementation time:
    skopeo inspect --format '{{.Digest}}' docker://docker.io/library/buildpack-deps:stable-scm
    and store it as docker.io/library/buildpack-deps:stable-scm@sha256:… (tag kept for readability). Put the exact re-pin command in a comment beside the literal — re-pinning is a manual PR, no automation.
  2. flake.nix checks — flip the two checks that assert the default unit passes no --container-image (currently ~lines 328-337 and 422-429): the default unit must now carry the pinned ref. Add/adjust a check proving an explicit defaultImage = null renders no --container-image (opt-out preserved). Keep the explicit-override fixture (~line 104) on a different ref (debian:stable-slim is fine) so it proves the override wins over the new default.
  3. .forgejo/workflows/deploy.yml — add the default dev-image ref to the existing skopeo "wait for refs" probe, so a stale/GC'd digest fails the deploy instead of surfacing as spawn refusals on a fresh host. This knowingly adds docker.io to the deploy path's probe dependencies.
  4. docs/ops.md — update the module-table row for container.defaultImage (no longer null) and the Container-runner prose. Also fix the self-contradiction in "Dev image expectations": it names debian:stable-slim as a qualifying example, but the same paragraph requires git and an ssh client, which that image does not ship — name images that actually meet the contract (e.g. buildpack-deps:stable-scm, alpine with the needed packages added).
  5. docs/adr/ — short successor ADR to ADR-0053: the demotion stands (global is a default, not required; per-repo ref wins; null valid), superseded only in that the module now defaults to a pinned stock scm image instead of null.

Rejected alternatives (from the design discussion)

  • Fallback baked into the Go binary — violates the operator-owns-the-image contract and would run unpinned.
  • Resolving/pinning the global ref at startup — pin-on-boot ≠ pin-on-review, and boot would depend on registry reachability.
  • A Cloonar-built dev image as the default — owns a Dockerfile + publish pipeline for a fallback that per-repo refs already override; revisit only if agents miss rg/jq userland.
  • Setting the value only in the host config repo — the default belongs upstream in the module.

Acceptance

  • nix flake check passes with the flipped assertions.
  • Default module deployment renders --container-image docker.io/library/buildpack-deps:stable-scm@sha256:… in ExecStart; explicit defaultImage = null renders none.
  • A container repo with no image_ref on a default deployment spawns instead of refusing.
## Problem Container spawns on a stock module deployment refuse with: > no dev image for this repo — set a dev image in the repo's Runner settings or configure a server default with --container-image because `container.defaultImage` defaults to `null` (nix/module.nix) and the production host config never sets it. ADR-0053 made the global image optional; this issue keeps that demotion but makes the *module* ship a working default so a fresh deployment can spawn container sessions out of the box. ## Decision The NixOS module defaults `container.defaultImage` to a **digest-pinned** `docker.io/library/buildpack-deps:stable-scm@sha256:…`. That image satisfies the dev-image contract as-is (shell, coreutils, git, openssh-client, curl, ca-certificates; anonymous pull from docker.io). The Go binary's `--container-image` flag default stays empty — lab-the-binary still ships no image of its own; the module is the opinionated deployment layer, exactly as it already is for `toolsImages`. `null` remains a valid explicit value (opt-out: per-repo-only deployments per ADR-0053 stay supported), and a repo's own `image_ref` still overrides the global. ## Work 1. **nix/module.nix** — change `container.defaultImage`'s default from `null` to the pinned ref, written as a literal. Resolve the digest at implementation time: `skopeo inspect --format '{{.Digest}}' docker://docker.io/library/buildpack-deps:stable-scm` and store it as `docker.io/library/buildpack-deps:stable-scm@sha256:…` (tag kept for readability). Put the exact re-pin command in a comment beside the literal — re-pinning is a manual PR, no automation. 2. **flake.nix checks** — flip the two checks that assert the default unit passes no `--container-image` (currently ~lines 328-337 and 422-429): the default unit must now carry the pinned ref. Add/adjust a check proving an explicit `defaultImage = null` renders no `--container-image` (opt-out preserved). Keep the explicit-override fixture (~line 104) on a *different* ref (`debian:stable-slim` is fine) so it proves the override wins over the new default. 3. **.forgejo/workflows/deploy.yml** — add the default dev-image ref to the existing skopeo "wait for refs" probe, so a stale/GC'd digest fails the deploy instead of surfacing as spawn refusals on a fresh host. This knowingly adds docker.io to the deploy path's probe dependencies. 4. **docs/ops.md** — update the module-table row for `container.defaultImage` (no longer `null`) and the Container-runner prose. Also fix the self-contradiction in "Dev image expectations": it names `debian:stable-slim` as a qualifying example, but the same paragraph requires `git` and an ssh client, which that image does not ship — name images that actually meet the contract (e.g. `buildpack-deps:stable-scm`, `alpine` **with** the needed packages added). 5. **docs/adr/** — short successor ADR to ADR-0053: the demotion stands (global is a default, not required; per-repo ref wins; null valid), superseded only in that the module now defaults to a pinned stock scm image instead of null. ## Rejected alternatives (from the design discussion) - Fallback baked into the Go binary — violates the operator-owns-the-image contract and would run unpinned. - Resolving/pinning the global ref at startup — pin-on-boot ≠ pin-on-review, and boot would depend on registry reachability. - A Cloonar-built dev image as the default — owns a Dockerfile + publish pipeline for a fallback that per-repo refs already override; revisit only if agents miss `rg`/`jq` userland. - Setting the value only in the host config repo — the default belongs upstream in the module. ## Acceptance - `nix flake check` passes with the flipped assertions. - Default module deployment renders `--container-image docker.io/library/buildpack-deps:stable-scm@sha256:…` in ExecStart; explicit `defaultImage = null` renders none. - A container repo with no `image_ref` on a default deployment spawns instead of refusing.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/coding-lab#230
No description provided.