Why I put up with nix
The Nix ecosystem has a lot of usability issues. This post is not about them. I spend a lot of time ranting about this (mostly to coworkers), but I want to highlight the things that I really like about Nix, for a change.
First though, when talking about Nix, I find it useful to make the following distinctions:
- Nix-the-program (the
nixcommand in particular)
- Nix-the-language (the grammar and semantics of the Nix expression language)
- Nix-the-implementation (all the Nix tools, such as nix-the-program and
It's not finished
I'm extra willing to forgive Nix-the-implementation because it is still a work-in-progress. I'm willing to attribute most rough edges to either growing pains or technical debt. And there are a lot of rough edges.
Having said this, the UX has improved as of Nix 2.4 (despite the change breaking every workflow imaginable - a great example of the growing pains I referred to).
Take flakes, for example: great idea, terrible execution. Switching a machine running my old pre-flakes config to use the new one based on flakes is painful to say the least. But it is infinitely better than having out-of-sync nix channels on different machines.
They are still rammed down your throat though, and making both flakes and the nix command itself experimental features retroactively made it significantly harder for me to actually start using the feature, which is a shame.
It helps me organize
Declarative configuration as implemented by NixOS, while not exactly transparent, makes my configurations easier to reason about from a perspective of managing multiple machines. Having a single config is less messy to maintain.
I also really like NixOS modules as a way to organize configuration, especially because it allows me to have self-contained units that I can enable and disable as appropriate.
It's more consistent
While Nix-the-implementation is horribly inconsistent, Nix-the-language is actually quite nice to work with, in my opinion.
Due to its domain-specific nature, it has useful shorthands for common
things that are generally well-designed.
Really, the only thing I'm missing
in the Nix expression language is an infix string concatenation operator.1
Y'know, besides documentation for the "standard library".
The true power of using Nix for systems configuration is that it allows me
to write all my dotfiles and other configuration files in a single language,
as opposed to writing one config in YAML and a couple of others in TOML
(both of which are... not ideal for complex files). Yet other configuration
files are written in their own specialized config format, for example the
window manager. For me, having my entire configuration in a single language
makes everything much easier to organize and doesn't require me to switch
between writing/reading different languages while I am messing with my
It's easier to reason about
Centralizing my config allows me to organize it in the way I want. Instead of having to remember where each config file is, I can choose where I want to have my configurations stored.
Using flakes also lets me store configurations for all my NixOS machines in a single repository. I've been looking for a functioning solution for managing dotfiles that might have subtle differences between my machines all over the place, and NixOS/home-manager (or as I've recently taken to calling it, NixOS plus home-manager) does this for me.
While I tend to prefer decentralization wherever possible, in the context of configuration it's easier to reason about a single source of truth.
It keeps things isolated
I like to use the command line as much as possible, and find myself using
xclip so much that I find it useful to have single-letter shortcuts for it:
c reads from stdin to my clipboard, and
p writes my clipboard to stdout.
I really hate the UX of
xclip, though. Really, I just want to forget it
exists. With Nix, this is easy to accomplish. I just specify
xclip as a
dependency of the shell scripts and don't add it to my user environment.
Another example where this is useful is, of course, development shells. I really don't like to have dependencies and tools I only need in one project cluttering my normal environment and potentially messing up assumptions I have about how commands get executed.
It just feels less messy!
It makes me track my changes
When I go to fix something in my config, this change is documented somewhere and I can add comments to explain why things are needed. Wanting to keep my system as state-independent as possible is a great motivator for me to abstain from using ugly hacks and then forgetting about them.
The "weaker" arguments
Messing around is fun
Hacking on a NixOS config is fun (when you finally know what you're doing) and generally low-risk, because if something goes wrong, either of the following happens:
- It won't switch to the new version
- It breaks and you roll it back
Aside from the occasional "
infinite recursion encountered at undefined position",
writing Nix code is very straight-forward and kind of fun too, if you know
what you're doing, that is.
Packaging stuff for Nix is overall a pretty satisfying experience, too. You can try stuff and if it doesn't work that's fine, because if a build fails it can't mess up your entire system by only partially executing. If it does work, it feels like you have solved a puzzle, as is often my experience with declarative programming.
I've spent a lot of time learning how to use it, damnit
The sunk cost fallacy strikes again. Learning Nix has probably taken a few years off my life, so I'm not ready to have all of that be for nothing. And having the time investment pay off is a good feeling.
It works for me
Aside from moving machines over from an old
based config to the new flakes-based setup, everything simply works as I
expect it to work. And frankly, I find this setup more maintainable than my
Arch setup with a git repository in my
~/.config and an absolute fuck-ton
of symlinks to other locations across my system. It was hell.
On Arch I had to manage these myself, which meant that I knew how it worked, but also how much it was held together by hope and prayers. On NixOS, this is done by home-manager and Nix, which means all those details are neatly tucked away.
I'm used to it
I have simply gotten used to the quirks of Nix at this point, and to me they are easier to work around than the quirks of managing my systems in a more "traditional" way.
Nix has some really cool features that I've started to take for granted, too:
- Overriding packages is really easy in the general case, and still easier
than elsewhere in the few more complex cases I've encountered. Usually it's
just a matter of overriding the
srcattributes of a package and popping it into an overlay in a place that makes sense.
- Rolling back after breakage is very trivial.
- Nixpkgs has a lot of packages in it and adding one is just a matter of putting it in an overlay somewhere, which can very easily be tracked by git.
- Adding dependencies to interpreters is super easy, because most of them have
withPackagesattribute that can be used to construct an interpreter that has the declared libraries in scope.
- Modifying attributes of a package in general is a well-supported use case in Nix. Because it's more expressive than just a shell script, overriding a single part of a build is trivial and doesn't really require any ugly hacks.
- Being able to temporarily install stuff without needing root privileges is
extremely useful. When I use a
nix-shellI don't have to remember to clean up after myself, nor do I have to enter my rather long
Nix has its quirks, but I've grown to live with them. As much as I love to complain about it, at the end of the day this is just me wanting to not feel bad about actually recommending Nix to other people because it is genuinely very cool.
It has been pointed out to me that
+works on strings. All my wishes have been granted. ↩