I use Nix Flakes to define my NixOS and nix-darwin system configurations, including home-manager configs for my dotfiles.
One of the cool things about flakes is the "flake registry", which lets you refer to popular flake sources by a short name. For example, you can run nix shell nixpkgs#vim
to open a new shell with vim
in the path, which uses the registered short name nixpkgs
instead of the full flake URL github:NixOS/nixpkgs/nixpkgs-unstable
.
This is cool, but because the nixpkgs
flake tracks nixpkgs-unstable
, it gets updated super frequently and is pretty much always ahead of the revision I used to build the system config. This means that every time I run nix shell
, it needs to download a ~30 MB tarball of the current nixpkgs revision, and depending on what I'm installing it may need to download a bunch of updated dependencies for things that have had version bumps since I last updated my system.
It turns out that you can add your own entries to the flake registry, and "user" and "system" entries take precedence over the global default entries. So if you register your own nixpkgs
flake, it will use whatever URL you registered when running nix shell nixpkgs#whatever
.
New nixy approach
Thanks to a kind soul on Mastodon, I learned that there's a NixOS config option to set the flake registry entry.
Assuming that you have a flake input named nixpkgs
in scope, all you need to do is set nix.registry.nixpkgs.flake = nixpkgs;
. After building the config, you can run nix registry list
, and you should see something like this:
system flake:nixpkgs path:/nix/store/vj10h6bzy9fldd1a4p917h0kjx7gr4sz-source?lastModified=1679172431&narHash=sha256-XEh5gIt5otaUbEAPUY5DILUTyWe1goAyeqQtmwaFPyI=&rev=1603d11595a232205f03d46e635d919d1e1ec5b9
global flake:agda github:agda/agda
etc...
Running nix shell nixpkgs#whatever
should now be much snappier, since you'll already have the nixpkgs derivation and a bunch of system dependencies in your nix store.
Old janky approach
Below is the hacky imperative approach I took initially, preserved for posterity or whatever :)
I added a little script to pin the nixpkgs revision in my local flake registry to the revision used to build my system config. The script just runs this commmand:
nix registry add nixpkgs "github:NixOS/nixpkgs/$(jq -r '.nodes.nixpkgs.locked.rev' flake.lock)"
This uses jq
to parse out the revision of nixpkgs from my flake.lock
file, and adds a registry entry for nixpkgs
with the URL set to that specific revision. Note that the command assumes that you're running it in the directory containing the flake.lock
file.
I recently started managing my "housekeeping" tasks with just
, a command runner that supports dependencies between build tasks. This makes it easy to run the pin-nixpkgs
task after another task completes.
The relevant bit of my justfile
looks like this:
# store the hostname of the current machine in a variable, removing anything after the first '.'
hostname := `hostname | cut -d '.' -f 1`
# Build the NixOS configuration and switch to it. Also pins nixpkgs to rev in flake.lock
[linux]
switch target_host=hostname: && pin-nixpkgs
sudo nixos-rebuild switch --flake .#
# Pin the revision of nixpkgs in the local flake registry to the rev from flake.lock
pin-nixpkgs:
nix registry add nixpkgs "github:NixOS/nixpkgs/$(jq -r '.nodes.nixpkgs.locked.rev' flake.lock)"
So now when I run just switch
to switch to a new system config, it will automatically update my local flake registry to point to the nixpkgs revision that was used to build the config.
Now when I run nix shell nixpkgs#thing
, the nix store already has the derivation of nixpkgs itself, so there's no need to download a big tarball, and there's a good chance that I've already got most of the dependencies for thing
in the store also. This makes the whole thing much snappier and cuts down on a lot of wasted disk space due to duplicate dependencies.