NixOS is the Developer OS
One broken Arch update was all it took. NixOS fixed the problem completely, and then gave me things I didn't know I needed.
Windows 10 is dead. Microsoft made the call for me: forced Win11, forced TPM requirements, forced the whole thing. I wasn’t interested. I’ve dual-booted Mac and Windows before, but Linux was new territory. When Win10 EOL gave me the nudge, I figured it was time.
I had basic Unix under my belt from college, and work had put me in shells on AWS instances and remote servers over the years, so Linux wasn’t foreign, just not something I’d run as a daily driver. I started on an Arch fork. Arch is fast, minimal, and the AUR is genuinely incredible. Then one update destroyed my system so completely I couldn’t even get a TTY. Black screen, no recovery, nothing. That was enough. I’m not here to babysit my OS or skim the Arch news page for breaking changes before every update. My computer is a tool.
So I switched to NixOS. That was the fix.
Why NixOS Doesn’t Break
NixOS builds your entire system from a declarative configuration. Every package, every service, every dotfile: described in code, reproducible, deterministic. When you run nixos-rebuild switch, Nix doesn’t mutate your current system in place. It builds a new generation alongside the existing one, then atomically switches to it.
If the new generation has a problem, you check out a previous commit in your config repo and run nixos-rebuild switch. You’re back to exactly that state. If things are bad enough that the system won’t even boot, previous generations are listed right in the bootloader. Either way: no recovery mode, no chroot, no reinstall, no lost afternoon.
BTRFS Makes It Even More Bulletproof
BTRFS is a filesystem with built-in snapshot support that pairs almost perfectly with NixOS’s rollback model. I found it while going down the rabbit hole after my Arch install died.
The catch: you have to set it up before you install the OS. BTRFS needs to be your filesystem from the start, with subvolumes laid out correctly. It’s not something you bolt on later. But if you’re doing a fresh NixOS install anyway, there’s no reason not to.
With BTRFS and snapper configured, every system change creates a snapshot. Those snapshots show up in GRUB alongside your Nix generations, so at boot you have two independent layers of rollback to choose from. You can pick a Nix generation, a BTRFS snapshot, or both.
Can it even really break at that point?
Genuinely hard to imagine. Between Nix building new generations atomically and BTRFS preserving the filesystem state before every change, I have never once worried about running an update. I just run it.
Configuration as Code, Actually
Here’s the thing that doesn’t fully click until you’re living in it: your system config is a git repo. It’s code. You can commit it, diff it, branch it, share it.
# This is your whole system. In a file. In git.{ config, pkgs, ... }:{ environment.systemPackages = with pkgs; [ git neovim ripgrep fd ];
services.openssh.enable = true; programs.zsh.enable = true;}When I get a new machine, I clone the repo and run nixos-rebuild switch. Twenty minutes later it’s configured identically. Not “mostly the same.” Identical. That’s not an exaggeration. Nix closes the gap between “works on my machine” and reality.
Hardware differences are a new problem, but they’re isolated to
hardware-configuration.nix(the machine-specific file NixOS generates on install). Everything else transfers cleanly.
Fix It Once, Fix It Forever
On a traditional distro, you might back up your dotfiles and feel like your setup is portable. But dotfiles are only part of the picture. They configure tools. They don’t install them. They don’t capture your system services, your kernel parameters, your udev rules, or any of the hundred small system-level tweaks you made and forgot about. Hit a problem on a new machine and you’re debugging from scratch.
With Nix, when you fix something, you fix it in the config. That fix now lives in git and applies everywhere. Every machine you ever build from that config gets the solution automatically. You never debug the same problem twice.
Devshells Changed How I Think About Projects
This is the one I didn’t expect to care about as much as I do. I was aware of the concept through dev containers and Docker (isolated, reproducible environments per project), but getting that right was always cumbersome enough that I never fully committed to it.
Devshells are that idea, without the overhead. nix develop (or direnv + nix-direnv) lets you define a per-project shell with its own toolchain. No global npm install -g. No version manager soup. No “wait, which Python is this?” Each project carries its own environment, pinned, reproducible.
# flake.nix — your project's devshelldevShells.default = pkgs.mkShell { buildInputs = with pkgs; [ nodejs_22 nodePackages.npm typescript ];};Run nix develop (or just cd into the directory with direnv), and you’re in an isolated shell with exactly those tools. Leave the directory, they’re gone. Your global PATH doesn’t accumulate cruft. Every dev I add to a project has an identical environment from day one.
Combined with flake.lock (which pins the exact git commit of nixpkgs you’re building against), this is a level of reproducibility that package.json and lockfiles gesture at but never fully deliver.1
The Bad Parts
I won’t pretend Nix is frictionless. It isn’t.
The learning curve is steep. The error messages are dense. The Nix language is unlike anything else you’ve used. The docs aren’t exactly scattered but they’re split across a few places: the NixOS manual, nix.dev, the nixpkgs source itself, and knowing which one to reach for takes time. Some things that should be easy (running a random binary, packaging a project with weird build tooling) are genuinely painful until you know the patterns.
Package availability is another real limitation. nixpkgs is enormous with great coverage, but packages aren’t always on the latest version. Someone has to update the derivation and get it merged. If you need bleeding edge, you can pin specific packages to unstable or even maintain the package yourself in nixpkgs, but that’s real work you’re taking on. Sometimes packages just don’t install correctly at all. I ended up installing Discord via Flatpak because the Nix package was giving me grief. It’s not common, but it happens.
I also haven’t had to do a full NixOS version upgrade yet, so I can’t speak to how smooth that is. I suspect there’s some friction there too.
The saving grace is that most of these are front-loaded problems. You hit them early, you learn the patterns, and they stop coming up. The day-to-day is smooth. The system doesn’t surprise you, it doesn’t break under you, and it does exactly what the config says.
AI Makes the Learning Curve Manageable
All of that said, this is where AI tooling genuinely changed the equation for me. Working through the ecosystem with an LLM was dramatically faster and less frustrating than digging through Google results and fragmented documentation. I could describe what I wanted in plain English and get working Nix config back, then ask follow-up questions in context. It felt like having a knowledgeable guide rather than piecing things together from a dozen Stack Overflow threads. The occasional hallucination is a real issue (Nix option names change, packages get renamed), but nixos-option and the NixOS search site resolve those quickly. I genuinely think Nix would have been a much harder sell without modern AI tooling to smooth the onboarding.
Worth It
I’m not going back. My machine doesn’t break. My environments are reproducible. Every problem I’ve solved stays solved. That’s a fundamentally different relationship with your computer than anything I had on Windows or a traditional Linux distro.
If you’ve been burned by Arch, or you’re finally making the jump to Linux and want to do it right the first time: NixOS is where I’d start.
If any of this resonates, the video that started it for me was NixOS: Everything Everywhere All At Once by Tris from No Boilerplate. Worth the watch.
Footnotes
-
Lockfiles pin package versions, just one layer of the stack. They say nothing about the Node version running your code, the C compiler that built your native addons, or the system libraries those addons linked against. Two devs with identical
package-lock.jsoncan still get different behavior because one is on Node 20 and the other on Node 22, or becausenode-gypcompiled against a different libssl.flake.lockpins a commit of nixpkgs, which means it pins all of that: Node, npm, every system library, every build tool, down to glibc. The entire closure is reproducible. ↩