A word about the title first: "Nixopalypse" is our word, not the project's. I could not find it used anywhere
in the Nix project or its community, and if you use it in a sentence someone will look at you oddly. But the
four-part framing is real, and it matches what actually hurts when something goes wrong on a Nix system. If you
are going to run a service — including an inference stack — on NixOS, it is worth knowing which of the four
horsemen you are about to meet.
Nixpkgs today carries more than 140,000 packages and ships a Linux distribution built from the same language.
The current release is NixOS 25.11 "Xantusia", made with 2,742 contributors, and it ships Nix 2.31 in the
25.11 tree while Nix 2.34 is the latest toolchain. The older 25.05 "Warbler" reached end of life on 31 December
- So: everything below is written against the 25.11 line, and it is current as of November 2026.
Horseman one: expressions
The language really is a pure, lazy functional language, and the purity claim matters more than the academic one:
it means you can ask what was built instead of guessing. Three commands that pay for themselves immediately:
$ nix-instantiate --eval -E 'with import <nixpkgs> {}; lib.version' "25.11pre…" $ nix derivation show /run/current-system # the .drv text of everything in the system $ nix why-depends nixpkgs#hello /nix/store/…-glibc-… That is the honest advantage: a build failure in Nix is usually reproducible and inspectable. nix derivation tells you the exact arguments a derivation was called with, including the environment, and
shownix why-depends
shows the dependency graph as store paths rather than as an abstract concept.
What bites you is not the purity — it's the surface syntax. Tutorials copy with import <nixpkgs> {}; or
buildFHSEnv snippets that are really pkgs.… calls in disguise. Two things to keep in mind:
- Flakes are still marked experimental. The Nix 2.34 reference manual says flake output attributes "depend on both the flakes and nix-command experimental features, and subject to change without notice". Every guide you read will use
flake.nix; the manual is telling you it is not a stable interface. Decide deliberately whether you want that in a production host — I would not put it on a server I have to debug at 3 a.m. - Unfree and broken bits are opt-in by design. Nixpkgs splits free from unfreedom with license flags, so a CUDA or ROCm runtime may need
config.allowUnfree(or the equivalent in your host config) before it will evaluate. Do not treat that as friction: it is the project telling you where the non-licensed parts are.
Horseman two: the operating system
The OS is where NixOS's reputation for breaking gets earned, and the release notes show why: they are organised by
breaking-change category, and upgrading means reading them. In 25.11 the concrete things you must check before
upgrading a fleet: Linux 5.4 kernels removed entirely (mainline EOL inside 25.11's support window), display-manager
modules now strictly using tty1 where several previously used tty7, and Nixpkgs dropping macOS versions that
Apple itself does not support — 25.11 requires macOS Sonoma 14 or newer for Darwin users.
The safety rails are real, and they are in the manual, so use them:
$ nixos-rebuild switch --test # build first, do not activate yet $ nixos-rebuild list-generations # what you can fall back to $ nix-store --delete --ignorets …; nix-collect-garbage -d # the storage you forgot about --test is the honest habit: build, look, then switch. Generations give rollback — the boot entry pointing at a
previous system configuration — which is the difference between "we know what changed" and nothing at all on every
other distro I have administered. And stateVersion plus the module migrations are how NixOS forces you to
notice: if your config still claims an old release, the module system tells you what has moved. Budget an
afternoon per major release for a server that matters. Anyone who tells you they upgrade NixOS in one command is
running a machine they do not rely on.
Horseman three: modules
NixOS configuration is not one file; it is a set of modules evaluated together, with types, defaults, priority and
a lazily recursive config. That power is exactly what makes errors confusing. Two truths to internalise:
- Reading an option in your module reads the final value, not yours. Inside a module,
config.services.x.ymay be whatever any other module set it to; yoursettings.line is one input among many. If behaviour is surprising, the value is coming from somewhere else. - Types are the contract that saves you.
types.enum,types.submodule,typesEither-style composed types: they produce good errors for whoever uses your module.types.submoduleWithand portable submodules let you reuse option sets; if your options accept anything, they will eventually accept nonsense.
For debugging, the evaluator has flags for this exact job — tracing evaluation so you can find which file set a
value:
$ nix-instantiate --eval -N -E 'let pkgs = import <nixpkgs> {}; in pkgs.lib.version' --trace verbose 1> /dev/null $ nix-instantiate … --trace-verbose --trace-verbose-from-only … # narrow the noise The practical advice is boring and works: declare your options before you define them, keep one "glue" module per
service instead of four clever ones, and put asserts/warnings' where an invalid combination would otherwise
silently work. The module system's laziness will not warn you; it will let you find out at runtime.
Horseman four: tests
This is the horseman most people skip, and it is the one that pays for itself. NixOS ships a VM test driver —
nixosTest, from nixos/lib/testing.nix — where multiple machines boot in QEMU and you drive them from a Python
script that can talk to each node:
$ nix build -L 'github:NixOS/nixpkgs#nixosTests.nginx' # run someone else's test $ nix flake check # the checks your own flake declares $ nixpkgs-review pr 123456 # what you actually built, locally A test for our own inference service is small and worth the cost: a node with the systemd unit enabled, a health
endpoint checked with curl, one line of log output asserted. That catches the class of failure that used to
wake me up — a module change that made the unit start before its network was ready. Yes, VM tests are slow; scope
them to what you actually changed and keep them out of "run everything" paths.
The project's own gate is worth knowing: ofborg evaluates pull requests, Hydra builds the release and
unstable jobs, and since 2025 the merge path for package changes runs through @ofborg + @nixpkgs-merge-bot,
where a listed maintainer can land changes to their own packages — with backport merges and a "trusted
maintainers" design still being iterated on. The Nixpkgs core team's own updates (2025-11-04) say so plainly: the
merge process is in flux, and there was even a period where the connection to GitHub had to be re-established.
That is not a criticism of the machinery; it is what an honest project looks like in public, and it is why you
should pin your inputs and keep your own tests.
If you want to run local AI on NixOS
The parts that matter for an LLM host: the cudaQueryLinkerPorts hook exists because CUDA's pre-built libraries
dlopen their plugins at runtime — a pure-functional package manager cannot know about that, so the build-time
query makes the closure complete. GPU stacks are in nixpkgs, but under license flags you have to opt into, and the
driver/runtime coupling on your host will be whatever the current kernel modules allow. Keep the model files, cache
and weights on a partition you back up, not inside /nix/store — the store is reproducible inputs, not state.
What Nix is genuinely good for here: pinning a toolchain and its CUDA/ROCm closure to one revision of nixpkgs so
that a working inference box can be rebuilt byte-for-byte somewhere else. What it will not do: make an unfre driver
into a source-built one, or spare you the release-notes reading on upgrade day.
The checklist I would actually use
- Pin your nixpkgs input to a revision or channel you can name, and treat "unstable" as something you debug, not run.
- Read the breaking-changes section of the release notes for the version you are moving to, and again before every activation:
nixos-rebuild switch --test, then switch, with a generation ready to fall back to. - Write typed options for anything you expose to other modules; assume your value is not the final one.
- Put at least one NixOS VM test around any service you care about, and run
nixpkgs-review/nix flake checkbefore a PR rather than after. - Keep state out of the store — weights, caches, databases — so a rollback restores configuration, not your data.
Four horsemen, and one sentence to remember: Nix does not promise you it will never break; it hands you the
tools to see what was built, what changed, which value won, and whether your test still passes. That is the
whole appeal, and also the whole cost.
Sources
- NixOS/nixpkgs release notes for 25.11 ("Xantusia"), including breaking-change categories: https://ryantm.github.io/nixpkgs/release-notes/rl-2511/ and https://nixos.org/manual/nixpkgs/stable/release-notes
- NixOS 25.11 announcement (2,742 contributors; 25.05 "Warbler" EOL after 2025-12-31): https://discourse.nixos.org/t/nixos-25-11-released/72711
- Nix versions shipped per release (Nix 2.31 with 25.11; Nix 2.34 current) and the "flakes are experimental" wording in the reference manual: https://nix.dev/reference/nix-manual.html and https://nix.dev/manual/nix/stable/command-ref/new-cli/nix.html
- NixOS manual — building and running a system configuration, rollback through generations: https://nixos.org/manual/nixos/stable/
- Module system deep dive (lazy recursive config, option types): https://nix.dev/tutorials/module-system/deep-dive.html and https://nixos.org/nixpkgs/manual/
- NixOS VM test driver (
nixosTest,nixos/lib/testing.nix) and the nix.dev integration-testing tutorial: https://nixos.wiki/wiki/NixOS_Testing_library and https://nix.dev/tutorials/nixos/integration-testing-using-virtual-machines.html - Nixpkgs core team update 2025-11-04 (merge-bot extensions, trusted-maintainers draft, GitHub connection): https://discourse.nixos.org/t/nixpkgs-core-team-update-2025-11-04/71742
- Merge bot and CI tooling (ofborg/hydra/nixpkgs-merge-bot): https://github.com/NixOS/nixpkgs-merge-bot and https://wiki.nixos.org/wiki/OfBorg