nucleic-runner container image
The Covalence runner's host-of-record container (design:
docs/COVALENCE_RUNNER.md §2, §9; narOS milestone N3:
docs/NAROS.md §7.2). Extends the narOS agent tier
(ghcr.io/abkslm/naros-agent, built from os/ — it replaced
nucleic-sandbox) with the nucleicd daemon, so a single container
is both the sync host of record and the agent sandbox — the CLOUD_RUNTIME RunSpec.container = nil model, where the container is the sandbox. The base brings nash forced as /bin/sh
(docs/NASH.md §7.3), the narOS identity/manifest, and the full agent toolchain; this image
stamps the runner-tier VARIANT on top.
Building
The nucleicd Linux build is real — the CLOUD_RUNTIME Phase-2 port landed: the full
NucleicProtocol test suite (Noise handshake vectors, relay-enrollment PoP, sealed-credential
crypto) passes on Linux under swift-crypto, GRDB runs on Linux, the approval server listens on
BSD sockets, secrets live in the 0600-file store, and the relay WebSocket is SwiftNIO. A booted
Linux nucleicd enrolls with the deployed relay and mints pairing codes — verified on both
aarch64 (glibc) and x86_64 (the fully-static musl build, run under qemu).
- Self-host on a Mac: you don't need this image at all —
swift run nucleicdruns the daemon natively, joining the mesh as aPeerKind.cloudhost. - Container build — Cloudflare Containers run
linux/amd64ONLY. The image binary is a fully-static x86_64-musl cross-build (works at native speed from any host arch; the script installs the Static Linux SDK and gives its sysroot the SQLite that GRDB links):docker run --rm -v "$PWD":/src -w /src swift:6.3-bookworm scripts/build-nucleicd-linux.sh cp .build/x86_64-swift-linux-musl/release/nucleicd containers/nucleic-runner/ docker build --platform linux/amd64 \ -t registry.cloudflare.com/<ACCOUNT_ID>/nucleic-runner:v1 containers/nucleic-runner wrangler containers push registry.cloudflare.com/<ACCOUNT_ID>/nucleic-runner:v1
Self-hosted variant (tailscale)
The image ships in two flavours, chosen by the WITH_TAILSCALE build arg:
- Nucleic-hosted (default,
WITH_TAILSCALE=0) — what CI builds and pushes to Cloudflare. These runners are reachable only over Nucleic's relay infra (nucleicdlistens relay-only), so no tailscale is installed; it would be dead weight the cloud runner never uses. - Self-hosted (
WITH_TAILSCALE=1) — for an operator who wants the container reachable over their own tailnet. This installs the OS-leveltailscalepackage. Build + tag it separately:docker build --platform linux/amd64 --build-arg WITH_TAILSCALE=1 \ -t nucleic-runner:self-hosted containers/nucleic-runnernucleicd's own Tailnet transport (TailscaleKit) is Darwin-only with no Linux build, so on Linux "tailscale" means the system daemon: the operator supplies aTS_AUTHKEYand runstailscaled+tailscale upalongsidenucleicd(nothing starts it automatically — the default entrypoint is just the daemon). A Mac self-host needs no container at all — see Building above.
Registry — Cloudflare's managed registry (not GHCR)
The image the containers block deploys lives in Cloudflare's own managed registry
(registry.cloudflare.com/<ACCOUNT_ID>/nucleic-runner), not GHCR: Cloudflare Containers /
Wrangler don't support GHCR as a pull source, and any external registry needs an extra
wrangler containers registries configure + pull credentials. The managed registry needs
none of that — wrangler containers push authenticates with the account's
CLOUDFLARE_API_TOKEN and Cloudflare pulls it at deploy time with no further config.
.github/workflows/runner-image.yml does the build + push in CI (below); the base
naros-agent image stays on GHCR because it's only pulled at build time (by Docker in
CI), never by Cloudflare. The FROM tag is pinned by the Dockerfile's NAROS_VERSION arg,
whose default must match os/VERSION (CI asserts the lockstep) — and the tag exists only
after .github/workflows/naros.yml has published that release, so sequence a version bump
as: naros.yml green first, then this image.
One gate before this image deploys to Cloudflare: runner-image.yml must run once (on a
main/dev push or via workflow_dispatch) to publish
registry.cloudflare.com/<ACCOUNT_ID>/nucleic-runner:v1 — the containers block in
cloud/nucleic-runner/wrangler.jsonc references it.
The rest of the runner stack (Worker + pool DO) deploys and is tested without it.
Runtime contract
The pool (RunnerPool DO → RunnerContainer DO) injects at start:
NUCLEIC_RUNNER_{POOL_ID,INSTANCE_ID,EPOCH,INSTANCE_TOKEN,CONTROL_URL} and optional
NUCLEIC_RELAY_URL. nucleicd enrolls with the relay, listens relay-only, heartbeats the pool
with its fencing epoch, and serves a one-shot pairing payload on loopback :9200 for the
provisioning proxy.