Positions on Nix

These are my positions on recurring questions in the Nix ecosystem. Each entry states a clear position, explains the reasoning, and gives you alternatives if the answer is "don't do that." I've shipped NixOS to production workstations and admin terminals at national scale. These answers reflect what I've learned doing it.

They are opinionated. They are not tutorials. They assume you already know the basics of Nix.

Last updated: August 2026. See also the /now page for what I'm currently shipping and the buyer FAQ for commercial questions.


Nix the language and tooling

NixOS the operating system

The ecosystem


Nix the language and tooling

Q: Should I use flakes?

Position: Knowing when to use flakes and when not to is a sign of Nix maturity and ability to understand what it is that you are working with. If you care about understanding Nix on the longer term, try to avoid flakes. If you want to get things done fast and benefit from an ecosystem of tips and copy-pastable commands, flakes are acceptable.

Why: Flakes are an unfinished experiment that coupled too many concerns at once: dependency pinning, pure evaluation, a registry, a new CLI surface, and a schema for outputs: without solving any of them completely. People still run into many bugs with flakes up to today, and fixing them is not always easy. The result is a feature that looks polished from the outside and creates sharp edges the moment you deviate from the happy path.

That said, the core idea behind flakes: reproducible evaluation with pinned inputs: is desirable. The UX that flakes introduced while being integrated inside Nix core is also something that has contributed to help users navigate the Nix ecosystem, and it's a good idea. None of the dependency management tools came close to this UX. It's understandable why users reach for flakes naturally.

The core idea is correct. But the implementation conflates two separate problems: where do my dependencies come from and how do I evaluate Nix code. You can solve the first with npins, niv, lon, or a hand-managed JSON lock file. You can solve the second with --restrict-eval and explicit NIX_PATH management. Neither requires flakes.

What to do instead:

  • npins: simple, does one thing, works today
  • niv: legacy, no longer actively maintained. Requires Haskell, which means pulling GHC into your build graph; npins (Rust) avoids this.
  • lon: another option in this space
  • Built-in fetchers with a hand-managed JSON file: zero dependencies, fully transparent. A conceptual idea: it's wrong for any project above one dependency, but it's there.
  • --restrict-eval for restricting evaluation (Lix docs).

What you lose: There's no standardization of inputs/outputs: you make your own. There's also no standard CLI to manipulate these objects except for npins, niv, and lon themselves.

Further reading:

Q: How do I pin dependencies without flakes?

Position: Use npins for new projects. Use niv if you're already on it. Use lon if it fits your workflow. A hand-managed JSON file is a conceptual idea, but it's wrong for any project above one dependency.

Why: Each approach makes a different trade-off between convenience and transparency.

npins is the simplest: you run npins init, then npins add <name> <url> or npins update. It produces a npins/ directory with a sources.json and per-source metadata. Your Nix code imports it via import npins { inherit lib; }. It's maintained, it's fast, it doesn't try to do anything else. It's written in Rust, so it avoids pulling GHC into your build graph.

niv works similarly but is older and has accumulated cruft. It requires Haskell, which means your build graph includes GHC. If you're already using it, there's no urgent reason to migrate.

lon is another option in this space.

A hand-managed JSON file is the most transparent approach: you write sources.json by hand or with a small script, and your Nix code reads it with builtins.fromJSON (builtins.readFile ./sources.json). There's no tool to install, no magic, and you see every change in version control. The cost is that updates require manual URL/tag/hash changes. For small projects, this is wrong for any project above one dependency.

All three approaches give you reproducibility without the architectural baggage of flakes. Pick the one whose operational cost matches your project's complexity.

What you lose: There's no standardization of inputs/outputs: you make your own. There's also no standard CLI to manipulate these objects except for npins, niv, and lon themselves.

Q: Why is Nix rebuilding everything?

Position: It's likely one of six things. Here's the diagnostic tree.

