Why I Use NixOS

Table of Contents

I would like to celebrate the first post of this blog by talking about my favorite distro, NixOS, which I have been using for almost a year now and have no intention of changing soon. But why do I like NixOS so much? To discuss it in more depth, a bit of context and understanding where I come from is necessary.

Past Experiences

Ever since I started using GNU/Linux, I have always been a die-hard fan of Arch Linux for its simplicity, modularity, documentation, and its vast repository of packages, which allowed me to start with a “blank” system and customize it exactly as I wanted. However, there was always a problem that hindered my ability to express my creativity: the fragility of the operating system.

From time to time, I would find myself with an unusable system, caused by: errors while compiling packages from the AUR, a human error while editing a config file, or simply after updating packages. For me, this was always a problem I took for granted as unsolvable because there wasn’t an effective solution to prevent system breakages. Consequently, I felt like I was walking on eggshells, having to be careful every time I wanted to modify or update the system.

As a consequence, two further problems arose:

  • Fixing the operating system: I don’t mind doing troubleshooting, but when I am forced to do so, especially when i had other plans, it creates significant discomfort.
  • Possible loss of system state: When I found myself in catastrophic situations, I would reinstall the operating system. Even though I had backups ready with my config files, I still had to install all the packages, reconfigure systemd services, etc. In short, I would lose a lot of time, and it wasn’t guaranteed that I would return to the previous state.

The Switch to NixOS

fastfetch screenshot

fastfetch screenshot

When I found out there was an operating system that could definitively solve the problems mentioned above, I didn’t think twice, I installed it and… I understood why it isn’t so popular.

Two new problems arise:

  • the learning curve;
  • the documentation;

These problems are not intrinsic to the operating system’s architecture, but rather, it limits only the initial and intermediate user configuration experience. I like to think of this as the “threshold” to cross to receive the “reward”, as if it were a long-term investment.

Because at the beginning you will lose a lot (like really, a lot of time) trying to understand how it works, its paradigm, and generally changing your approach to the operating system. The reason why it is so difficult at first to get a grip on NixOS mechanics is due to its NOT being FHS-compliant1 and the paradigm shift, moving from imperative (traditional distro) to declarative (NixOS). Indeed, instead of using commands like systemctl --user enable waybar, you define the state of the system through a configuration file, just like dotfiles, if you will.

Comparison: Arch Linux vs NixOS

Feature Arch Linux (Traditional) NixOS (Declarative)
Configuration Method Imperative (sequential commands) Declarative (state defined in file)
Reproducibility Low (depends on operation order) High (same config file generates same system)
Error Handling Manual (debugging a broken system) Automatic (rollback to previous generations)
Attention Required High (risk of breaking the system) Medium (initial learning curve)
Flexibility Modular but fragile Pure and isolated

Example Module for the Waybar Service

{ vars, ... }:
let
	script_folder = vars.root + "/assets/scripts";
	config = (builtins.fromJSON (builtins.readFile ./config.jsonc));
	style = builtins.readFile ./style.css;
	iconPath = vars.root + "/assets/imgs/icons";
	nix_logo = "${iconPath}/NixOS.png";

	modules = with customs;
	{"custom/muted" = muted;} //
	{"custom/notification" = notification;} //
	{"custom/llama" = load_llama;} //
	{"custom/speaker" = speaker;} //
	{"image#nix_logo" = logo;};
in
{
	programs.waybar = {
		enable = true;
		settings = [(config // modules)];
		style = builtins.toString style;
		systemd.enable = true;
		systemd.enableDebug = false;
		systemd.target = "graphical-session.target";
	};
}

Stress-free distro

Once I had overcome the initial problems and created a “decent” configuration file that allowed me to have a usable operating system, I instantly noticed the “freedom of movement” in my hands. Indeed, the first advantage I noticed was the freedom to experiment, not just through development environments2, but also globally. In case of errors (even catastrophic ones like a non-responsive system), I could simply revert to an older generation using the command nixos-rebuild switch, after of course changing the config (in these cases, I use git checkout) or, in case of a broken system, through the GRUB boot entry. Indeed, NixOS automatically exposes the last N generations as possible boot entries in the bootloader.

The most beautiful and magical thing is that you return exactly to the point where you “saved,” almost like a videogame checkpoint, provided you have defined every aspect of the operating system declaratively. But that is not always easy or obvious, because inevitably you find yourself with missing packages or configurations for a service that doesn’t exist.

Derivations

Here begins the second most difficult part of NixOS because, if you are missing a certain service or program and want to continue with a “pure” system (according to Nix philosophy), then you are “forced” to create derivations. In general, derivations are Nix expressions that describe how to install a certain program, service, etc…

In my experience, I rarely found myself in situations where I could use derivations but didn’t find it necessary, because I would use the package rarely or it would be needed only for an isolated project. So I limited myself to creating development environments and building the executable.

Why I Don’t Use a Pure System

It is not necessary to create a derivation to run applications, but in a sense, this goes against the philosophy of Nix. Having to manually build packages is an imperative process that, in case of a complete wipe, will need to be repeated.

This suits me fine because it is a good compromise between ease and portability. Moreover, since I use a versioning system for the packages I build manually, the steps I will have to redo will be fewer, especially if I also incorporate a Makefile. Indeed, I will just need to do a git clone and make.

I can understand the satisfaction a 100% pure system offers, but I believe it is extremely difficult to achieve, and in my case, it’s not worth the hassle. One would have to add overlays3 or derivations even for the smallest flag of a program that is perhaps used rarely, and I would end up spending more time configuring than modifying manually. Obviously, I must keep track of every important configuration entered imperatively, otherwise non-deterministic behaviors are risked once the system is reinstalled from scratch.

Conclusion

Using NixOS, in a sense, allowed me to “not use it”; this is because now I can give more effort to the work and passions I carry out on the computer instead of being a slave to the operating system. Of course, this comes at the cost of initial effort, but I believe this is almost like climbing a mountain: Once you reach the summit, the view is breathtaking.

The english translation for this page was made with the help of an llm


  1. Filesystem Hierarchy Standard↩︎

  2. development shell; Development environments where it is possible to create a Nix config for a specific project without affecting the main system configuration. ↩︎

  3. “Modifications” to an existing derivation. ↩︎