Mic92/nix-tarmac

Content-addressed cache plugin for Nix: fast tarball fetching and a persistent eval store

C++

26

58 commits

updated Oct 19, 2026

See the code

README

nix-tarmac

Nix plugin with a content-addressed cache that speeds up two things: fetching tarball flake inputs, and evaluation via a persistent tarmac:// eval store.

Tarball fetcher

Nix unpacks every tarball input (nixpkgs, flake-utils, anything pinned to a .tar.gz URL) into a bare Git repository (~/.cache/nix/tarball-cache-v2). Each file is SHA-1 hashed and zlib-compressed. For nixpkgs that's 50k+ files per revision. The cache is never garbage collected or repacked.

nix-tarmac replaces the builtin tarball fetcher with an LMDB-indexed append-only packfile of LZ4-compressed blobs, BLAKE3 hashes, and mmap-based reads. Results are byte-identical (same narHash), so lock files keep working.

Fetching a nixpkgs tarball (51 MB, ~53k files) with Nix 2.35 on a ZFS-backed server, average of 3 runs (bench/e2e_bench.sh):

builtinnix-tarmac
first fetch (unpack + hash)10.4 s2.4 s
firefox.drvPath eval, warm3.3 s2.4 s
re-import into /nix/store10.3 s8.8 s

The tarball comes from a local file, so the first-fetch row excludes network time. With a real download the unpack and hash work overlaps the transfer. Files shared between revisions are stored once. One nixpkgs revision takes 102 MiB, a second one 200 commits later adds 5 MiB (bench/size_bench.sh). Git needs 69 MiB for the same two revisions because zlib compresses better than LZ4.

Over the network it also matters which compression the tarball uses. nixpkgs channels are published as both xz- and zstd-compressed tarballs. zstd decompresses fast enough to fully overlap with the download, but the builtin fetcher's per-file import cost hides most of that. Fetching channels.nixos.org/nixpkgs-unstable/nixexprs.tar.* from a datacenter server, fresh download each run, average of 3 runs (bench/network_bench.sh). "First fetch" starts with a cold cache. "Re-fetch" hits the content cache and only re-downloads:

builtinnix-tarmac
tar.zst, first fetch10.2 s2.4 s
tar.zst, re-fetch1.6 s1.3 s
tar.xz, first fetch10.6 s3.0 s
tar.xz, re-fetch2.3 s2.3 s

With the plugin a cold fetch costs barely more than a re-fetch: ingest and a single hash pass overlap the download.

The cache

Everything lives in one content-addressed packfile in ~/.cache/nix/tarmac, shared by the fetcher and the eval store. Identical content is stored once, no matter which feature wrote it. Entries unused for 30 days are garbage collected automatically. If the cache is detected as corrupt it is wiped and everything regenerates on the next run.

Eval store

The plugin also registers a tarmac:// store scheme, meant as an --eval-store. It keeps evaluation outputs (derivations, writeText files) in the same packfile format instead of writing them to /nix/store with SQLite registration. Unlike dummy:// it is persistent, so restarted evaluator workers and repeated runs skip all writes.

Evaluating the NixOS release-small jobset with nix-eval-jobs and 4 workers on a 16-core server (bench/eval_bench.sh):

wall time
/nix/store207 s
tarmac://, cold164 s
tarmac://, warm140 s

The whole jobset output fits in a 112 MB store directory, and a warm run issues a single fsync.

$ nix-eval-jobs --workers 4 --eval-store tarmac:// \
    --gc-roots-dir /tmp/roots nixos/release-small.nix

Building works: Nix copies the derivations out to the build store on demand, so nix build --eval-store tarmac://... behaves like a normal build. The store itself never holds build outputs and signs nothing.

Without a path, tarmac:// uses the shared cache described above. Eval records survive as long as something reads them. Pass an explicit path like tarmac:///var/cache/evalstore for an isolated store with its own retention.

Installation

On NixOS, add the flake input and import the module:

inputs.nix-tarmac.url = "github:Mic92/nix-tarmac";
# in your configuration:
imports = [ inputs.nix-tarmac.nixosModules.default ];

For nix-darwin use inputs.nix-tarmac.darwinModules.default.

For a manual setup:

nix build github:Mic92/nix-tarmac

Add to nix.conf:

plugin-files = /path/to/result/lib/nix/plugins/nix-tarmac-loader.so

or pass --plugin-files on the command line.

Supported Nix versions

Nix 2.34+. The plugin is built against all versions provided by the pinned nixpkgs of the flake. The loader picks the build whose libnixstore SONAME is already loaded in the process and otherwise skips with a warning.

Development

nix develop -c meson setup build
nix develop -c ninja -C build
nix develop -c meson test -C build
./tests/e2e.sh        # fetch with real Nix, compare narHash against builtin
./bench/e2e_bench.sh  # builtin vs plugin benchmark

License

MIT

Mic92/nix-tarmac

Content-addressed cache plugin for Nix: fast tarball fetching and a persistent eval store

C++

26