Why: Nix rebuilds derivations when their inputs change. If you're seeing a world rebuild, something low in your dependency graph changed. The question is what and why you didn't expect it.

  1. NIX_PATH mismatch. Your shell and your system configuration are pulling from different nixpkgs revisions. One has cached binaries, the other doesn't. This is the most common cause. Force synchronization with nix.nixPath = [ "nixpkgs=${yourPinnedNixpkgs}" ] in your NixOS config. Be aware that existing shells won't pick up the change until you open new ones.

  2. You're offline or can't reach cache.nixos.org. Nix falls back to building from source when the binary cache is unavailable. Check your network and nix show-config | grep substituters.

  3. You changed a low-level dependency. If you override openssl, systemd, glibc, or stdenv, everything that transitively depends on them will rebuild. This is almost certainly what you asked for, even if you didn't realize the blast radius.

  4. Store corruption. Rare but real. If your Nix store or your nixpkgs source tree is corrupted, hashes won't match and everything rebuilds. Run nix store verify to check.

  5. A Nix evaluation bug. If you've eliminated the above, you may be hitting a bug. Compare the derivation with nix-diff to understand what input changed unexpectedly.

  6. cache.nixos.org is having issues. Check the status. It happens.

The diagnostic order matters: start with #1, then #2, then #3. By #4 you're in debugging territory, and #5 and #6 are last resorts.

Q: Can Nix do incremental builds?

Position: Yes, but the technology to enable it is only experimental at this point. See Zilch and the NLnet-funded R&D project that made incrementalism with Nix-ish happen over C++, Go, and Rust.

Why: Nix's build model is functional: each derivation is a pure function from inputs to outputs. Within a single derivation, if anything changes, the entire derivation rebuilds from scratch. This is by design: it's what makes builds reproducible. You trade incremental compilation for the guarantee that the same inputs always produce the same outputs.

Incrementalism at the derivation level vs. the compilation unit level. These are two different scales. Nix already gives you derivation-level incrementalism: if you change one package, only that package and its dependents rebuild. If the binary cache has your package, nothing builds at all: Nix just downloads it. For most workflows, this is sufficient. Compilation-unit-level incrementalism (recompiling only the .o files that changed within a single package) is a harder problem and lives at a different layer.

What Nixpkgs could do better. There are patterns that would make derivation-level incrementalism more effective without breaking the purity model:

  • Relinkage. When a library updates, most downstream packages only need to be re-linked, not fully recompiled. If Nixpkgs split builds so that the link step was a separate derivation from the compile step, a library change would only trigger re-linking for most dependents, not a full rebuild. This is a packaging pattern, not a Nix feature: it requires restructuring how derivations are written.

  • Splitting derivations into phases. A typical Nix derivation bundles configurePhase, buildPhase, and checkPhase into one. If the checkPhase was a separate derivation that depends on the build output, changing a test wouldn't force a rebuild of the package itself. Similarly, splitting buildPhase from installPhase would let Nix cache the build output independently. More granular derivations = more cache hits = less rebuilding.

  • Early cutoff. The idea is to let the build system tell us that nothing changed. If the build produces the same output as what's already in the store, there's no need to update the output contents: they're already good. Move on to the next derivation. Content-addressed derivations enable this: the output hash is determined by the content, not the build process. No redundant copies, no wasted build time. The closest to correctness is zb by Roxy Light.

Compilation-unit-level incrementalism. Projects like Zilch (NLnet-funded) explored making Nix-style builds work at the compilation unit level for C++, Go, and Rust. The idea is that if only one .c file changed, you recompile that file and re-link, not the entire package. This is what tools like ccache and sccache already do outside of Nix, but they break the purity model: the cache is stateful and not reproducible. A Nix-native approach would track dependencies at the file level and produce reproducible outputs. No one with the know-how is actively building it at the moment.

For development loops, simply use the development tools directly and skip using Nix. Nix is there to give you guarantees. Development may require shortcuts, and Nix will give you the guarantee at the CI level.


NixOS the operating system

Q: Should I use initrd secrets?

Position: No.

Why: Initrd secrets are not secret. They live in your initrd. Your initrd lives in your boot partition. Your boot partition is unencrypted and readable by anyone with physical access: and sometimes by virtual attackers too.

