Private Cache Mesh¶
Overview¶
A private cache mesh is how a fleet shares pre-built store paths: several hosts act as substituters (HTTP caches) and/or push targets, wired together with explicit signing keys—not with VPN membership alone. One org often runs a central durable cache (Attic, Cachix, S3) while edge builders expose live stores (Harmonia, nix-serve) or push upstream after CI builds.
This page owns multi-host topology and client wiring—who substitutes from whom, in what order, and how remote builders participate. It does not cover picking and operating a single cache backend (Binary cache hosting), mesh trust vocabulary (Machine mesh), USB/file:// offline flows (Airgap and offline), or design-only gossip config (Self-healing config mesh).
Typical homelab split: CI or a builder pushes closures to a hub cache; long-lived builders run Harmonia for fast LAN/overlay hits; laptops and deploy targets list hub + edge URLs under substituters with matching trusted-public-keys, still keeping cache.nixos.org for upstream gaps.
Details¶
Peer substituters vs one cache host¶
| Model | Shape | Strength | Weakness |
|---|---|---|---|
| Single hub | All pushers → Attic/Cachix/S3; all clients → one URL | Durable, deduplicated, survives builder GC | Single ops surface; edge latency without CDN |
| Edge live-store | Each builder serves /nix/store (Harmonia/nix-serve) |
Zero push step for paths already on disk; great on overlay LAN | Paths vanish after GC; no cross-site durability |
| Mesh (mixed) | Hub for CI/products + edges for hot paths + optional peer nix copy |
Combines durability with local speed | More URLs, keys, and DNS to keep consistent |
A mesh is not “every peer trusts every peer’s store blindly.” Clients still need reachability (Overlay networks), substituter allow-listing on multi-user installs, and signature trust (Inter-machine trust).
Topology patterns¶
Hub + edge. CI or Hydra pushes to Attic/Cachix (or nix copy --to s3://…). Homelab builders run Harmonia on overlay hostnames (http://builder-a.tailnet:8080/). Laptops and servers add private URLs via extra-substituters (and matching keys) while keeping the default https://cache.nixos.org/ for upstream gaps. Prefer fleet caches with lower Priority in nix-cache-info (or configure store-URL priority) so private hits win when both have the path.
Builder-as-cache. A machine that already runs remote builds can serve its store without a separate cache VM—configure Harmonia/nix-serve on the same host that holds build products. Products appear for substitution only after they exist locally; schedule nix copy --to to the hub if you need durability.
Laptop → builder → server chain. The laptop offloads builds to a remote builder; the builder may substitute from its own caches (builders-use-substitutes, below) or build from source. Deploy hosts (NixOS servers) substitute from hub + builder HTTP caches; deploy tools may additionally nix copy closures over ssh:// when HTTP is wrong for one-off shipping (nix copy and bundles).
Peer copy alongside HTTP. HTTP substituters scale for many clients pulling the same .narinfo/NAR. nix copy --to ssh://host (or --from) complements HTTP for ad hoc closure transfer, airgap handoff, or when no cache daemon runs on the donor—see Store protocols.
┌── Attic / Cachix (durable hub) ◄── CI push
│
laptop ──remote build──► builder-a (Harmonia, live store)
│ │
│ substituters │ substituters + optional push
▼ ▼
server-1, server-2 ── HTTP ──► hub + builder-a + cache.nixos.org
Declarative nix.settings by role¶
Illustrative NixOS fragments only—replace hostnames, keys, and tokens with your fleet. Use extra-substituters / extra-trusted-public-keys to append without dropping the default NixOS lists.
CI pusher (builds, pushes to hub; may still substitute from hub + upstream):
{ ... }: {
nix.settings = {
extra-substituters = [ "https://myorg.cachix.org" ];
extra-trusted-public-keys = [ "myorg.cachix.org-1:BASE64…" ];
};
# Push step is tool-specific: cachix push, attic push, nix copy --to s3://…
}
Builder + edge Harmonia (serves live store; substitutes from hub):
{ ... }: {
services.harmonia = {
enable = true;
signKeyPaths = [ "/run/keys/cache-signing.secret" ];
};
nix.settings = {
extra-substituters = [ "https://attic.internal.example/my-cache" ];
extra-trusted-public-keys = [ "attic.internal.example-1:BASE64…" ];
};
}
Deploy target / server (consumes hub + nearby builders; keep default cache.nixos.org via NixOS defaults):
{ ... }: {
nix.settings = {
extra-substituters = [
"http://builder-a.tailnet:8080/"
"https://attic.internal.example/my-cache"
];
extra-trusted-public-keys = [
"builder-a.example-1:BASE64…"
"attic.internal.example-1:BASE64…"
];
trusted-substituters = [
"http://builder-a.tailnet:8080/"
"https://attic.internal.example/my-cache"
];
};
}
Laptop (coordinator + remote builds):
{ ... }: {
nix.distributedBuilds = true;
nix.settings.builders-use-substitutes = true;
nix.buildMachines = [
{
hostName = "builder-a.tailnet";
sshUser = "remotebuild";
system = "x86_64-linux";
}
];
nix.settings = {
extra-substituters = [
"http://builder-a.tailnet:8080/"
"https://myorg.cachix.org"
];
extra-trusted-public-keys = [
"builder-a.example-1:BASE64…"
"myorg.cachix.org-1:BASE64…"
];
trusted-substituters = [
"http://builder-a.tailnet:8080/"
"https://myorg.cachix.org"
];
};
}
Multi-user daemons need trusted-substituters (or a trusted-users entry) so unprivileged nix build can use private HTTP caches—see Trusted users and substituters.
Substituter priority, fallback, and remote builders¶
Nix prefers substituters by priority (lower number = higher priority; often from each cache’s nix-cache-info). Keep cache.nixos.org as a fallback for paths the mesh never built; use extra-substituters so you do not accidentally drop the default official cache when adding private URLs.
With fallback = true (non-default), a failed substitute attempt can fall back to building locally after substituters are exhausted. Without it, some failures stop early—know your default on the channel.
builders-use-substitutes = true (on the machine that schedules remote builds) lets each remote builder pull its own substituters for build inputs instead of waiting for the coordinator to upload every path. Default is false. Remotes must independently trust the same private keys and reach the same URLs (overlay DNS, firewall). Details: Remote builders, Binary caches.
Signing and reachability are separate axes¶
Overlay membership (Tailscale, WireGuard, ZeroTier) gives hostnames and routes—it does not add a public key to trusted-public-keys or a URL to trusted-substituters. A peer on the VPN can still serve unsigned or wrongly signed .narinfo; Nix rejects those when require-sigs is true (default).
Configure stable names for substituter URLs: MagicDNS, Headscale DNS, or static WG AllowedIPs—see Overlay networks. Signing ceremony and key rotation: Signing and caches, Inter-machine trust.
GC vs durable cache¶
Live-store servers (Harmonia, nix-serve) expose whatever paths exist under /nix/store on that host. When nix-collect-garbage or auto-GC runs, substitutable paths 404 even though clients still reference the store hash. Treat edges as accelerators, not the sole copy of release artifacts.
Durable org caches (Attic, Cachix, S3 populated by nix copy) decouple substitution from any single builder’s lifecycle. CI pushes release closures to the hub; edges may mirror hot paths but should not be the only retention policy.
Operational habit: after important builds, push to hub; use Harmonia for same-day repeats; expect edge misses after GC and fall back to hub or cache.nixos.org.
Peer nix copy vs HTTP substituter¶
| Mechanism | Best for | Notes |
|---|---|---|
| HTTP substituter | Many clients, repeat pulls, CI/deploy hosts | Needs cache daemon or static HTTPS tree; signed .narinfo |
nix copy over ssh:// |
One-off closure to a host, deploy prep, no HTTP server | Uses store protocols; --substitute-on-destination can let the target fetch |
Hub push (cachix / attic / s3) |
Durable sharing across sites | Not peer-to-peer; central index |
HTTP scales for “always-on” consumption; nix copy fits “ship this closure now” without standing up substitution for every builder.
Failure modes¶
| Symptom / mistake | Likely cause | What to check |
|---|---|---|
| Private cache works from one laptop, not others | URL missing from trusted-substituters; user not in trusted-users |
System nix.settings.trusted-substituters on multi-user targets; Trusted users and substituters |
| VPN up but substituter timeout | Wrong hostname, firewall on overlay, or cache bound to LAN only | Ping/curl nix-cache-info over overlay IP; Overlay networks |
| Edge cache 404 for known-good path | GC removed path from builder; never pushed to hub | Rebuild or copy to hub; use durable cache for releases |
| Remote build slow despite nearby cache | builders-use-substitutes = false (default) |
Enable on coordinator; ensure builder trusts same substituters/keys |
| Signatures rejected fleet-wide | Key name mismatch, rotated secret without updating clients | Compare Sig: on .narinfo to trusted-public-keys; use extra-trusted-public-keys |
Only cache.nixos.org hits |
Missing private URLs/keys, or private cache priority higher than expected | Confirm extra-substituters + trusted-public-keys; check cache Priority / store-URL priority |
| Assumed “mesh VPN = trusted binaries” | Conflated reachability with binary trust | Add explicit keys; VPN does not replace Inter-machine trust |
| Harmonia works, Attic empty | No push pipeline from builders/CI | Wire CI with Nix or post-build nix copy / attic push |
Where to go next¶
- Chooser / symptom table: Binary caches (cheatsheet)
- Run one backend (sign, push, TLS): Binary cache hosting
Examples¶
Sanity-check a mesh substituter from any fleet member (overlay or LAN):
curl -sf "http://builder-a.tailnet:8080/nix-cache-info"
# Optional: fetch a known path's .narinfo (hash = store basename without /nix/store/):
# curl -sf "http://builder-a.tailnet:8080/<hash>.narinfo" | head
Ship a closure to a server without HTTP cache (peer copy):
Coordinator: remote builder uses its own caches (nix.conf fragment):
builders = ssh://remotebuild@builder-a.tailnet x86_64-linux /root/.ssh/id_remote 8 1 -
builders-use-substitutes = true
References¶
- nix.dev — Setting up an HTTP binary cache — NixOS nix-serve/Harmonia-style serving, signing, client trust
- Harmonia — live-store HTTP cache server
- Nix reference manual —
nix.conf—substituters,trusted-substituters,trusted-public-keys,fallback,builders-use-substitutes
See also¶
- Binary cache hosting — pick and operate one cache backend
- Binary caches — substitution model and settings
- Binary caches (cheatsheet) — consume / host / sign chooser
- Machine mesh — fleet trust axes (not topology detail)
- Inter-machine trust — binary authenticity vs VPN
- Signing and caches — keys,
require-sigs - Trusted users and substituters — multi-user allow lists
- Remote builders —
builders-use-substitutes, SSH builders - Overlay networks — stable hostnames for mesh URLs
- nix copy and bundles — peer closure copy over SSH
- Store protocols —
ssh://, HTTP store URIs - CI with Nix — push pipelines into org caches
- Clan and mesh — declarative peer fleets (reachability + inventory)
- Airgap and offline — non-HTTP offline paths (contrast)
- Self-healing config mesh — design-only gossip (not shipped)