org-howto/articles/2026/06/nix-binary-cache.org
Roland Conybeare 40fbc6eaaa
All checks were successful
Deploy / publish (push) Successful in 21s
+ nix binary cache article
2026-06-21 17:57:36 -04:00

6.2 KiB

[20jun2026] nix binary cache

#

#

#

#

#

#

Problem

I run nix with a non-standard nix store location (see https://github.com/Rconybea/nix-from-scratch). This means that all packages build from source, and the default substituter cache.nixos.org is not usable.

One problem that arises is that internet endpoints aren't always permanent; over time URLs change or disappear.

Nix' solution for this is a binary cache. This note describes setting one up.

Typical purpose is to record nix derivation outputs separately from the nix store. Although we'll get that as a byproduct, our goal is actuall to store derivation inputs, i.e. so-called fixed-output source derivations.

Setup

Create Cache Directory and Key

I have a network attached storage device with multiple TB of unused capacity.

We'll choose a location for holding nix store artifacts. As of June 2026, my nix store contains about 190GB.

We will make the binary cache available at /mnt/nas1/share2/nix-cache.

  nix-store --generate-binary-cache-key nas1-=roland-1 \
  		  ~/.config/nix/nas1-cache.secret \
  		  ~/.config/nix/nas1-cache.public
  mkdir /mnt/nas1/share2/nix-cache
  cp ~/.config/nix/nas1-cache.public /mnt/nas1/share2/nix-cache/

Copy Nix Store

  nix copy --all \
  	--to 'file:///mnt/nas1/share2/nix-cache?secret-key=/path/to/.config/nix/nas1-cache.secret&compression=zstd'

Register Store as Substituter

in ~/.confrig/ni9x/nix.confg:

  substituters = file:///mnt/nas1/share2/nix-cache
  trusted-public-keys = nas1-roland-1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=

Maintenance

Added some scripts to nix-from-scratch:

Manual script

# nix-from-scratch/binary-cache/nix-cache-sync.sh

#!/usr/bin/env bash
#
# nix-cache-sync.sh — push the local nix store to the NAS binary cache.
#
# This workstation runs a from-source nix with a relocated store
# (/home/roland/nixroot/nix/store), so it can't use cache.nixos.org and builds
# everything from source. The NAS binary cache is our durable, self-controlled
# archive of sources + build outputs, and the only configured substituter
# (see ~/.config/nix/nix.conf).
#
# This copies every currently-valid store path into the cache. It is incremental
# (paths already present are skipped), so it's cheap to re-run — use it as the
# after-a-build top-up, or let the systemd timer (../systemd/) run it nightly.
#
# Usage:
#   nix-cache-sync.sh            # sweep the whole store (--all)
#   nix-cache-sync.sh PATH...    # copy only the given store paths (+ closures)
#
# Override defaults via env:
#   NIX_CACHE_MOUNT     NAS mount point that must be mounted (default /mnt/nas1/share2)
#   NIX_CACHE_DIR       cache directory               (default $NIX_CACHE_MOUNT/nix-cache)
#   NIX_CACHE_SECRET    signing key  (default ~/.config/nix/nas1-cache.secret)
#   NIX_CACHE_COMPRESSION  zstd|xz|none               (default zstd)
#   NIX                 nix binary   (default /home/roland/nixroot/bin/nix)

set -euo pipefail

MOUNT="${NIX_CACHE_MOUNT:-/mnt/nas1/share2}"
CACHE_DIR="${NIX_CACHE_DIR:-${MOUNT}/nix-cache}"
SECRET="${NIX_CACHE_SECRET:-${HOME}/.config/nix/nas1-cache.secret}"
COMPRESSION="${NIX_CACHE_COMPRESSION:-zstd}"
NIX="${NIX:-/home/roland/nixroot/bin/nix}"

die() { echo "nix-cache-sync: $*" >&2; exit 1; }

[ -x "$NIX" ] || die "nix binary not found/executable: $NIX"
mountpoint -q "$MOUNT" || die "NAS not mounted at $MOUNT — refusing to sync"
[ -f "$SECRET" ] || die "signing key not found: $SECRET"
mkdir -p "$CACHE_DIR"

to="file://${CACHE_DIR}?secret-key=${SECRET}&compression=${COMPRESSION}"

if [ "$#" -gt 0 ]; then
  echo "nix-cache-sync: copying ${#} path(s) -> ${CACHE_DIR}"
  exec "$NIX" copy --to "$to" "$@"
else
  echo "nix-cache-sync: sweeping whole store -> ${CACHE_DIR} (incremental)"
  exec "$NIX" copy --all --to "$to"
fi

Systemd service to refresh cache

# nix-from-scratch/systemd/nix-cache-sync.service

# Sync the local (relocated) nix store to the NAS binary cache.
#
# Reference unit — NOT auto-installed. See README.md in this directory for
# manual install. Companion: nix-cache-sync.timer.
#
# Captures every currently-valid store path (sources + build outputs) into the
# NAS cache. Incremental: paths already in the cache are skipped, so it is cheap
# to run repeatedly.

[Unit]
Description=Sync nix store to NAS binary cache (source + build-output archive)

[Service]
Type=oneshot
# Skip (not fail) the run if the NAS is not mounted.
ExecCondition=/usr/bin/mountpoint -q /mnt/nas1/share2
ExecStart=/home/roland/nixroot/bin/nix copy --all --to "file:///mnt/nas1/share2/nix-cache?secret-key=%h/.config/nix/nas1-cache.secret&compression=zstd"
# Be gentle: this competes with interactive builds for CPU and NFS I/O.
Nice=10
IOSchedulingClass=idle

Plus nightly trigger

# nix-from-scratch/systemd/nix-cache-sync.timer
 
# Nightly trigger for nix-cache-sync.service.
#
# Reference unit — NOT auto-installed. See README.md for manual install.

[Unit]
Description=Nightly sync of nix store to NAS binary cache

[Timer]
OnCalendar=daily
# If the machine was off at the scheduled time, run once after next boot.
Persistent=true
# Avoid a thundering-herd exactly at midnight.
RandomizedDelaySec=15min

[Install]
WantedBy=timers.target