ADR-0010 Own a Repository Runner Image
Context and scope¶
Needlr's trusted Linux validation and release jobs use ephemeral PitCrew workers. Those jobs repeatedly acquire the .NET SDK and install Native AOT prerequisites even though the required toolchain changes much less frequently than the workers.
Release v0.0.3-alpha.3 demonstrated the operational cost. Its publication worker spent
about twenty minutes in SDK setup before any release preparation could begin. Staged
release publication now makes downstream retries cheaper, but it does not make worker
startup deterministic.
Needlr already selects trusted Linux runners through CI_RUNNER, preserves a
general-purpose PitCrew fallback, and forces external forks onto GitHub-hosted workers.
PitCrew supports operator-approved external OCI profiles with immutable image identity,
verification commands, rolling worker replacement, and rollback.
This decision governs the toolchain image, publication boundary, profile identity, and workflow routing for Needlr's trusted Linux jobs. It does not let repository workflows mutate PitCrew, change host capacity, or select arbitrary images at runtime.
Decision drivers¶
- Ephemeral workers should start with the exact supported .NET SDK already installed.
- Runner configuration must remain reproducible and rollback-safe.
- Pull requests must validate image changes without publishing deployable images.
- Only trusted
mainmay publish the image. - External forks must never reach self-hosted infrastructure.
- GitHub-hosted and general-purpose PitCrew fallbacks must remain available.
- No source, generated output, or credentials may enter an image layer.
- Host capacity and image activation remain operator-owned.
Decision¶
Needlr will publish a public Linux amd64 runner image at
ghcr.io/ncosentino/needlr-runner.
GHCR creates the package as private on its first push. A repository owner performs a
one-time visibility change after trusted main publishes the initial image and verifies
anonymous digest access before the profile PR is opened. Later image revisions retain the
package's public visibility.
global.json is the source of truth for the exact .NET SDK. The runner Dockerfile uses
the same SDK version, pins every base image by SHA-256 digest, copies only the .NET
installation from the SDK stage, and installs the stable Native AOT OS prerequisites.
Python and Node.js remain workflow-managed until the repository defines exact versions
for them.
Pull requests build and execute the image on GitHub-hosted Ubuntu without publishing it.
Trusted main publishes a commit-tagged image and records the immutable manifest digest.
No pull-request event receives package publication capability.
Activation uses a separately reviewed .pitcrew/runner-profile.json named needlr-ci.
The profile references the immutable GHCR digest, disables GitHub default labels, and
verifies the runner listener, SDK, and advertised native tools. PitCrew automatically
adds needlr-ci as the routing label.
Bootstrap and updates use two repository changes: the image contract first, then the
published digest and routing contract. A workflow cannot safely commit the digest of an
image that trusted main has not published yet.
The host operator applies the profile with Needlr's existing capacity, confirms the
specialized runner is online, and only then sets CI_RUNNER=needlr-ci. Repository
workflows detect the exact SDK before invoking setup, so the specialized profile avoids
the download while hosted fallback remains functional.
Alternatives considered¶
Continue runtime SDK installation¶
This keeps workflows portable and avoids image ownership. It was rejected because every ephemeral worker repeats slow external acquisition and can stall before useful work begins.
Put the toolchain in PitCrew's default image¶
This centralizes image maintenance. It was rejected because PitCrew is a runner orchestrator, not an ecosystem toolchain catalog. Needlr owns its SDK and native build requirements.
Publish and activate a mutable tag¶
This simplifies updates because the profile never changes. It was rejected because the same profile document could resolve to different bytes, weakening auditability and rollback.
Publish from pull requests¶
This would make the image digest available before merge. It was rejected because untrusted or unreviewed code must not publish deployable runner images.
Include Python and Node.js in the initial contract¶
The base runner already supplies those tools and workflows currently request floating versions. Adding them without exact repository version contracts would create an appearance of determinism without providing it. They remain workflow-managed.
Consequences¶
Positive¶
- The exact .NET SDK and Native AOT prerequisites are prepared once per image revision.
- Image changes are reviewed, validated, digest-pinned, and independently rollbackable.
- Hosted fallback and external-fork isolation remain intact.
- Needlr owns its toolchain lifecycle without expanding PitCrew's responsibilities.
Negative¶
- Image publication and digest activation require two pull requests.
- Needlr assumes GHCR image maintenance and storage.
- The operator must apply the profile on each approved host before changing routing.
- SDK servicing requires coordinated changes to
global.json, the image, and profile digest.
Neutral¶
- Python and Node.js setup remain unchanged.
- Existing general-purpose PitCrew capacity remains available during rollout and rollback.
- Busy stale workers may continue until their current job finishes.
Confirmation¶
Repository tests validate the SDK pin, base-image digests, absence of source or credential-bearing Dockerfile inputs, trusted publication trigger, commit tag, and digest capture.
The pull-request image workflow builds and executes the candidate. The trusted-main run must publish a digest before the profile PR can be created.
Activation is confirmed by a live needlr-ci runner, representative source validation,
both Native AOT jobs, documentation generation, and a non-publishing release dry run.
PitCrew rollout evidence must report the approved target image and preserve the existing
Needlr capacity.
Outcome Notes¶
2026-07-29 - initial image publication¶
Trusted main published the initial Linux amd64 image at:
ghcr.io/ncosentino/needlr-runner@sha256:233a06905fc35312fe73099d12e93bf496ac1b98dd19a0482be000c12d7b4461
The package permits anonymous digest retrieval. The separately reviewed
.pitcrew/runner-profile.json pins that identity and preserves Needlr's existing
one-worker capacity.
References¶
- ADR-0009 records the staged release publication model whose preparation job benefits from deterministic worker startup.
- Needlr issue 119 defines the repository-owned image and portable routing requirements.
- PitCrew's repository-owned image guide defines the operator/repository ownership boundary and digest-pinned external profile lifecycle.