ADR-0026: One Makefile builds Proven, builds and pushes its image, and starts, checks and stops the judges' lab
Status: accepted · Date: 2026-10-23
Decided by: the owner · Written by: the advisor (claude-code, claude-opus-5-5), 2026-10-23
Follows: ADR-0017 (the Gitea lab), ADR-0013 (no hardcoded settings), ADR-0025 (the Prover's container)
Amends: ADR-0017, point 6 (Proven's own image is tagged latest) and the runner choice left open in its Consequences (Docker in Docker)
Trigger: the owner, on 23 October: one standard Makefile, the same one used in every project, for the whole judges' environment end to end
Context
On 23 October main held no Compose file, no lab and no image of Proven: the lab of ADR-0017 was decided and not
built. A judge also needs a way to build Proven on a machine that may have Go, only Docker, or neither.
The owner uses one Makefile shape in every project. Run with no target, it prints its help. It builds the software, builds images for several platforms, pushes them to a registry, starts and stops the environment, and shows its status and its logs.
Decision
- A
Makefileat the repository root is the one entry point.makewith no target prints the help: every target with one line, the version, whethergoanddockerwere found, and the lab's address when it runs. make buildbuilds the native binaries (proven,livecheck) into./bin/, choosing the first path that works:- Go is installed at the version
go.modasks for or later:go build; - no Go, but Docker: the same build inside a pinned
golangimage, with the binaries written to the host; - neither: it prints how to install Go on macOS and on Linux, and exits 1.
- Go is installed at the version
make build-allcross-compiles for darwin and linux, amd64 and arm64, into./dist/with checksums.make imagebuilds Proven's image for the local platform;make pushbuilds it for several platforms (by default linux/amd64 and linux/arm64) with provenance and an SBOM, and pushessaphan/provento Docker Hub. The registry and the image name are settings.- Every image we build is minimal and scanned by Docker Scout (the owner, 23 October). Proven's image carries the
static binary and only the two programs Proven runs (
git, and the container client the Prover calls), on the smallest base that holds them, and runs as a user that is not root. The same holds for any helper image of the lab we build (the seed).make scanruns Docker Scout on each image we build, andmake pushbuilds once, scans every platform of that build by digest, and makes the final tags from those digests only when every scan passes; it makes none while Scout reports a critical or high vulnerability with a fix available on any platform; the severities that refuse are a setting. The pinned images of others in the lab (Gitea, its runner, Docker) are scanned too and their result is written indocs/LAB.md: we cannot fix them, and a judge should know what they hold. - Proven's own image is tagged
latest(the owner, 23 October). Each push also carries the taggit describegives, so the image a judge ran on a given day can still be pulled. Every other image in the lab (Gitea, its runner,golang, Docker) stays pinned to a version, as ADR-0017 point 6 says. make upstarts the lab of ADR-0017;make downstops it. One Compose file: Gitea, a Gitea Actions runner, and a one-time seed that creates the admin, the demo repository with an open pull request and Proven's workflow, and registers the runner. Everything binds to localhost. The admin's password is generated on the firstmake upand reaches the judge once, never through a log (the owner, 25 October). When stdout is a terminal,make upprints it once, and a later run does not print it. When stdout is not a terminal (CI, a redirect, a pipe),make updoes not print it: it writes the password to a file readable only by the user (mode 0600, created with that mode) in the lab's state directory,lab/.state/, and prints only the path. No password goes into a log, a trace, a Compose output or amake logsline.make cleanremoves the file. A judge may supply their own password inLAB_ADMIN_PASSWORD; it is read from the environment ofmake up, is used, is neither printed nor written to a file on the host, and is never in a committed file. It never enters the Compose environment:lab/compose.yamldoes not name it, sodocker compose configrenders no password.make upwrites it, at mode 0600, to a file in the lab's state volume, and the one-shot seed reads that file.- The runner starts the Prover's containers in Docker in Docker inside the lab, never through the host's Docker socket (the owner, 23 October). The lab's jobs get no control of the judge's Docker.
make statusshows the containers, Gitea's health, whether the runner is registered, the last workflow result on the pull request and the lab's address.make logsfollows the logs (SERVICE=for one service).make sampleruns the sample-mode review from a directory outside the repository, with no network, no keys and no Docker.make livechecknames which live services are ready, reading an optionallab/.env.make e2eis the judge's rehearsal: it starts the lab, waits for the workflow, asks Gitea's API whether Proven's review is on the pull request, and exits 0 or 1. It is the daily rehearsal run before each push.make cleanremoves the lab's containers, volumes and local images, and./binand./dist.- Every version, port and name is a setting with its default in one file,
lab/versions.env, which the environment can override, and each one is listed indocs/CONFIGURATION.md. Keys live only inlab/.env, which is never committed; the repository holdslab/.env.example. - The judge's runbook is
docs/LAB.md, and the README's quick start begins withmake up.
Consequences
- A judge with only Docker gets the binaries, the lab and the review on a pull request, without installing Go.
make e2eturns ADR-0017 point 7 (the lab is rehearsed the way a judge runs it) into one command that fails loudly.- Docker in Docker needs a privileged container inside the lab. That is a cost on the judge's machine, paid instead of handing the lab's jobs the host's Docker.
latestmoves with every push: a judge who pulls on a later day gets a later Proven. The dated tag is how a given day's image is pulled again.- Docker Scout needs a Docker Hub sign-in, so
make scanandmake pushare the maintainers' targets. A judge who only builds and starts the lab never needs an account. - Windows is not a target:
makeand the scripts assume macOS or Linux.