How to enable per-flake binary caches with nixConfig

Monday, the 27th of July 2026

For context: Nix is a programming language that lets you construct derivations from expressions, ending up in reproducible package builds. This means every package is technically built from source.

However, Nix identifies derivation outputs by a hash derived from their inputs, so for the same input, a hash can be looked up on a binary cache and downloaded instead of being built locally. This is, in essence, how NixOS works (+ symlinking).

Also, you can configure additional binary caches to the default of <cache.nixos.org> - this is useful in cases where Hydra (the nixpkgs build farm) does not populate the derivation you want to use in the default cache, like when you are using an external flake.

Introduction

I have five binary caches configured for my machines on this flake. These are:

(You might also see <pi.cachix.org> - left over from when I was using https://github.com/lukasl-dev/pi.nix)

These caches are configured only for the machines that need the packages from those caches - this was done primarily because cache lookup takes a while during building. The five (really six) caches I had were adding 5 to 10 seconds per Nix build - usually the results are cached, but in cases where I'm editing Nix code and need to quickly iterate with builds, this was getting tiresome.

I took another stab at fixing the issue other than my earlier misstep, and decided to try and reframe the problem - I would only ever be building the tools flake from the tools flake directory, and so needed those binary caches only while building the project, NOT the machine; it should have been project-local instead of machine-specific.

With that mental model in mind, I (or rather the agent) began hunting for options.

Project-local Configuration

I detest installing npm or Python system wide - I view this tooling as tooling and strongly prefer them to be used only for the projects they need to be used for. Nix is great for this - I can just declare a development shell with the exact dependencies I need and get them without polluting the rest of my system.

There's usually a couple ways to do this, other than pure Nix. First paradigm I considered for this task was CLI flags or env vars - there's almost always a way to configure something at runtime, and Nix config is no different. I was able to override the binary caches in my Nix settings at runtime by using them. However, this would mean all my commands and scripts would require duplicating my binary caches across every call site.

I remembered the nixConfig flake attribute from NotAShelf's blog post decrying it. While I obviously don't disagree and have stuck mostly to this advice since I have read it, I think I found the perfect use case here, even though it's not exactly the easiest tip to find for Nix flakes (a common problem with the ecosystem).

Declaring nixConfig in your flake like so:

{
  nixConfig = {
    extra-substituters = [
      "https://rrvsh.cachix.org"
    ];
    extra-trusted-public-keys = [
      "rrvsh.cachix.org-1:pkljA9d1Q88P7GB/bUHB5CBJacyCUp/m4zXu8IzI4a4="
    ];
  };
  # inputs, outputs, etc.
}

means that these settings will come into effect only when you are evaluating the flake that they are declared in.

The above code snippet is representative of the rest of the work - the other thing I had to be aware of was adding the --accept-flake-config flag to all my Nix commands, otherwise it would result in an interactive prompt to allow using the nixConfig.

You can see the full implementation at https://github.com/rrvsh/tools/commit/0e72e35a2f7065266798e257d3126c136736f450.

WARNING

While this is a perfectly safe use of nixConfig for setting a per-flake binary cache, it is NOT recommended to accept any flake config or use any untrusted binary caches. It's a direct way to execute attacker-controlled code on your system. Never set nix.settings.accept-flake-config to true.

Conclusion

Nix builds faster = Rafiq happier. See you tomorrow