Traditional cache-backed Nix on non-native ephemeral CI runners has a
performance bottleneck - /nix/store hydration. Caches are monolithic archives
that must be sequentially transferred, written to disk, then extracted.
Network and disk I/O are heavy, CPU is pegged. For a typical 1.5G cache (6G
uncompressed) on a standard runner this means minimum 60s before useful work
can begin. The full cache must be transferred on every job, post-job the store
must be re-archived and uploaded. Caches lack granularity and scale poorly with
input graph size.
This is extremely wasteful in terms of both compute/energy and engineer flow.
This is the unavoidable tax on purity.
This was the unavoidable tax on purity.
Nix seed provides seed OCI images with Nix and the project dependencies baked in. OCI layers are content-addressed, so naturally support re-use and extreme cacheability, and are pulled and mounted in parallel, without full extraction.
Performance characteristics:
- CI runner (VM) provisioning: ~5s (fixed provider cost)
- Layer pull + mount: <5s (with runner-local registry, e.g. GHCR)
- Source fetch: unchanged
- Build execution: unchanged
Replacing actions/cache with OCI layers makes build artifacts explicit. Once
artifact identity became first-class, release could no longer be treated as a
procedural step. Release is authority. Nix seed provides trust postures that
make that authority explicit.
Supply chain, secured: $$$.
Dependencies realised, once: $$$.
Flow state, uninterrupted: Priceless.
Nix Seed provides three trust postures: Innocent, Credulous, and Zero.
The default posture is Innocent.
“IDGAF about trust. Gimme Perf!”
99.999% of engineers polled
Innocent posture performs builds on a single CI provider.
- Guarantee: None.
- Attack Surface: CI provider, Nix binary cache infrastructure.
- Resiliency: Bounded by CI provider.
- Cost: Provider-bound.
Quorum-based postures require an N-of-M quorum of builders operating in independent failure domains (organisational, jurisdictional, and infrastructural) to attest bitwise-identical output. Forgery effort compounds with quorum size.
Guarantee: No single builder can forge a release.
Recommended quorum: 3 builders with a 2-of-3 quorum.
- Tolerates single builder outage without blocking promotion.
- Prevents a single compromised builder from forging a release.
- Trade-off: if an attacker can disable one builder, only two compromises are required.
Increasing quorum from 2-of-3 to 3-of-4 raises required compromises from 2 to 3 — a 50% increase in attack effort.
Integrity scales with the quorum threshold (k), not the total number of builders (n). Adding builders without raising quorum improves availability, not security.
“Trust, but verify.”
— Ronald Reagan (from Russian proverb), 1987
“I Want To Believe.”
— Fox Mulder, The X-Files, 1993
Credulous posture anchors trust on the Rekor public-good instance. Promotion is performed by a Release Node after quorum verification.
- Attack Surface: Builder set, OIDC trust roots, Release Node, Rekor, Nix binary cache infrastructure.
- Resiliency: Provider-bound. Rekor public-good has a 99.5% SLO (not SLA); downtime blocks block build and verify.
- Cost: Provider-bound. Rekor public-good is free.
Builders run witness/gossip checks against Rekor both as part of build and on a schedule; mismatches indicate split-view/equivocation. A self-hosted witness/gossip check is recommended.
Warning
Credulous posture does not mitigate compromise of Nix binary cache infrastructure; if all builders consume the same poisoned cache, malicious output will satisfy quorum.
“Ambition must be made to counteract ambition.”
— James Madison, Federalist No. 51, 1788
“Everyone has a plan until they get punched in the mouth.”
— Mike Tyson, 2002
Zero assumes that any actor may be compromised or coerced.
Binary caches are not trusted; builders must perform full-source bootstrap.
Promotion occurs mechanically upon quorum verification.
No Release Node exists; enforcement is contract-based.
Ethereum anchors trust and enforces quorum rules; build integrity remains defined by builder consensus.
Contracts are upgradeable under multi-signature, time-delayed governance.
Governance cannot alter finalised releases, but can modify future validation rules.
Structure constrains power. Verification replaces trust.
- Attack Surface: Governance keys, misconfiguration, hardware interdiction.
- Resiliency: High.
Full-source bootstrap (genesis build) is expensive. Wire Jansen's full-source bootstrap thesis reports ~17h30m on 12 logical cores / 16 GiB RAM - ~200 CPU-hours.
Cost scales with:
- Builders
- Systems
- Toolchain churn. A script is provided to estimate cadence from toolchain-critical path changes (events/week and median days-between-events)
Order-of-magnitude example (3 builders × 4 systems): ~2400 CPU-hours per full bootstrap event ≈ $100–$200 at typical rates ($0.04–$0.08 per vCPU-hour).
Contract enforcement cost ≈ Ξ0.001–Ξ0.003 (~$3–$9 @ Ξ1=$3k)
Add nix-seed to your flake.nix and expose seed in packages:
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
nix-seed = {
url = "github:0compute/nix-seed/v1";
inputs.nixpkgs.follows = "nixpkgs";
};
systems.url = "github:nix-systems/default";
};
outputs = inputs: {
packages =
inputs.nixpkgs.lib.genAttrs (import inputs.systems) (
system:
let
pkgs = inputs.nixpkgs.legacyPackages.${system};
in
{
# placeholder: replace
default = pkgs.hello;
seed = inputs.nix-seed.lib.mkSeed {
inherit pkgs;
inherit (inputs) self;
};
}
);
};
}Note
The examples below are GitHub-specific. The approach applies to any CI.
Seed and project builds require id-token: write permission.
If outputs include an OCI image, like seed build, the packages: write
permission is required.
Warning
Untrusted pull requests that modify flake.lock MUST NOT trigger seed or
project builds.
name: seed
on:
push:
branches:
- master
paths:
- flake.lock
workflow_dispatch:
jobs:
seed:
permissions:
contents: read
id-token: write
packages: write
strategy:
matrix:
os:
- macos-15
- macos-15-intel
- ubuntu-22.04
- ubuntu-22.04-arm
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- uses: 0compute/nix-seed/seed@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}on:
push:
branches:
- master
paths-ignore:
- flake.lock
workflow_run:
workflows:
- seed
types:
- completed
jobs:
build:
if: ${{
github.event_name == 'push' ||
github.event.workflow_run.conclusion == 'success'
}}
permissions:
contents: read
id-token: write
strategy:
matrix:
os:
- macos-15
- macos-15-intel
- ubuntu-22.04
- ubuntu-22.04-arm
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- uses: 0compute/nix-seed@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
cachix_cache: <name>
cachix_auth_token: ${{ secrets.CACHIX_AUTH_TOKEN }}Update seedCfg to use a quorum-based posture and define builders:
seedCfg = {
trust = "credulous";
builders = {
github.master = true;
gitlab = {};
scaleway = {};
};
quorum = 2;
};Builder independence requirements are detailed in Threat Actors.
nix-seed includes a helper to initialise and configure builder repositories to
mirror the source repository.
Provider credentials must be present in the environment.
nix run github:0compute/nix-seed/v1#sync