In which I argue that Nix is the only sane way to work with Linux-based machines, especially today.
For those unacquainted, I feel the best description of Nix is a “software management tool,” intentionally vague but necessarily so: Nix can be applied in so many ways! Managing dotfiles, maintaining development environments, packaging software, provisioning machines, and quite a bit more. Want a specific version of languages and datastores for your next app? Here:
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
packages = with pkgs; [
python312
nodejs_20
postgresql_16
redis
];
}Onboarding a new coworker? nix shell gives them your environment.
And nixpkgs is the largest curated software repository out there: Twice as big as the AUR, and over 3x larger than Debian/Ubuntu!
And then there’s NixOS, which extends the whole thing to machine provisioning:
{ pkgs, ... }: {
networking.hostName = "my-server";
networking.firewall.allowedTCPPorts = [ 80 443 ];
services.nginx = {
enable = true;
virtualHosts."example.com" = {
forceSSL = true;
enableACME = true;
root = "/var/www/example.com";
};
};
services.postgresql = {
enable = true;
package = pkgs.postgresql_16;
};
system.stateVersion = "24.05";
}I don’t have to faff around with ad-hoc install.sh scripts, esoteric configuration files, and hand-rolled systemd units. A single file describes everything about the environment.
But Nix has been around for as long as you have. Why “especially today”?
Good Question! Nix is literally as old as I am :)
The language stopped being a problem
Nix, the tool, is one of the most incredible pieces of software out there. Nix, the language, is far from it. It’s a bespoke functional language that can begin to look like hieroglyphics very quickly. I would be remiss to blindly recommend Nix to every developer on the strength of the tool alone.
On the other hand, I don’t even write Nix anymore. Coding agents handle Nix just fine, flattening the learning curve almost completely. I believe this is an even more tractable problem than general software development. The complexity is bounded: there are only so many technologies in a single project or only so many services on a single machine. The Nix component of a project will never be anything close to a traditional codebase in size.
Everything is auditable
I maintain a homelab with nearly a dozen heterogeneous hosts. A half-dozen x86 servers, three Raspberry Pis, and even a couple of Mellanox Bluefield DPUs. These span everything from a NAS driving a bcachefs-based JBOD, to controlling the custom fan curves and NVMe power states on the Pis, to managing parts of the 200Gbps-capable DPUs.
Without Nix/NixOS I’d have disparate configurations across all of them, each in various states of drift, each with its own undocumented list of hacks to get it set up and working with the underlying hardware. Instead I have a single flake (“project”, effectively) managing the whole lot. Provisioning a new machine is nearly hands-free and takes less than 10 minutes, most of which is waiting around. And I can carefully configure various sysctl flags, HDD spindown, hook up my reverse proxy automatically, etc.
Every host’s configuration lives in version control. Every change is a git diff and an apply from my Mac. Every rollback is a nixos-rebuild pointing at a previous commit. It reduced homelabbing from a part-time job to an occasional hour on the weekends. And now I can throw a Claude session at my homelab configs, and it immediately has complete context on what I’ve set up, why I’ve set it up, and the things I’ve had to work around. Any change it proposes is trivially auditable like any git diff.
And it goes so much further than homelabs! Any team managing Linux infrastructure benefits from the same property, with Nix trivially allowing the systematization of machine setup and drift prevention.
The supply chain is getting worse
Then there’s the elephant in the room. Just a couple of days ago LiteLLM, a relatively popular Python package, experienced a supply-chain attack. Before that we saw:
- GlassWorm hitting the VSCode Extension scene.
- Shai-Hulud hitting various npm packages.
- Phishing attacks on maintainers for npm libraries (Prettier, ESLint, etc).
- Far too many GitHub Actions-related supply chain attacks.
And perhaps most notoriously, the near disaster that was the XZ backdoor.
And thanks to agentic coding tools, the total volume of software out there (and thus software able to be poisoned) is increasing at hockey-stick rates. I actually think more software is a great thing as there are so many more things that wouldn’t have been built otherwise. Unfortunately, more software also means more software built upon a spiderweb of dependencies that has yet to solve its fatal flaw: trust.
Your package.lock (or Cargo.lock, uv.lock, etc.) pins your language-level dependencies. Great. But what about apt install? What about the system libraries your code links against, the runtime your language needs, the tools in your CI pipeline? Keeping track of all of that across pip, npm, cargo, go, and your system package manager just doesn’t scale.
Nix doesn’t really solve the trust problem. But it completely solves the urgency problem associated with trust in supply chains. With Nix, you Just Pin™:
{ pkgs ? import (fetchTarball
"https://github.com/NixOS/nixpkgs/archive/a1b2c3d.tar.gz") {} }:The entire dependency tree reduces to this one pin, even all the way down to the glibc version, everything becomes a known quantity.
To be frank, Nix is not nearly as pleasant as it could be. The documentation is notoriously scattered. Error messages range from unhelpful to cryptic. The community seems to be in constant debates about directions for the language, and I’ve also definitely had moments where I felt like I hated Nix.
But the alternatives are actively getting worse. More supply chain attacks, more configuration drift, more “special snowflake” servers, and more “works on my machine.” The rough edges that come with Nix are a known, mostly fixed, cost.
The cost of not having a declarative, reproducible, pinnable system?
Unknown.