Nix flakes landed in Nix (as an experimental option) with the 2.4 update a few months ago, and represents a substantial change in how projects using Nix can standardize their outputs, inputs and ensure reproducibility.

Nevertheless, this hermeticity comes with some downsides, especially when it comes to bandwidth, disk space and CPU usage. The reason for this is that Nixpkgs occasionally merges PRs that “rebuild the world,” for instance, staging next cycles, or urgent updates to OpenSSL and other critical packages (which cause a rebuild in say, Vim because it affects the git derivation used to fetch it.) Thus when you want to use a package that depends on an older or newer commit of Nixpkgs and some mass-rebuild PR landed in the intervening time, you’ll be faced with mass downloads of almost every dependency that probably did not change in terms of build contents, but whose build environments differed enough that Nix considers them different.

After over a year of using flakes in practice, I’ve noticed certain ways in which I overcome these inconveniences, which I’ll elaborate below.

Note that this isn’t to say the hacks are without drawbacks. I’ll make it apparent in each hack what the benefits and drawbacks are.

Avoiding mass rebuilds with input overriding

Scenario: want to avoid a mass rebuild when trying to build an older project

Fix: override the nixpkgs input with a fixed reference

Drawbacks: might lose reproducibility, but it’s fine if the changes weren’t major between the pinned commit and overriden one

Around a year ago, I started pinning my Nixpkgs registry. This lets me keep my flake reference to nixpkgs consistent across my systems (as opposed to using channels.) This is good when running commands with nix run so that instead of using the most up-to-date commit of Nixpkgs, it uses the pinned one from my system instead.

I then deploy my server configuration using a simple tool. So when I want to update my server I run the following command

$ nix run github:winterqt/deploy -- siraben-land
[0/81 built, 1/0/14 copied (3.7/924.4 MiB), 1.0/161.4 MiB DL] fetching llvm-13.0.0-lib from https://cache.nixos.org

Huh? What does LLVM have to do with using the deployment tool? Why are there 81 rebuilds? Such scenarios are commonplace in my experience, due to the gap between what the package set Nixpkgs to and where Nixpkgs is currently. The solution is thus to override the flake input altogether. Many flake commands accept the --override-input flag that takes two arguments; a path to override and the new flake reference to override it with. In the following command I’m overriding the input called nixpkgs with nixpkgs from my registry.

$ nix run github:winterqt/deploy --override-input nixpkgs nixpkgs --siraben-land
warning: not writing modified lock file of flake 'github:winterqt/deploy':
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/5c37ad87222cfc1ec36d6cd1364514a9efc2f7f2' (2021-12-25)
  → 'github:NixOS/nixpkgs/a529f0c125a78343b145a8eb2b915b0295e4f459' (2022-01-31)

Notice that the reference to Nixpkgs went forward in time by a month. In this case, I avoided rebuilds and the server config deployed without any problems. Of course, the natural downside to this is that you might lose reproducibility if there were major changes between the two commits. In most non-critical cases, the resources and time saved are worth the risk.

Importing a path when using a Nix command

Scenario: when working with a pre-flakes project, we want to be able to build a derivation specified with a given expression

Fix: pass the --impure flag

Drawbacks: could lead to larger closure sizes

In the world of Nix flakes, impure references to things as such as the current directory are outright banned. For instance, suppose we’re on aarch64-darwin and we want to build GNU Hello for x86_64-darwin, before flakes we might run

$ nix-build -E 'with (import ./. {system="x86_64-darwin";}); hello'

So the Nix command equivalent would be

$ nix build --expr 'with (import ./. {system="x86_64-darwin";}); hello'
error: access to absolute path '/Users/siraben/Git/forks/nixpkgs' is forbidden in pure eval mode (use '--impure' to override)
(use '--show-trace' to show detailed location information)

As the error message suggests, we have to pass --impure to it, resulting in

$ nix build --impure --expr 'with (import ./. {system="x86_64-darwin";}); hello'

which succeeds as usual. Note that this might lead to increased closure sizes because a path reference results in the entire directory of the package being copied to the Nix store.

Building unfree or unsupported packages

Scenario: want to build unfree packages or packages that are marked as broken for the current platform

Fix: pass --impure to nix build

Drawbacks: mostly harmless™

As an example, the math-comp book has a flake.nix file defined. So we might be tempted to try to build the book with flakes:

$ nix build github:math-comp/mcb
error: Package ‘math-comp-book’ in /nix/store/z5d23mcmv3va30nfkg1q40iz62xyi57a-source/flake.nix:36 has an unfree license (‘cc-by-nc-40’), refusing to evaluate.

       a) To temporarily allow unfree packages, you can use an environment variable
          for a single invocation of the nix tools.

            $ export NIXPKGS_ALLOW_UNFREE=1

       b) For `nixos-rebuild` you can set
         { nixpkgs.config.allowUnfree = true; }
       in configuration.nix to override this.

       Alternatively you can configure a predicate to allow specific packages:
         { nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
             "math-comp-book"
           ];
         }

       c) For `nix-env`, `nix-build`, `nix-shell` or any other Nix command you can add
         { allowUnfree = true; }
       to ~/.config/nixpkgs/config.nix.
(use '--show-trace' to show detailed location information)

Unfortunately in this case it’s not clear what the fix is. Even if you set that environment variable, you still get the same error message. Again harkening back to the philosophy of Nix flakes, querying environment variables is considered impure. The fix is to again pass the --impure flag while setting the environment variable at the same time.

$ NIXPKGS_ALLOW_UNFREE=1 nix build --impure github:math-comp/mcb && tree ./result
./result
└── share
    └── book.pdf

There really isn’t any downside to this method, as far as I know. Unless environment variables you set in your shell also affect other aspects of the build, everything should be the same, and you’ll be able to run and build packages that were marked as broken or unfree previously.

Final thoughts

Nix flakes isn’t to blame for these workarounds arising per se. In a sense, Nix becomes too pure to the extent where resources are being used when they don’t strictly need to, especially for non-critical use cases. In the future, features such as a content-addressed store may help with issues such as mass rebuilds, where package hashes are determined by their build contents and not their input derivations.