58 commits

updated Oct 19, 2026

See the code

README

nix-tarmac

Nix plugin with a content-addressed cache that speeds up two things: fetching tarball flake inputs, and evaluation via a persistent tarmac:// eval store.

Tarball fetcher

Nix unpacks every tarball input (nixpkgs, flake-utils, anything pinned to a .tar.gz URL) into a bare Git repository (~/.cache/nix/tarball-cache-v2). Each file is SHA-1 hashed and zlib-compressed. For nixpkgs that's 50k+ files per revision. The cache is never garbage collected or repacked.

nix-tarmac replaces the builtin tarball fetcher with an LMDB-indexed append-only packfile of LZ4-compressed blobs, BLAKE3 hashes, and mmap-based reads. Results are byte-identical (same narHash), so lock files keep working.

Fetching a nixpkgs tarball (51 MB, ~53k files) with Nix 2.35 on a ZFS-backed server, average of 3 runs (bench/e2e_bench.sh):

builtinnix-tarmac
first fetch (unpack + hash)10.4 s2.4 s
firefox.drvPath eval, warm3.3 s2.4 s
re-import into /nix/store10.3 s8.8 s

The tarball comes from a local file, so the first-fetch row excludes network time. With a real download the unpack and hash work overlaps the transfer. Files shared between revisions are stored once. One nixpkgs revision takes 102 MiB, a second one 200 commits later adds 5 MiB (bench/size_bench.sh). Git needs 69 MiB for the same two revisions because zlib compresses better than LZ4.

Over the network it also matters which compression the tarball uses. nixpkgs channels are published as both xz- and zstd-compressed tarballs. zstd decompresses fast enough to fully overlap with the download, but the builtin fetcher's per-file import cost hides most of that. Fetching channels.nixos.org/nixpkgs-unstable/nixexprs.tar.* from a datacenter server, fresh download each run, average of 3 runs (bench/network_bench.sh). "First fetch" starts with a cold cache. "Re-fetch" hits the content cache and only re-downloads:

builtinnix-tarmac
tar.zst, first fetch10.2 s2.4 s
tar.zst, re-fetch1.6 s1.3 s
tar.xz, first fetch10.6 s3.0 s
tar.xz, re-fetch2.3 s2.3 s

With the plugin a cold fetch costs barely more than a re-fetch: ingest and a single hash pass overlap the download.

The cache

Everything lives in one content-addressed packfile in ~/.cache/nix/tarmac, shared by the fetcher and the eval store. Identical content is stored once, no matter which feature wrote it. Entries unused for 30 days are garbage collected automatically. If the cache is detected as corrupt it is wiped and everything regenerates on the next run.

Eval store

The plugin also registers a tarmac:// store scheme, meant as an --eval-store. It keeps evaluation outputs (derivations, writeText files) in the same packfile format instead of writing them to /nix/store with SQLite registration. Unlike dummy:// it is persistent, so restarted evaluator workers and repeated runs skip all writes.

Evaluating the NixOS release-small jobset with nix-eval-jobs and 4 workers on a 16-core server (bench/eval_bench.sh):

wall time
/nix/store207 s
tarmac://, cold164 s
tarmac://, warm140 s

The whole jobset output fits in a 112 MB store directory, and a warm run issues a single fsync.

$ nix-eval-jobs --workers 4 --eval-store tarmac:// \
    --gc-roots-dir /tmp/roots nixos/release-small.nix

Building works: Nix copies the derivations out to the build store on demand, so nix build --eval-store tarmac://... behaves like a normal build. The store itself never holds build outputs and signs nothing.

Without a path, tarmac:// uses the shared cache described above. Eval records survive as long as something reads them. Pass an explicit path like tarmac:///var/cache/evalstore for an isolated store with its own retention.

Installation

On NixOS, add the flake input and import the module:

inputs.nix-tarmac.url = "github:Mic92/nix-tarmac";
# in your configuration:
imports = [ inputs.nix-tarmac.nixosModules.default ];

For nix-darwin use inputs.nix-tarmac.darwinModules.default.

For a manual setup:

nix build github:Mic92/nix-tarmac

Add to nix.conf:

plugin-files = /path/to/result/lib/nix/plugins/nix-tarmac-loader.so

or pass --plugin-files on the command line.

Supported Nix versions

Nix 2.34+. The plugin is built against all versions provided by the pinned nixpkgs of the flake. The loader picks the build whose libnixstore SONAME is already loaded in the process and otherwise skips with a warning.

Development

nix develop -c meson setup build
nix develop -c ninja -C build
nix develop -c meson test -C build
./tests/e2e.sh        # fetch with real Nix, compare narHash against builtin
./bench/e2e_bench.sh  # builtin vs plugin benchmark

License

MIT

Significant stargazers

Guilhem Saurel

159 followers · starred Aug 2026

Ilan Joselevich

94 followers · starred Aug 2026

Quinn Edenfield

11 followers · starred Sep 2026

Archit Gupta

22 followers · starred Sep 2026