The name is misleading. Initrd secrets protect files from ending up in the Nix store, which is world-readable. That's an improvement over putting secrets directly in the store. But "better than world-readable" is not the same as "secure." If your threat model includes a motivated attacker, initrd secrets give you a false sense of security.

Think about what you're actually protecting. If it's an SSH host key, ask yourself whether an attacker who can read your boot partition is already in a position where that key is the weakest link. If it's an API token, it probably shouldn't be on the machine at all: use short-lived credentials fetched at runtime.

If you are an expert who understands the boot chain deeply, you know how to tie a proper machine identity scheme to the TPM. If you're not, you're setting yourself up for failure. Either way, initrd secrets are the wrong answer.

What to do instead:

  • Tang: network-based disk decryption with NBDE
  • TPM2-based unlocking via systemd-cryptenroll
  • systemd-creds for service-level credentials bound to the machine's TPM
  • Short-lived credentials fetched at runtime via Vault, AWS IAM, or similar
  • Kernel keyring for in-memory secret handling

Q: How should I handle secrets in NixOS?

Position: There is no single answer. Match the mechanism to the threat model. But start with systemd-creds and work up.

Why: Secrets in NixOS span a spectrum from "an API key for a home service" to "the private key material for a national administration terminal." The right answer depends on what you're protecting and from whom.

For most services, systemd-creds is the right starting point. It binds encrypted credentials to the machine's TPM, decrypts them at service start time, and presents them to the service as a file. The encrypted blob can live in your NixOS config in the clear: it's useless without the TPM it was encrypted for. This gives you declarative configuration without leaking secrets into the Nix store.

LoadCredentials=, LoadCredentialsEncrypted=, and ImportCredential= are the cleanest declarative surface for credentials in any Linux distribution I'm aware of. They streamline dynamism in NixOS and secret management at scale.

That said, all the wiring to fully utilize sd-credentials in NixOS is not here yet. In its absence, sops-nix and agenix are equally good options. For more advanced needs, systemd-vaultd and systemd-creds-openbao are interesting to connect to a secret management tool.

What I do in Sécurix is context-dependent and goes beyond what belongs in an FAQ. But the principle is universal: understand your threat model, then pick the lightest mechanism that satisfies it. Don't over-engineer. Don't put secrets in the Nix store.

Further reading: systemd-creds documentation

Q: How do I set up Secure Boot on NixOS?

Position: Use lanzaboote. It tries to offer sovereign key custody compared to most Secure Boot solutions in the Linux ecosystem. That's because NixOS is mostly about customizability of the initramfs and the kernel, and it would not really make sense to sign one golden kernel and initramfs for everyone via a shim key.

Why: Secure Boot on a traditional Linux distribution is relatively straightforward: sign your kernel and bootloader, enroll your keys, enable it. NixOS complicates this because every kernel update produces a new binary at a new store path. Boot artifacts need to be signed at bootloader install time, and sd-boot's installer doesn't know about this. It's what I rely on for Sécurix.

lanzaboote integrates with NixOS's boot loader infrastructure to sign kernels and initrds during nixos-rebuild, before they land in the boot partition. It supports custom keys and can keep Microsoft keys, and the full TPM-backed measured boot chain.

lanzaboote has ~1800 GitHub stars and is widely deployed across the ecosystem. It has some fundamental hiccups due to hardware issues with certain firmware, but it's production-ready. If you think you need something it doesn't have, open a bug report.

Further reading: lanzaboote project, NixOS Secure Boot on wiki.nixos.org

Q: What filesystem should I use with NixOS?

Position:

  • Desktop: XFS + LVM, then ZFS, then Btrfs.
  • Server: ZFS, then XFS + LVM, then Btrfs.

Why: First of all, ext4 has low inode limits, and a Nix store can consume a lot of inodes, leading to hard-to-debug problems if you don't know about it. Keeping it simple with XFS + LVM goes a long way. ZFS is the next advanced step, but at the cost of lagging behind the most modern kernels: ZFS usually lags behind the latest kernel releases.

NixOS doesn't care which filesystem you use: the Nix store is just files. The question is about your operational needs.

