Nix, the Good and the Bad
If you know me in person, you’ve probably heard me rant about
nix
serveral times. I have mixed feelings about
nix
, which I will try to express in this blog post. But
before any of that, what is nix
, and what is it good
for?
I could go on for way too long describing everything that nix
does in too great detail for the purposes of this blog post. In
fact, I tried writing a comprehensive guide of everything that
nix
is, and what it isn’t. It was supposed to be a
multi-page thing, with it’s own dedicated section on this website,
but ultimately the momentum and my motivation ran out and I
abandoned the ambitious project.
like too many other projects of mine…
What is nix
?
Nix is commonly separated into three different parts, successively building upon each other:
Nix - the language and build system
At it’s core, nix
is essentially just a build
system. The user can write derivations (build scripts
written in the Nix programming language), which can be
evaluated (built) into store paths (a package
living inside the /nix/store/
). By evaluating
derivations in a sandbox without access to the rest of the
filesystem or the internet, Nix makes it very difficult to
accidentally make non-reproducible derivations. The idea here is
that a derivation build on one machine should be completely
identical to the same package built on another machine, even if it
has a different architecture or different OS.
Nixpkgs - the package repository
Built upon Nix - the language, is Nixpkgs - the package repository. It consists of more than 80.000 packages, which actually is the largest repository of all linux distibutions (according to repology).
You can see if Nixpkgs has your favorite package here!
NixOS - the declarative operating system
In NixOS, instead of editing the state of your machine
configuration by imperatively executing comamnds in your terminal,
you have a configuration.nix
-file (or a
flake.nix
, if you’re a cool nix kid) which explicitly
declares the desired state of your machine. This includes
things like which programs should be installed and how they should
be configured. After editing your NixOS configuration to your hearts
content, you then run nixos-rebuild switch
to activate
that updated configuration.
The good
Now that you’ve seen the tip of the iceberg, let’s get into my opinions.
Declarativity
Editing a file and running a command to update system state might seem like an extra step and a hurdle to some, but in my experience, it’s 100% worth it. Because of all this explicit state collected in one location it becomes trivial to answer questions like “how did I configure that program?”, or “how did I work around that bug?”. It is all documented right there. No more digging around in the filesystem trying to find a bug in that one systemd-unit that you hand wrote for a package you build from source.
Another benefit of this in conjunction with NixOS’ module system is that it’s possible to write one single configuration for all your computers to share, disabling the parts you don’t need for that particular machine.
NixOS generations
Every time you nixos-rebuild switch
your system,
another generation gets added to your list of boot entires.
So if your new generation is borked in some way you can just
nixos-rebuild switch --rollback
, or reboot back into a
known good version. This can really save your ass if you for example
upgrade to a new kernel, but discover it’s not working as it
should.
nix shell
Ever wanted to try out a program without installing it on your
computer forever? Just run
nix shell nixpkgs#<program>
and nix will fetch
the program for you and put you in a shell with the program
available. When you leave the shell it will no longer be available
(but it will still be cached in your /nix/store
)
Direnv + flakes
Using flakes you can neatly tie build instructions, testing, and
nixos modules, but also development shells with all your development
dependencies automatically installed. Additionally this can be
further improved by using direnv
to automatically enter
your development shells when you enter the project folder.
Contributing to a project with a flake.nix
in the root
make me confident I will have no issues setting up the development
environment for the contribution I’m making.
The mixed
Some aspects of nix are like a douple edged sword, useful for slicing your problems, but also useful for slicing yourself.
nixpkgs
GitHub repo
The nixpkgs
GitHub
repo is both a benefit and a drawback of the Nix ecosystem. For
one, it allows super easy access to the actual source of
the distribution. How is this package implemented? You can look it
up! It also makes it super easy to contribute new packages or
modules to nixpkgs, as it’s as simple making a PR. However, maybe
it’s because so easy to contribute new packages, or maybe it’s
because nixpkgs doesn’t have enough maintainers, but there are so
many issues and PRs that GitHub literally won’t count them all
(+5k). This makes having anyone actually review and merge your
package very difficult.
Documentation
Documentation is also a bit of a mixed bag. There are a multitude of sources: nixpkgs manual, NixOS manual, NixOS wiki, Nix Pills, Zero to Nix. (These are not even all of them) The first two manuals are single pages, making them painfully slow to load, even on modern hardware. The documentation is often good, but sometimes it’s lacking or incomplete. Unfortunately this makes Nix needlessly difficult to pick up.
The bad
Lastly, we have the absolute worst parts of nix; the things I’m referring to when I say that nix sucks.
Build times
Nix is a rather difficult language to benchmark. It’s not like a traditional programming language, and it’s not meant to do the same things as one. So there isn’t really anything to compare against, apart from guix, which I haven’t tried. But even though it is in a class of it’s own, I feel like Nix is needlessly slow. The evaluation time obviously goes up as with the complexity of your NixOS configurations, but when evaluating all the systems I have declared in my flake can take up to 10 minutes before nix is done “thinking” and starts to actually build stuff. Even when evaluating only one system, it’s more than a handful of seconds before anything really starts to happen, which really kills the development cycle.
Error messages
Nix error messages are horrible. I have not seen another language with less helpful error messages. If you’re lucky, and keep to the beaten path, you will most likely have alright error messages saying what’s wrong and what option is wrong. It doesn’t tell you where though. If you are unlucky, or want to actually write functions and anything more complex than the bare minimum, you will run into stack traces filling up half your terminal buffer, filled with references to internal functions in nixpkgs that you didn’t write, with incredibly cryptic descriptions. If you actually scroll through this stack trace, you might find a reference to a file that you actually have created yourself, but that’s not a guarantee. More often than not, I find myself using trial and error to work around the errors. Sometimes you’ll run into infinite recursions for inexplicable reasons. My preferred method of resolving those is rewriting the entire code in another way to see if it fixes it. And while debugging any errors, the 10+ second build times certainly aren’t helpful.
Dynamically linked libraries
Due to how nix wants complete control over the entire dependency chain (which, again, has it’s benefits) it makes it really difficult to gradually approach nix. It’s more of an “all or nothing”-kind of thing. You can say goodbye to downloading precompiled binaries outside of nix itself, as more often than not they depend on dynamically linked binaries. The issue with this is that because of how nix is designed, the libraries aren’t in the usual places, and if the binary isn’t specifically linked to the right path, it’s just going to crash. There are ways to work around this with tools like nix-alien, or nixGL, but it’s cumbersome and not idiot proof. So if it ain’t from the nix store, you probably ain’t runnin’ it, man.
The conclusion
Unfortunately there are essentially no alternatives to Nix. The closest project would be guix, but as far as I’m aware it lacks many of the features nix has, apart from being a libre-only distro. I mean, I like the idea of libre, but I also like my steam games. In another way, docker could also be an alternative, but in my experience docker manages to be even worse, and again only share some aspects of nix.
Shortly said, this is how I like to put it:
Nix is pretty bad, but it’s the best that there is.
Thanks for actually reading all this lol