Reproducible Builds & Build Attestation
SPR ships two independent supply-chain guarantees you can check yourself:
- Reproducible builds — building a given git commit produces byte-identical container images, so the digest we publish is one anyone can reproduce.
- Build attestation — every published image is signed with a keyless Sigstore signature that ties it to our public GitHub Actions build. On the main release channel, SPR verifies this attestation before applying an update, so tampered or unsigned images are rejected.
The two work together: attestation proves an image came from our build pipeline, and reproducibility lets anyone confirm that pipeline actually built the source.
Reproducible builds
SPR container images are bit-for-bit reproducible: building the same git commit
on any machine, on any day, produces byte-identical image digests. This applies
to the super repo and to the container_template base image every service
builds FROM.
Reproducibility requires that every build input is content-addressed and that the output bytes are deterministic. The levers:
- Base images pinned by digest.
ubuntu,alpine,node, thecontainer_templateruntime base, the Dockerfile frontend, and the BuildKit backend are all pinned by@sha256:(multi-arch index digests valid for amd64 and arm64). - apt via the Ubuntu snapshot archive. Every
apt-getresolves againsthttps://snapshot.ubuntu.com/ubuntu/<TS>/at a fixed timestamp, freezing package versions. GPG signatures are still verified against the Ubuntu archive keyring. Docker CE (only insuperd) is pinned to exact package versions from download.docker.com. - Toolchains downloaded and checksummed. Go and bun are fetched at pinned
versions and verified against a sha256;
GOTOOLCHAIN=localstops Go from fetching a different compiler mid-build. - Source deps pinned to commits. Everything
git cloned (coredhcp, coredns and its SPR plugins, hostap, xdp-tools, godns, sprbus-json) is fetched at an immutable commit SHA rather than a moving branch or tag. - Deterministic Go builds.
go build -trimpathstrips absolute build paths; the frontend usesyarn install --frozen-lockfile. - Deterministic timestamps.
SOURCE_DATE_EPOCHis set to the git commit time and the image exporter runs withrewrite-timestamp=true, which rewrites in-layer file timestamps so the layers are byte-identical.
Single source of truth: reproducible.env
All shared pins live in reproducible.env. build_docker_compose.sh and CI read
it and thread every value into the build as a --set *.args.* override. Each
Dockerfile also declares matching ARG defaults so a plain docker build of a
single context is reproducible too.
Pins are regenerated with update-pins.sh, which re-resolves every value (image
digests, Go/bun checksums, the snapshot timestamp, git commits) from read-only
sources and rewrites both reproducible.env and the Dockerfile ARG defaults so
the two never drift.
Building reproducibly
# Reproducible local build (loads single-arch images into Docker):
./build_docker_compose.sh
# Reproducible multi-arch build + push (used by CI):
./build_docker_compose.sh --push
The script sets SOURCE_DATE_EPOCH from the commit, pins the BuildKit backend,
and forces the rewrite-timestamp=true exporter. CI does the same via
.github/workflows/docker-image.yml.
docker compose buildis not bit-for-bit — it uses thedockerexporter withoutrewrite-timestamp, so in-layer timestamps vary. Usebuild_docker_compose.sh.
Verifying reproducibility
The repo's verify script builds each target twice with --no-cache and asserts
the two digests match:
# Default targets: base api frontend db
./reproducible-build-verify.sh
# Specific targets:
./reproducible-build-verify.sh dns wireguard
It prints a ✅ line with the shared digest for each reproducible target, or a
❌ line if the two builds diverge, and exits non-zero on any failure. Set
PLATFORM (default: your host arch) to verify a specific architecture.
Reproducibility is guaranteed per git commit, from a clean checkout — a dirty working tree (untracked or modified files) can change a build. CI builds from a clean checkout.
Build attestation
Every image our CI publishes is attested with a Sigstore keyless signature. There is no long-lived signing key: Sigstore's Fulcio CA issues a short-lived certificate to each GitHub Actions workflow run over OIDC, the signature is recorded in the Rekor public transparency log, and the resulting bundle is attached to the image digest. Verification confirms the image was built by our workflow, and the transparency-log entry can be audited by anyone.
superd verifies these attestations against a fixed identity:
- Issuer —
https://token.actions.githubusercontent.com(GitHub Actions OIDC). - Subject (SAN) — the certificate's identity must match the
spr-networks/superrepo's.github/workflows/docker-image.ymlworkflow. - Transparency — the Sigstore trusted root is fetched via TUF and the verifier requires a signed certificate timestamp plus a Rekor inclusion proof.
Because verification is bound to our workflow identity, stealing a registry token is not enough to push a backdoored image to your router — an attacker would have to take over the GitHub Actions workflow itself, which would leave a permanent entry in a transparency log we do not control.
How updates are gated
On the main release channel, superd verifies provenance around every update
(superd/code/attest.go):
- Before pulling, it enumerates the images the update would pull, resolves each remote digest, fetches the attestation bundle from the GitHub attestations API, and verifies it against the workflow identity. If any image fails, the update aborts before anything is downloaded.
- After pulling, it re-verifies the digest that was actually pulled to each local image. If a pulled digest fails verification, the update fails.
Verification results are published on the SPR bus (superd:attest:ok /
superd:attest:failure) and surfaced in the app. See
Verified Updates for the update
flow. The dev channel is not gated this way and is not recommended outside of
development.
Verifying an attestation by hand
You can reproduce the same provenance check with the GitHub CLI:
gh attestation verify \
oci://ghcr.io/spr-networks/super_base:latest \
--repo spr-networks/super
This fetches the image's Sigstore bundle and confirms it was signed by the
spr-networks/super build workflow.