ZFS has the richest feature set and the most production mileage. The NixOS integration is solid. The downside is the out-of-tree kernel module, which means you're tracking kernel compatibility with every update. For a server or a workstation where you need snapshots and reliable send/receive for backups, ZFS is the right answer today.

XFS + LVM is the pragmatic choice: it's in-tree, it's stable, and it gives you logical volumes, snapshots (via LVM), and resize capabilities without the complexity of ZFS. ZFS blocks certain desktop features like hibernation and integrates less well in the desktop ecosystem. If something goes wrong, there's a higher chance that your recovery ISO can mount XFS + LVM than ZFS. For a desktop or a simple server, XFS + LVM is often the right answer.

Btrfs works, is in-tree, and has improved substantially. It's a safe default if you want CoW features without the ZFS kernel module.

I currently use XFS + LVM on my primary machine and ZFS on servers where snapshot reliability is critical.


The ecosystem

Q: Nix vs Lix: which should I use?

Position: Lix has reached an operational readiness that is superior to Nix.

Why: I founded Lix after trying hard to get the Nix team to care about a couple of issues (see nix#9412). Lix exists because it's a different team of people with different standards. Nix is a great idea, and we are thankful to Eelco Dolstra, Eelco Visser, and Armijn Hemel for bringing this amazing ecosystem together. Unfortunately, Nix has grown organically as a research codebase initially. It contains many architectural issues and dead ends.

Fixing that was not happening in the Nix codebase, and more research-y features kept coming up: content-addressed derivations, dynamic derivations, recursive Nix, and so on. At Lix, we wanted to make something that was realistic and reliable for our users.

At the time of writing, Lix still contains quality-of-life fixes that Nix has not tackled:

  • Fast turnaround for correctness bug fixes. When a correctness issue is reported, the Lix team prioritizes it and ships fixes quickly.
  • Work hand-in-hand with people with real use cases. The team engages directly with users hitting production problems, not just theoretical issues.
  • Strong stability guarantees. Lix takes regression seriously and maintains a higher bar for what ships.
  • Serious-grade daemon protocol. Incoming Cap'n'Proto RPC v2 for the daemon, replacing the legacy protocol.
  • Extremely low sandbox overhead per derivation. For gigantic build graphs, Lix's sandbox implementation has significantly lower overhead than Nix's.

The vision we are working towards at the Lix team is the one that will probably open a new way of Nix and let us all usher in a new era of Nix technology to finally get what we all wanted after all these years of errance.

Further reading: Lix project, AFNix

Q: Is NixOS ready for production?

Position: Yes, and you need to internalize its operational model before you start.

Why: I've deployed NixOS to more than a dozen large corporations, including governments, in multiple formats: image-based NixOS, store-based NixOS, embedded Nix-flavored OSes. The technology works.

The question isn't whether NixOS works: it's whether your team is ready for the NixOS operational model. NixOS replaces imperative system administration with declarative configuration. That's a cultural change, not just a technical one. Your SREs need to understand the Nix language well enough to read and modify configurations. Your deployment pipeline needs to handle nixos-rebuild. Your debugging workflow changes: you're no longer SSH-ing into a machine and running apt, you're changing a configuration file and redeploying.

The operational model is the product. If you adopt NixOS without adopting the model, you'll be fighting the tool constantly. If you adopt the model, you get reproducibility, auditability, rollbacks, and the ability to reason about your infrastructure as a function of its inputs. That's worth the learning curve.

I consult on this transition through Omnicité. The projects that succeed are the ones where the leadership understands what they're signing up for. The ones that fail are the ones that treat NixOS as a drop-in replacement for Debian.

Q: What's going on with the Nix community?

Position: The community is going through an overdue governance transition and growing pains, as with many projects. Multiple implementations now coexist: Nix, Lix, Snix.

Why: The Nix ecosystem has outgrown its original governance structure. The community is reorganizing around more formal governance, multiple implementations, and independent organizations like AFNix. As a user, this doesn't affect your day-to-day use of NixOS. The tools work.

I've written a longer analysis of the governance situation, its root causes, and what I'm doing about it through AFNix and Lix. It's in draft and will be published when it's ready.

Further reading: AFNix, Lix project, Snix, NixOS Foundation