Audience: teams shipping Nix flakes on remote Mac CI in 2026 who must balance flake.lock hygiene, substituters, and max-jobs against APFS pressure.

Across 2026 renewals, buyers want a written map from flake inputs to cache hosts and a plan that keeps Apple Silicon runners from stampeding distant substituters. This guide turns that into paste-ready nix.conf, lockfile commands, and disk gates you can defend in audits.

Outcome: a cache matrix, a nix.conf starter with two signing keys, flake update and rollback commands, a disk-and-retry parameter checklist, and bounded fetch retries. Jump to the blog index, registry pull matrix, or MacPull home—no login required.

Why flake inputs and substituters fail on remote Mac fleets

Builders idle on narinfo, flake.lock drifts without review, or trusted-public-keys miss a new mirror. Tag incidents as lockfile, substituter, or disk so you tune one lever at a time.

1
Silent lockfile drift: wide nix flake update pushes land without cache coverage, forcing huge source builds.
2
Substituter ordering: every listed host is consulted; geography alone does not fix bad priority.
3
Store contention: high max-jobs on full disks stalls unrelated matrix legs.

Decision matrix: local store hits vs remote substituter vs self-hosted cache

Pick a primary cache path plus a documented fallback. In 2026 procurement reviews, auditors increasingly ask which country hosts your nar bytes; capture that answer beside each substituter URL so security teams do not block CI during renewals.

Source Strengths Risks Cross-border note Choose when
Local binary cache hits (warm /nix/store) Fast repeats when graphs already live in /nix/store; no TLS to third parties for cache hits. Cold hosts pay full fetch; tighten permissions on shared disks; GC can evict hot paths. Egress-free once objects land; still watch APFS fragmentation on busy pools. You already warm stores centrally or snapshot golden /nix volumes.
Remote substituter (vendor or community CDN) Zero ops; wide nixpkgs coverage; predictable signing keys. Latency, inspection policies, and occasional CDN brownouts surface as slow narinfo. Geography matters: order closest mirrors first, then widen only with explicit approval. Compliance allows public CDNs for upstream artifacts.
Self-hosted binary cache (same metro as runners) Metro-local latency; you own signing, retention, and log exports. You run keys, GC, capacity, and incident paging when disks fill. Best cross-border story when policy demands data residency next to runners. Policy forbids distant public caches for every path, or you need private derivations.

Hybrid setups are normal: keep self-hosted caches for proprietary derivations, fall back to cache.nixos.org for upstream misses, and still budget APFS space as if every job were cold because flake checks often touch new hashes after security bumps. If you also ship containers beside Nix, reuse the same disk discipline described in our pull stability FAQ so Docker layers do not starve the store.

Separate concerns: flake input locks vs substituter priority

flake.lock pins which git trees or tarballs enter your graph; substituters only decide where pre-built store paths may be fetched. Teams conflate the two when a wide nix flake update lands without cache coverage, then blame the CDN. Treat them as orthogonal controls: review lock diffs for supply-chain drift, and separately prove that each new hash is either substitutable or acceptable to build from source within your SLA.

On remote Mac fleets, funnel lockfile edits through one bot or trusted laptop, then fan identical flake.lock bytes to every runner so you can tune nix.conf ordering without silent graph drift.

Copy-ready nix.conf plus disk and retry parameters

Merge into /etc/nix/nix.conf (multi-user installs) or the per-user config your CI user reads, then restart the daemon on macOS so builders pick up substituters before jobs start. On typical Nix multi-user installs use launchctl kickstart -k system/org.nixos.nix-daemon; confirm with nix show-config | grep substituters. List closest caches first; append cache.nixos.org only when policy allows a public fallback.

Every host in substituters needs a matching trusted-public-keys entry; duplicate keys are ignored, missing keys produce trust errors or force unexpected source builds. Replace the placeholder cache name and key with values from your own nix-store signing doc—never reuse example secrets in production.

# Remote Mac CI baseline (2026) — metro-first substituters; two signing keys shown
substituters = https://my-cache.example https://cache.nixos.org
trusted-public-keys = my-cache.example-1:REPLACE_WITH_BASE64_PUBLIC_KEY_FROM_YOUR_SIGNER
trusted-public-keys = cache.nixos.org-1:6NCHdD59Z431t0A2VuUAgwCm9zPXAgEVJj5sRdfGC9LBukJCvQywFrpF4cD0H+3hz509uE+2wlJYwiGS9VSFMUXM78mKrQJpB8A/jDmpCDi838ZY4aWKg==
max-jobs = 2
cores = 0
connect-timeout = 5
max-substitution-jobs = 8

