fastpkgs is a Nix flake. It lets you name a nixpkgs package by its
attribute path (like legacyPackages.x86_64-linux.hello) and get back a
real, usable store path — without making Nix evaluate nixpkgs.
Here's the problem it solves. Normally, nix build nixpkgs#somePackage has
to evaluate that package's derivation first (and often a lot of its
dependencies too) before Nix even knows what to build or fetch. If all you
want is "give me the cached binary for this package," that evaluation step
is wasted work. fastpkgs skips it. Ahead of time, and offline, it checks
which packages are already cached and records their store paths. Then, when
you load the flake, it rebuilds lightweight stand-ins — "fake derivations" —
straight from that saved data.
Status: WIP — the interface (attribute names, output shapes) is still changing.
-
Scrape (
scripts/scrape.sh): This script runsnix-eval-jobsagainst a real nixpkgs flake (by defaultgithub:NixOS/nixpkgs), once for each of three systems:x86_64-linux,aarch64-linux, andaarch64-darwin. For every attribute underlegacyPackages.<system>, it notes the name, the output names, and whether that derivation's outputs are already sitting in the configured binary cache (checked with--check-cache-status). For each system, this produces:legacyPackages.<system>.json.raw— the full, unfiltered output fromnix-eval-jobs, including error messages for any attribute that fails to evaluate.legacyPackages.<system>.json.pre— the same data, trimmed down to just{name, attr, outputs, isCached}.legacyPackages.<system>.json— that trimmed data reshaped into a nested tree that matches the real package attribute layout. For example,pkgs.foo.barturns into{"foo":{"bar": {...}}}.legacyPackages.<system>.json.min— a shrunk-down version: anything not cached is dropped (shown asnull), and anything cached is reduced to just[outputs]. This is the only one of these files that matters past the scrape — see the next step for what happens to it. The rest are large, throwaway files created fresh each time the scrape runs.
-
Rebuild fake derivations (
flake.nix,mkFakeDerivation.nix,mapAttrsRecursiveCondFunc.nix): When the flake is evaluated, it reads the.minJSON snapshot — fetched via aflake = falseinput, not read from the repo tree (see step 3) — and walks through it recursively (mapAttrsRecursiveCondFunc.nixis a general-purpose helper for walking a tree conditionally) to rebuild a tree shaped just like nixpkgs'legacyPackages. Each cached entry at the bottom of that tree becomes a "fake derivation" (built bymkFakeDerivation.nix): basically an attrset whoseoutPathand other output attributes point at the real store paths recorded in the snapshot. It usesbuiltins.appendContextso Nix treats those paths as genuine and substitutable. It also has adrvPathattribute, but reading that just throws a helpful error, because there's no real.drvbehind a fake derivation — these only work for pulling an already-built copy, not for building from source. You can still reach the real, original nixpkgs package through.original. -
Publish and consume the snapshots as release assets (
scripts/upload-release.sh,flake.nix): The.minfiles aren't committed to the repo. Instead,scripts/upload-release.shuploads them as assets on a new GitHub Release, andflake.nixdeclares oneflake = falseinput per system (data-x86_64-linux,data-aarch64-linux,data-aarch64-darwin) pointing at that release's download URLs.flake.lockpins their exact content hash, just like any other flake input. To publish a fresh snapshot: runscripts/scrape.sh, thenscripts/upload-release.sh <tag>, update the URLs inflake.nixto the new tag, and runnix flake lock. -
Expose it as flake outputs (
flake.nix): The flake's outputs —packages,legacyPackages, andapps, one set per system — are all built from the.minsnapshots (via those data inputs) usinglib.packagesFromJSON.originalPackagesholds the real, unchanged nixpkgs input, in case you want to compare against it or fall back to it.packages.<system>.defaultbundles the three systems'.minsnapshots together with the flake's own files into one archive,fast.tar.xz— this is the file this repo is meant to publish as a release.
| Path | Purpose |
|---|---|
flake.nix |
The flake's outputs: the fake package/app sets, the data-input declarations, and the default release tarball. |
mkFakeDerivation.nix |
Builds one fake derivation attrset from one entry in the snapshot. |
mapAttrsRecursiveCondFunc.nix |
A general-purpose, conditional, recursive mapAttrs, used to walk the snapshot tree. |
default.nix |
A flake-compat shim, so the flake also works with older, non-flake Nix commands. |
scripts/scrape.sh |
Regenerates the legacyPackages.<system>.json* snapshot files by running nix-eval-jobs. |
scripts/upload-release.sh |
Publishes the .min snapshots as assets on a new GitHub Release. |
gcroots/ |
A directory nix-eval-jobs uses to hold GC roots while scraping. It's local only and never committed. |
Only a handful of files are tracked in git: flake.nix, flake.lock, the
two helper .nix files, and the two scripts. Nothing under
legacyPackages.*.json* is committed — those are either large scratch
output from running the scraper locally (.raw, .pre, the uncompressed
.json), or the .min snapshot, which is hosted as a GitHub Release asset
and pulled in through flake.lock instead (see "How it works" above).
gcroots/ is likewise local-only scratch output.
To build or fetch a package by its nixpkgs attribute path, pulling from the snapshot instead of evaluating nixpkgs:
nix build .#legacyPackages.x86_64-linux.helloTo look at the real, underlying nixpkgs derivation instead of the fake one:
nix eval .#legacyPackages.x86_64-linux.hello.originalTo build the release tarball (it bundles the .min snapshots for all three
systems):
nix build .#defaultTo regenerate the snapshots yourself (you'll need a configured binary cache to check against, and it takes a while, since it has to evaluate all of nixpkgs):
nix run .#local.x86_64-linux.scrape -- github:NixOS/nixpkgsRunning this replaces the legacyPackages.<system>.json* files for all
three systems in your current directory.
To publish a fresh snapshot after scraping (needs push access to the repo
and a gh login):
scripts/upload-release.sh data-YYYYMMDDThen update the URLs in flake.nix's data-* inputs to the new tag, and
run nix flake lock to pin them.
- A package only shows up with a real output if it happened to be cached in
the binary cache you had configured when you ran
scrape.sh. Anything that wasn't cached at scrape time just becomesnullin the snapshot, and is left out ofpackages/legacyPackagesentirely. - Fake derivations don't have a real
.drvfile behind them, so you can't build them from source — you can only fetch an already-built copy. Trying to read.drvPaththrows an error telling you to use.out(or whichever output name you need) or.originalinstead. - The snapshot is just a one-time check of cache status against one
specific nixpkgs revision (the one pinned in
flake.lock). As the binary cache changes over time, the snapshot becomes outdated, and you'll need to re-run the scrape. - For the best experience, use a Nix version that includes
NixOS/nix#15726. Without it,
commands like
nix build .#hello.outcan fail outright instead of falling back to another location (likelegacyPackages) when an attribute doesn't resolve the way Nix expects — which fake derivations, by their nature, can run into. - Publishing a new snapshot is a two-step, manual process: run
scripts/upload-release.sh, then hand-edit the tag baked intoflake.nix'sdata-*input URLs and re-runnix flake lock. Nothing automates that edit yet.