Siggsy

Melting flakes

2026/06/14 [nix] [flakes]

I use nix in every git repository to document the contex of the project. I lost count at the amount of times it saved me by removing the “bootstraping” phase of revisiting an old project. Unfortunatelly, because I used flakes, this came at a cost: every project has its own version of nixpkgs, and in practice, because for now nix uses input-addressed derivations, you end up with multiple copies of “the same” package. At some point I had 6 instances of nixpkgs#ghc, each taking up

console (docs)

$ nix path-info --recursive --size --closure-size --human-readable nixpkgs#ghc

# Path                                                  size     closure-size
...
/nix/store/k493jzz83044mqayvlb6247l35780kxy-ghc-9.10.3  1.8G     3.0G

Not to mention nixpkgs#haskell-language-server

console

$ nix path-info --recursive --size --closure-size --human-readable nixpkgs#haskell-language-server

# Path                                                                        size     closure-size
...
/nix/store/ymvsfvbhw5kvw639cv8k447v1v6jbsc4-haskell-language-server-2.13.0.0  110.9M   7.5G
/nix/store/ps0yfb2bvv33srn6fcnwfiw8rg7w5g7s-haskell-language-server-2.13.0.0  896.0    7.5G

Strictly speaking, I have no need for multiple ghc/hls versions. Pre-nix I just used the most recent without any major issues. Most software cannot assume everyone has nix-adjacent package managers, so most of the time there is some leeway when it comes to version pinning. Lets abuse this a bit

I decided that my build times and disk space are more important than “first try” deterministic builds, so I implemented a fallback strategy:

  1. Pin system’s nixpkgs in flake registry
  2. Use the registry to override flake inputs automatically and have a simple toggle for switching back to flake.lock
  3. Provide a simple way to update flake.lock to point to the same nixpkgs revision as the current system.

I find this very similar to how most flake inputs use inputs.nixpkgs.follows = "nixpkgs", but instead, this is done automatically!

If you use flakes for your system configuration, chances are, you already have nixpkgs pinned to the revision you used for building the system (see nixos option).

Spoiler alert! This will not suffice

Mostly, I only want this on devShells since that is my main pain point. I use nix-direnv to automatically activate development shells. The result is a .envrc file in project repository:

.envrc

use flake

We can pass arguments to this line. More specifically, we want --override-input option:

.envrc

use flake . --override-input nixpkgs flake:nixpkgs

This works, but we don’t want to force this impurity on other people! So what I ended up doing is overriding use_flake function in my direnvrc file like so:

$XDG_CONFIG_HOME/direnv/direnvrc

# store original version of use_flake
eval "_$(declare -f use_flake)"

use_flake() {
  _nix_direnv_warning "Using system nixpkgs!"

  # Inject --override-input argument on every `use flake` invocation
  _use_flake "${@:-.}" --override-input nixpkgs flake:nixpkgs
}

If I try to cd into flake enabled repository with .envrc, I automatically use my systems nixpkgs. But, since this might backfire, I added a kill-switch with variable NIX_DIRENV__FEELING_SPICY and moved everything to my configuration:

modules/hjem/zsh.nix

zsh

export NIX_DIRENV__VARS="$XDG_CONFIG_HOME/direnv/vars"

nix-spicy-off() {
  echo "export NIX_DIRENV__FEELING_SPICY=0" > "$XDG_CONFIG_HOME/direnv/vars"
}

nix-spicy-on() {
  echo "export NIX_DIRENV__FEELING_SPICY=1" > "$XDG_CONFIG_HOME/direnv/vars"
}
if ! [ -f "$NIX_DIRENV__VARS" ]; then
  nix-spicy-on
fi

direnvrc

eval "_$(declare -f use_flake)"
use_flake() {
  source "$XDG_CONFIG_HOME/direnv/vars"
  watch_file "$XDG_CONFIG_HOME/direnv/vars"
  if [[ ''${NIX_DIRENV__FEELING_SPICY:-0} -eq 1 ]]; then
    _nix_direnv_warning "SPICY! Using system nixpkgs"
    _use_flake "''${@:-.}" --override-input nixpkgs "flake:nixpkgs"
  else
    _use_flake "$@"
  fi
}

If I now try to run

console

nix flake update nixpkgs \         # We only want to update nixpkgs
  --override-input nixpkgs flake:nixpkgs \
  --output-lock-file "flake.lock"  # --override-input implies --no-write-lock-file

the flake.lock is now updated, but with input type that only really works on my machine: type: path. To fix this, I configured system flake repository manually:

modules/the-shire/core/nix.nix

registry = {
  self.flake = inputs.self;
  nixpkgs.to = {
    owner = "NixOS";
    repo = "nixpkgs";
    rev = inputs.nixpkgs.rev;
    type = "github";
  };
};

By doing so, flake:nixpkgs repository now resolves to github:NixOS/nixpkgs/<rev> when using the update command. Problem solved! I also added an alias for this command to my zshrc:

modules/hjem/zsh.nix

nix-spicy-lock() {
  nix flake update nixpkgs --override-input nixpkgs flake:nixpkgs --output-lock-file flake.lock "$@"
}