max-jobs caps concurrent derivations; cores = 0 lets Nix pick per-job CPU parallelism. On shared remote Mac pools start conservative—raise only after you measure p95 build time and disk write rates. connect-timeout shortens hung TLS handshakes to bad mirrors, while max-substitution-jobs bounds parallel nar downloads so you do not open hundreds of connections through an egress filter.

Disk watermarks for the Nix store volume

Watch the APFS volume for /nix: warn at eighty-two percent used, throttle at eighty-seven, hard stop at ninety-two, then nix-collect-garbage with policy approval.

Fetch retries (job wrapper): exponential backoff 1 → 3 → 9 → 27 seconds plus small jitter, overall cap ninety seconds; abort immediately on 401, trust failures, or repeated missing narinfo responses so you do not hammer a misconfigured substituter.

#!/usr/bin/env bash
set -euo pipefail
attempt=0
backoffs=(1 3 9 27)
while true; do
  if nix build ".#packages.aarch64-darwin.default" --print-build-logs; then
    exit 0
  fi
  status=$?
  if [[ $attempt -ge ${#backoffs[@]} ]]; then
    exit "$status"
  fi
  sleep $(( ${backoffs[$attempt]} + RANDOM % 3 ))
  attempt=$((attempt + 1))
done

Runbook numbers: begin with max-jobs = 2, cores = 0, keep twenty gibibytes free, and never list a substituter without its matching trusted key.

Parameter checklist: disk, jobs, and substitution concurrency

Keep this table beside each runner image. Numbers assume shared Apple Silicon hosts; tighten thresholds when Docker or Xcode caches share the same APFS volume.

Parameter Starter value Signal to tune
max-jobs 2 (shared pool) or auto (dedicated host) Rising system load, nar download stalls, or APFS latency spikes.
cores 0 (auto per job) CPU-bound derivations under twelve minutes with idle GPUs.
max-substitution-jobs 8 Many simultaneous TLS connections tripping egress IDS.
Disk warn / throttle / stop 82% / 87% / 92% used on /nix Repeated GC during business hours or CI queue pauses.
Minimum free space ≥ 20 GiB before starting nar-heavy builds Large nixpkgs bumps or first-time llvm rebuilds.
Retry ladder 1 · 3 · 9 · 27 s + jitter, cap 90 s total Transient 5xx or TLS reset storms on a single CDN POP.

Flake lockfile update and rollback commands

Update locks from a trusted bot or laptop; commit flake.lock with ticket ids. Pair every bump with a quick nix flake check or scoped nix build .#packages.aarch64-darwin.default so Apple Silicon graphs stay warm before the change fans out to every remote Mac agent. Avoid --recreate-lock-file in automation unless you intend to drop unspecified inputs—prefer explicit --update-input calls so reviewers see intent in shell history.

# Refresh every flake input and commit (trusted workstation or bot)
nix flake update --commit-lock-file

# Surgical bump of nixpkgs only
nix flake lock --update-input nixpkgs

# Roll back the lockfile with git (most teams)
git checkout HEAD^ -- flake.lock
# or revert the merge commit that carried the bad lock
git revert --no-edit <merge_sha>

# Pin nixpkgs to a known-good revision without editing flake.nix by hand
nix flake lock --override-input nixpkgs github:NixOS/nixpkgs/<REV>

After any rollback, capture nix flake metadata for your audit trail so security can compare resolved revisions with the last green build.

Five CI gates before nix build on remote Mac runners

  1. Disk probe: df -g /nix; fail under twenty gibibytes free or past the throttle percent.
  2. Lock review: reject flake.lock edits without ticket ids.
  3. Substituter smoke: nix path-info -r .#checks.default with a tight timeout.
  4. Bounded build: nix build --option max-jobs 2 --print-build-logs.
  5. GC: nix-collect-garbage --delete-older-than 30d only after green builds and safe disk headroom.

Also read build pool pull & disk FAQ when Docker shares the same APFS volume.

FAQ: trust errors, slow narinfo, and lockfile rollback

Substituter trust warnings. Pair every host with a trusted-public-keys line; reapply after macOS upgrades.

Sudden source builds. Diff nix flake metadata against the last green lock before blaming nixpkgs.

Rollback. Prefer git checkout on flake.lock or nix flake lock --override-input for surgical pins. If compliance wants receipts, archive the exact nix-store paths your policy allows instead of every intermediate experiment.

Summary: pin inputs, order caches, gate on disk

In 2026, ship Nix on remote Mac CI by pinning flake.lock, ordering substituters with keys, keeping max-jobs modest, and gating on disk before nar storms.

Need dedicated Apple Silicon hosts where you control nix.conf, store placement, and outbound allowlists? Visit pricing, purchase, and the help center, then continue on the blog index or homepage—all readable without signing in.

Remote Mac CI tuned for Nix flakes

Rent Mac Mini class runners with SSH or VNC, sized disks for the Nix store, and room to place private substituters near your build fleet.