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:

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).

Flakes

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 i3 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 configs.

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:

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 /etc/nixos/configuration.nix- 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:

In conclusion

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.


  1. It has been pointed out to me that + works on strings. All my wishes have been granted.