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…
nix
?Nix is commonly separated into three different parts, successively building upon each other:
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.
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!
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.
Now that you’ve seen the tip of the iceberg, let’s get into my opinions.
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.
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
)
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.
Some aspects of nix are like a douple edged sword, useful for slicing your problems, but also useful for slicing yourself.
nixpkgs
GitHub repoThe 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 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.
Lastly, we have the absolute worst parts of nix; the things I’m referring to when I say that nix sucks.
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.
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.
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.
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