Why NixOS stopped my distro hopping

I've been using NixOS for a while now, on my laptop and on the servers I self-host my stuff on. This post explains how I use it and why I think it's the best setup I've had so far.

Before NixOS I used Arch. I liked it, but I got tired of my system slowly drifting: packages I'd installed once and forgotten, config files I'd edited by hand, leftovers from things I'd tried. After a while it was full of garbage and I couldn't tell anymore what I had changed. With NixOS that doesn't happen, so I haven't had a reason to switch since.

My setup

I have three machines:

All three are configured from the same Git repository.

Configuring everything in one place

On most distros you install the system, then install packages and edit config files as you go. After a few years you end up with a machine where nobody really knows what was changed and why.

On NixOS you don't do that. The whole system is described in Nix files, and you apply them with nixos-rebuild switch. If I want Vaultwarden on my server, I add this to the config:

services.vaultwarden = {
  enable = true;
  config.ROCKET_PORT = 8222;
};

and rebuild. If I remove it, it's gone from the system on the next rebuild. Nothing is left behind.

Since the config is in Git, I also get the history of every change, and I can look up how anything is configured with a simple search in the repo, instead of looking around in /etc.

Rolling back

Each rebuild creates a new "generation", and the previous ones are kept. If an update breaks something, I can choose an older generation in the boot menu, or run nixos-rebuild switch --rollback, and I'm back to a working system.

Deploying from my laptop

I don't need to log into the servers to update them. Since all the configs are in the same repo, I can build any machine's system from my laptop and send it over SSH:

nixos-rebuild switch --flake .#gila --target-host gila --sudo

This is great for gila. It's a small VPS that would take ages to compile anything, so my laptop does the building and gila just receives the result and switches to it.

Erasing the system on every boot

Because the system is rebuilt from the config anyway, you can go one step further. On my laptop, / and /home are reset to an empty state every time it boots. Only the folders I list in my config are kept, using the impermanence module:

home.directories = [
  "Documents"
  "Downloads"
  ".ssh"
  ".local/share/fish"
];

I like this a lot. Programs can't leave files around my home folder forever, and the list of persisted folders is also the list of what I need to back up.

Running a server with fifty services

This is where NixOS helps me the most. Maintaining that many services alone would be a lot of work, but most of it is automated now:

Secrets

Passwords and API keys are in the same repository, encrypted with sops-nix. Each machine decrypts them with its own SSH host key, and only gets the secrets its config uses. When I set up a machine, I don't have to copy secret files around by hand.

The downsides

NixOS isn't easy to learn. You need to learn the Nix language, which is unusual, and the documentation is spread between the manual, the wiki, forum posts and other people's configs. Error messages can be hard to read.

Software that comes as a prebuilt binary can also be a problem, because it expects libraries in places that don't exist on NixOS. I use nix-ld on my laptop for that, and it works most of the time.

In summary

For me it's been worth it. I can rebuild any of my machines from scratch, undo a bad change, and always know what's running where. If you're curious, reading other people's configs is a good way to start. There are a lot of them on GitHub.

In the next post I'll explain how I organise my config with den, which made a big difference once the server got bigger.

Thanks for reading!