Give the Dockerfile-building step a builder image, and make §9n true again #119

Merged
tepichord merged 11 commits from feat/118-dockerfile-builder-image into main 2026-10-07 21:46:50 -07:00
Owner

Closes #118

Automated PR created by forgejo-done adapter.

Closes #118 Automated PR created by forgejo-done adapter.
The four measures a consumer's step needs to build a Dockerfile in a
namespaced pod (issue #118) become an executable this repo owns:
`buildAndPush` is a writeShellApplication with the map width read from
the pod (no awk), the pause process' runtime dir, the wide re-exec, the
three measures around `podman build`, and a push whose credential
arrives on stdin and whose served digest is compared against the pushed
one — same shape as build-oz.

`podmanBuilderImage` is a dockerTools.buildLayeredImage (no base image)
carrying podman (helpers via its own closure), nix at
nixVersions.nix_2_35 — never pkgs.nix, 2.34.8 at this pin against the
seeded store's 2.35.2 — busybox, and the container configs plus
subuid/subgid via extraCommands, at the pin-derived tag
nixpkgs-<first 8 of nixpkgs.rev>. `podmanBuilderCheck` opens the
image's own layers and asks every claim of them: the tools resolve
inside, the nix reports 2.35.2, the helpers are carried, the configs
read back, Cmd/Env from the image's own config bytes, and the recipe
keeps each measured measure with no credential in argv.

Verified: nix build .#podmanBuilderCheck && cat result.

Refs: #118
The Dockerfile recipe stops being prose each consumer transcribes
(docs/woodpecker-ci.md §9n) and becomes an image this repo builds,
checks and pushes: `build-podman-builder` copies build-oz's shape —
nixos/nix:2.35.2 on the shared store, the check of the image's own bytes
before the push, the flake's wrapped skopeo, then the served-digest
compare — over the flake outputs .#podmanBuilderImage / Check / Tag.

Path-filtered on the two files the image's toolchain and recipe are
written in; a pull request builds and checks and pushes nothing, because
the tag is shared (the decision build-enqueuer's pull-request half
already makes), and a bare manual run stays the escape hatch for a lost
tag.

The builder is never run here — no unshare, no namespace, no privileged
field — which is what keeps NAMESPACED_ALLOWLIST empty and the
privileged-tier image-name guard satisfied at the same time.

Refs: #118
§10a's writer list and §10g's mounting-step list gain
`build-podman-builder`, and §10g's count is the honest one for the
pipeline as it now stands (every gates step plus cleanup-preview).

Refs: #118
build-podman-builder joins every list that names this pipeline's image
builds: the story-18 ordering tuple (the suite still precedes it), the
store writers list and §10g's seeded image set (nixos/nix:2.35.2, one
image across seed and writer), the wrapped-skopeo push loop, and the
bare-manual escape hatch.

The new test holds what is specific to this step: the recipe is an
executable with exactly three required arguments and the token on
stdin, the tag is the flake's nixpkgs pin rather than a literal, the
check of the image's own bytes runs before the push, and the pull
request half writes nothing.

Refs: #118
The section published a shape measured against a distro podman, kept the
chroot isolation buildah#6947 kills at the pin, offered a namespace's mere
existence as proof of width, and called the allowlist "one step per pipeline".
It now points at milner/podman-builder first, keeps each measured measure with
its reason, drops chroot isolation, replaces the unshare -U true proof with the
width argument, states the allowlist per run with the two disjoint events, and
records the three follow-ups as open rather than assumed. The gates.yaml
builder comment block says the same invariant.

Refs: #118
§15 and §15d now state plainly that a derivation needs none of the user
namespace, procfs bind, vfs or pause-process measures, and that a
Dockerfile-building step pays all of them — the reason to prefer the
derivation.

Refs: #118
The allowlist test now also fails §9n that carries BUILDAH_ISOLATION again
(buildah#6947 at the pin) or that states the invariant per pipeline rather than
per run: a consumer's build and publish are two disjoint events, so two grants
never meet in one run.

Refs: #118
Story 20b and the allowlist test still said "one step per pipeline",
which #118's per-run invariant supersedes: a consumer's build and
publish are two granted namespaced steps on disjoint events, so the
per-pipeline count undercounts what may be granted. Reword the story
(hard rule: the story changes in the change that contradicts it), the
test's docstring and its failure message, and drop two flake comments
that repeated a reason already given at its code site.

Refs: #118
The script re-execs itself as `"$0" --inside "$@"`, so the second
invocation sees four arguments. The arity check sat above the re-exec and
therefore refused every real call with exit 2 — a bug the image check could
not catch, because it only greps the recipe's bytes. Move the check below
the `shift` that strips the marker, and assert the order in the suite so a
future move back cannot ship.
docs(woodpecker-ci): name the registry in the builder example
Some checks failed
ci/woodpecker/pr/gates Pipeline failed
329828c657
The consumer snippet called `build-and-push` with two arguments while the
prose above it — and the script — require three: context, tag, registry. A
reader who copied it would get the usage error.
tepichord added this to the MVP project 2026-10-07 18:43:42 -07:00
chore(reaper,annia): bump the two tags the builder's flake change moves
All checks were successful
ci/woodpecker/pr/gates Pipeline was successful
ci/woodpecker/pull_request_closed/cleanup Pipeline was successful
6fcf8037a4
`flake.nix` gains the `podmanBuilderImage` output, and that file is an input
to two tag guards — `ci/reaper-tag-guard.sh` over `flake.nix` and
`flake.lock`, and `ci/enqueuer-tag-guard.sh` over `enqueuer/`, `flake.nix`
and `flake.lock`. Both guards compare the branch's tip against the base
tip, so touching `flake.nix` moves both tags whether or not the builder is
anything either image is built from:

  * the reaper's image is `kubectl` and a static busybox over the pin, so
    this change does not move its bytes — but the guard cannot tell that,
    and its own suite (`tests/test_ci_guards.py`,
    `test_a_pin_change_without_a_tag_bump_fails`) states the invariant the
    reaper's three earlier bumps already followed;
  * the enqueuer's is `python313.withPackages (ps: [ ps.pika ])` over the
    same pin, and that guard keeps the router's both-directions form.

The pull request that adds the builder is where `reaper-tag-guard` failed
(pipeline #165), and `enqueuer-tag-guard` would have been the next step.
Both `build-reaper` and `build-enqueuer` are path-filtered on `flake.nix`,
so both rebuild and re-push under the new tags when this merges.

The enqueuer's tag is a literal in three other places that name it — the
kustomization's comment, `docs/rabbitmq.md` §16e and `docs/nix.md` §15a —
and each moves with the manifest, because a doc that names the old tag is a
stale doc.
tepichord deleted branch feat/118-dockerfile-builder-image 2026-10-07 21:46:50 -07:00
Sign in to join this conversation.
No reviewers
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
tepichord/milner!119
No description provided.