Benefits of running NixOS for a homelab (en)
I’ll try summarize what I would have liked to know before starting my journey with NixOS, benefits and caveats.
NixOS is a configuration-driven distribution
Every system state can be described by some configuration. NixOS provides a fully-features framework, under
/etc/nixos/configuration.nix, that can recreate any system state from a given configuration. The configuration is
written using a language Nix, a functional language and package manager designed as part of NixOS.
More exactly, Nix can work independently from NixOS, but the opposite is not true: NixOS depends on Nix to work.
Familiar with infrastructure-as-code tools such as Terraform or CloudFormation? NixOS brings the same for operating system management.
Reproducibility
To achieve reproducibility, Nix (the language) is designed as a functional language, meaning evaluating the same expression (configuration) should yield the same results.
However, as Nix is a package manager, it provides a few extra non-pure operations. In particular, Nix can:
- write files (eg: generating Nginx configuration)
- read files (eg: reading secrets)
- fetch URLs (eg: download the sources of a program)
- build programs (using a concept called derivation)
See the detailed and beginner-friendly guide into Nix concepts.
Overall, this allows Nix to be a fully-featured language for a system management. But instead of shipping this as-is, NixOS comes in handy and provides a powerful framework to manage the system using standard options.
NixOS options
NixOS adds an extra layer on top of Nix. Available online, the NixOS options translates business configuration to Nix expressions in order to setup the system.
On the above link, you could search for users to see the available options to create a new user to the system,
including its home folder, attached groups, but also default shell, SSH keys, or eventual Luks device for home
encryption.
You could search for other examples like services.nginx or services.postgresql to configure and enable a dedicated
application. Those key/values pairs are translated to actual read-only system(d) configuration. Any rollback on the
configuration will reset the system state as if the changes never existed in the first place.
Eventually, operating a simple NixOS system would only involve relying on basic options, never dealing with packages versions or custom derivations (except if you need bleeding edge or exotic software).
NixOS generations
As you might guess, the system does not magically changes once you edit your /etc/nixos/configuration.nix.
Configuration must be applied by running nixos-rebuild switch, which will take care of evaluating all the
configuration and applying it to the system. The result of a nixos-rebuild is called a generation.
All generated config and built programs are contained within a read-only folder: /nix/store/....
The store
The Nix store contains files using the following pattern:
/nix/store/zjamihq5c55ikhjgjfrzw357jc94yffc-nginx-1.22.1/
Where zjamihq... is a hash of the application, giving the ability to have different variants of a store entry
installed at the same time.
To follow up with this architecture, NixOS installs almost nothing on conventional Linux folder hierarchy. Essentially, it creates symbolic links to its read-only store.
To stick on the Nginx example: enabling the service using the provided options led NixOS to create a symlink to the generated Systemd service:
/etc/systemd/system/nginx.service -> /nix/store/1sfn3150p8m62waaw3vd45njh3x640ar-unit-nginx.service/nginx.service
The generated service’s start directive contains the full path to the Nginx binary with an absolute reference to the generated Nginx configuration:
...
ExecStart=/nix/store/zjamihq5c55ikhjgjfrzw357jc94yffc-nginx-1.22.1/bin/nginx -c '/nix/store/0m7ap0vzlrhvwsh7ny38i6fchvpln462-nginx.conf'
...
Rolling back
To handle rollbacks, NixOS essentially… keeps old store entries. As the store entries does not conflicts with each other
(remember the hash prefix?), it is quite easy to make several versions live alongside each others:
/nix/store/0m7a…-nginx.conf being the old nginx.conf, and /nix/store/sb9k…-nginx.conf being the new one, it is
relatively easy to just change the nginx.service file to match the appropriate config file.
That exactly is what generations are: a set of symlinks directed to the appropriate configuration. A new generation is
created each time the configuration is applied running nixos-rebuild.
That allows NixOS to provide us the last few generations as boot options within Grub. The last generation doesn’t boot? Drivers are failing? Network is broken? Boot to the previous generation to check, write the changes, rebuild… and that’s it!
Documenting the changes
The huge benefit towards descriptive system management over imperative is how easy it is to document system changes:
- NixOS allows any kind of file hierarchy to store configuration. This allows easily splitting the features into files (any Nextcloud-related config in the same file, for instance). Listing files in a directory easily allows checking every available, or enabled services on the system.
- Descriptive also means ability to add meaningful comments to the configuration: where the tweak comes from, what does this constant means, how to regenerate this value, …
- Changes can be iterative, and, to keep track of modifications, hosted on Git and commited. Multiple admins? Review the pull requests!
Daily maintainance
As changes are versionned, and the state is reproducible, maintainance becomes incredibly light.
To enable automatic and daily upgrades, an option was made available by the NixOS maintainers. It’s easy to tweak the time window, whether to allow reboots, or a few other extra options. The purpose is to fetch new software versions within NixOS repositories (aka channels) and to apply the entire system configuration with the new software versions.
Something broke? Switch to the previous generation and fix!
Limitations
As great benefits don’t come without some sacrifices, NixOS is indeed tied to some constraints.
Hard dependency to Systemd
This makes debugging sometimes hard: as Systemd enjoys hiding everything, it’s harder figuring out whether an issue comes from bad software configuration, errors in NixOS configuration, hardware constraints, … Methodological debugging and trying to reproduce the issue is the key.
The Nix syntax is hard
Although it’s easy to write pure configuration, getting in depth with Nix (by writing derivations (packages), factorizing code, …) becomes terribly harder. Except you’re familiar with functional programming, you’d need some time reading Nix guide to get the fundamentals.
Also, don’t hesitate to ask questions. On the forums, on Reddit, or even to me: Nix community is friendly, don’t start the journey alone :)
Running bleeding edge software (obviously) requires extra maintainance
Unexpected breaking changes in the configuration might happen, it could even have conflicts with dependencies between unstable and stable packages versions. At any time, rebuilds could fail for this reason.
Any failure will prevent a rebuild
Although there is workarounds using Flakes, or pinning channels (NixOS package repositories) versions; as rebuilds are atomic, having a single software failing to rebuild means the whole rebuild fails. It needs to be fixed (or rolled back) for any change to be applied again. It could delay a tiny unrelated modification by a few hours as the build failure needs to be investigated first.
Some package managers (PyPI) are just a mess in NixOS
People know. PyPI packages are just hardly compatible with Nix philosophy. Especially, instead of trying to have different Python software cohabiting, it’s better to isolate those within Docker containers (or NixOS containers) rather than struggling with dependency version conflicts.
TL;DR and conclusion
I would say the first factor whether one would benefit NixOS or not is weither or not maintaining a system is painful, time consuming, and the preference towards longer setup rather than frequent maintainances. As a matter of scale, I often spend 1 or 2 monthes without any manual operation to my homelab.
I mentionned NixOS for servers, but it also runs very smooth on desktop or laptop. Config can be shared between systems! Having exotic or poorly supported hardware? Instead of bookmarking guides, writing (and losing) docs, or looking for commands ran a year ago in shell history: writing NixOS configuration helps keep track of the work done. Nvidia Optimus user there. (You too? See NixOS support.)
That said, NixOS requires curiosity, and some energy. Especially when coming from bigger distributions like Fedora or Debian and their respective derivates. First installation is not that hard (compared to distributions like Gentoo or ArchLinux), but digging further indeed requires to learn essential concepts.