NixOS: What is it, how does it work, and should you use it?
I talk about my experience using NixOS after two weeks of using it as my primary OS. I'll explain what it is, how it works, and whether you should try it.
Contents
On this week’s episode of “Which meme Linux distro is Aires installing?”, I decided to go all-in on NixOS. And hoo boy, this is an interesting distro. It’s been about two weeks and I still feel like I’m only just starting to grasp it. But what is NixOS? What’s it all about, how’s it work, and why do some techies treat it as the next best thing since containers? I’ll try to cover as much of this as I can, while talking about my own experience, in this blog.
Note: This blog will go into some pretty technical concepts and assumes you have at least a cursory understanding of Linux. If not, you can still read, but this might start looking like hieroglyphics real soon.
What is NixOS? ⚓
NixOS is built on top of Nix, a “purely functional package manager.” What does that mean? Well, there are two key problems Nix solves:
First, dependency hell. Most software requires other software in order to run. These are called dependencies. For instance, if I want to install Firefox in Arch, I also need to install 37 other packages 🔗, and they may need to be specific versions. What if I install another package that uses the same dependencies, but different versions? Or what if I want to install a beta version of Firefox alongside the official version? Nix lets you do that by managing each package + version combination as its own unique thing, and then linking dependencies together.
Second, declarative configuration. What do you normally do after installing a Linux distro? Add some users? Install some packages? Tweak the settings? What if all of this was done for you, before you ever closed the installer? With Nix, you can define the state of your system in text-based configuration files. You then apply these config files and Nix modifies your system state to match the definition.
NixOS takes these concepts and applies them to the entire operating system. It manages everything from the bootloader to the kernel to Systemd services and even your home directory. You can declare user accounts, set passwords and login behaviors, decrypt drives, and so much more. It’s like Ansible built into a Linux distro, or if you’ve played with containers, it’s Kubernetes for an entire PC.
What’s the big deal about NixOS? ⚓
“Ok, but Aires, you devilishly handsome lion you,” you may be thinking, “what makes this so neat?” Well my accurate and perceptive friend, there are a few reasons:
- Recoverability. If I ever need to reinstall my OS due to a hardware failure or accidental breakage, I don’t need to try to remember what software I used, or re-configure all of my apps. I just install the base version of NixOS, run my config files, and everything’s right back to the way it was.
- Reproducibility. Nix can pin software to specific versions, so even if you hand someone else your config files, they’ll have the exact same setup as you. Flakes—an extension to Nix—takes this one further by tracking the specific versions of any libraries or modules used in your config files. That means—get this—Nix can pin itself to a specific version, so you can rely on its output to always be identical.
- Re…uh…visibility? Because everything’s defined in config files, I can get a complete understanding of my system just by reading the config. I can figure out which packages I have installed, what services I have running, which kernel modules I’m using, etc, without having to go digging around a dozen different tools.
These are just my own personal benefits. Other people have different things they get out of NixOS, but I think these cover the broad swath of reasons.
Cool…so how does it work? ⚓
Like I’ve mentioned a hundred times, Nix and NixOS work using config files. These files are written in a language specific to Nix, though it looks very much like JSON. All Nix files have the extension .nix
, or at least I recommend using it. A plain NixOS system will have its main config file at /etc/nixos/configuration.nix
. For example, here’s what the default config (as of this posting) looks like:
{ config, pkgs, lib, ... }:
{
# Allow unfree packages
nixpkgs.config.allowUnfree = true;
# Some programs need SUID wrappers, can be configured further or are
# started in user sessions.
# programs.mtr.enable = true;
# programs.gnupg.agent = {
# enable = true;
# enableSSHSupport = true;
# };
# Enable the OpenSSH daemon.
# services.openssh.enable = true;
# Open ports in the firewall.
# networking.firewall.allowedTCPPorts = [ ... ];
# networking.firewall.allowedUDPPorts = [ ... ];
# Or disable the firewall altogether.
# networking.firewall.enable = false;
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. It‘s perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "23.11"; # Did you read the comment?
}
To apply the config, use nixos-rebuild
. You can choose how and when to apply the changes: immediately, on next boot, dry build, in a virtual machine, so you can preview them, and many more. You have a ton of flexibility.
# Apply the config immediately
sudo nixos-rebuild switch
Each rebuild creates a generation, which you can think of as a change set similar to a Git commit. A generation matches a configuration, with the most recent generation representing your last run of nixos-rebuild
. NixOS adds an entry for each generation to your bootloader, so you can boot directly into a previous working generation in case your new one isn’t working out. Isn’t that awesome?
# Get the list of system generations
sudo nix-env --list-generations --profile /nix/var/nix/profiles/system
Generations are independent of each other, so you can poke around in a generation without accidentally breaking your entire system. Check the settings in it, add files, do whatever you want.
Earlier I mentioned that Nix is like Ansible. That’s only partly true. Ansible works by reading your config file(s), then going out and making changes to your system until it matches the target state. It’s cleverly designed to appear idempotent (i.e., you can run it multiple times and it’ll always generate the same result), but under the hood, it’s frantically shuffling things around in just the right way to get the desired result. Nix kinda sorta works the other way around: the system is built up from the configuration. This makes it a lot easier to jump between specific generations, since Nix isn’t moving things around. With Ansible, it’s impossible (as far as I know) to jump between builds—with Nix, it’s part of the design.
Confused yet? Me too! That’s part of what makes this so much fun!
Nix Flakes ⚓
You might note I said “plain NixOS” in the last section. This is because Nix has an extension called Flakes, which add some new functionality and behavior to Nix. Even I don’t know the specific features Flakes add, but the community generally prefers using Flakes, so if you want to reference anyone else’s config files or import extra modules into your code, you’ll most likely want to use Flakes too.
One great thing about Flakes is that it makes it really easy to run your config files from anywhere. Your configuration.nix
file will need to be renamed to flake.nix
first. After that, you can run the Flake by using:
nixos-rebuild switch --flake .
Flakes add another neat feature: profiles. Profiles let you define different configurations, then specify those configurations when calling nixos-rebuild
. As an example, I have two main computers: one is a Lenovo Legion, and one is a Microsoft Surface. I basically have two main profiles: “Legion” and “Surface.” My flake.nix
file looks something like this:
{
description = "Aires' Flake";
inputs = {
# For SecureBoot support
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
lanzaboote.url = "github:nix-community/lanzaboote";
# For Flatpak support
nix-flatpak.url = "github:gmodena/nix-flatpak/?ref=v0.2.0"; # Use github:gmodena/nix-flatpak/?ref=<tag> to change release version, or remove to track main (unstable).
# Home-manager
home-manager = {
url = "github:nix-community/home-manager/master";
inputs.nixpkgs.follows = "nixpkgs"; # Use system packages list where available
};
};
outputs = inputs@{ self, nixpkgs, lanzaboote, nix-flatpak, home-manager, ... }: {
nixosConfigurations = {
Surface = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
...
./hosts/Surface/default.nix
];
};
Legion = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./hosts/Legion/default.nix
...
];
};
};
};
}
There’s quite a bit going on here, so we’ll take it step-by-step.
First, there are three main fields a Flake has: description
, inputs
, and outputs
. description
is just a text field explaining the contents of the Flake. inputs
lets you define modules and other functions that will be passed on to outputs
, which is where the real work happens. Under outputs is a section called nixConfigurations
, which is where we define each of our different profiles. You can load in other Nix files under modules
, like I’ve shown above. In this case, I’ve given each profile its own file under the hosts/<hostname>/default.nix
.
Now, if I want to install NixOS to my Surface, I’ll run:
sudo nixos-rebuild --flake .#Surface
You can, of course, reuse imports between profiles. So instead of having to re-define a bunch of options, I can create a single “workstation.nix” file that defines things like default software, user interface settings, shell settings, and users, and import that into both host-specific files. I love this, because it means I can define my ideal system setup just once and have it automagically apply to all of my systems!
One last thing: Nix will pin the versions of the Flake modules you use. You can see these yourself in the auto-generated flake.lock
file. If you’re familiar with package management tools like NPM, this is the same idea. This file doesn’t get updated automatically, so if you want to use the latest and greatest, you’ll want to add --update
to your rebuild command:
sudo nixos-rebuild --update --flake .#Surface
Should I use NixOS? ⚓
I won’t lie, I mostly included this section for SEO purposes 😹 I don’t intend to convince you whether or not to use Nix or NixOS. But it’s still a good question to ask. Should you use NixOS, and if so, what would it take to get from a fresh installation to your ideal system?
The honest answer is: ehh, probably not. If you have only one or two computers that you use, you have them set up the way you like, you don’t plan on replacing them soon, and you don’t plan on reinstalling or reformatting your OS, you probably don’t need NixOS. If you’re using a distro like Arch, where you have access to tens of thousands of software packages, you probably don’t need NixOS or Nix.
Where Nix and NixOS shine:
- You have multiple systems you want to keep in sync.
- You use a distro that doesn’t have a large package repository, or a lot of packages are out of date. Nix isn’t rolling release by design, but a lot of packages are recent, and the unstable repository is even more up-to-date.
- You are a software developer working on code with other developers, and you want to use the same development environment as everyone else.
- You’re
paranoidworried about your systems failing unexpectedly and want a way to quickly and easily recover.
Now, if you still want to try it out, just be aware that:
- You need to be ready to do some digging. Nix’s documentation i…ess than ideal, to be diplomatic about it. The NixOS wiki is spotty; the official manual is difficult to read; and while there are a lot of comprehensive, well-written tutorials they’re scattered around the Internet. Also, NixOS doesn’t follow the Linux Standard Base 🔗 (LSB), so in many cases you won’t be able to reuse commands or reference the same files as other distros.
- You need to know how to troubleshoot. You’ll get a lot of esoteric errors when writing your first Nix files, and a lot of Google searches will lead you directly to GitHub. In some cases, the best way to learn about how to configure specific packages and options is to just read the source code.
- You need to have some Linux admin experience. If you can’t use a terminal, view log files, use the systemd journal, or install a distro, you really probably shouldn’t use Nix. I’m not trying to gatekeep, I’m just trying to save you some headaches. Start with a more traditional distro—even something like Arch would be a better start.
And most importantly: NixOS is not like any traditional Linux distro. Sure, it runs the Linux kernel, sure it uses systemd and GNU coreutils and all that good stuff, sure it runs applications built for Linux, but the way it does this is almost completely unique to any other distro.
Think about it this way: using a traditional distro is like getting pre-made fast food. You go to the counter, pick from a pre-built list of food, then walk away. Maybe it’s not exactly what you wanted, but it’s fast, easy, and accessible. Using NixOS is like writing your own recipe from scratch. It takes more time, there’s a lot of trial and error, and the meal may come out awful at first, but once you fine-tune the recipe, it comes out delicious every time.
…ok, that was a terrible analogy, but whatever. I’m sticking with it.
Conclusion ⚓
Anyway, that was my rant about NixOS. Despite all the shit I talked about it, I do still recommend checking it out. If you want to try it without nuking your computer, start with Nix, the package manager side of it. You can actually use Nix on any distro as sort of secondary package manager. I also recommend checking out
Home Manager 🔗, a Nix module that can manage configuration files in your /home/<user/
directories. I use Home Manager to do things like install and configure the ZSH shell, set up Git, and start Syncthing. Home Manager also slots nicely into your main NixOS configuration, so if you do go all-in with NixOS, you can run it alongside your main system configuration.
Ok that’s it, I swear. This blog post has gone on long enough. But if you found it useful, let me know 🔗!
Previous: "Reflecting on Everything Everywhere All at Once" | Next: "Installing a Custom Package With Nix" |