Using Nix for Everything

October 10, 2024

This week marks one year of me using Nix. It all started with my frustration over the endless “version managers” I was having to install. Every programming ecosystem seemed to have its own, yet the need to manage different versions of a software is hardly limited to the field of software engineering.

A few curious searches later, I stumbled upon the root cause ― the Filesystem Hierarchy Standard. This sent me down a rabbit hole, and after hopping through several hyperlinks, I landed on Nix. I remember going through the documentation, not fully grasping everything, yet blown away by the concepts—reproducibility and how it elegantly tackles the problems I was facing.

“I’ve just found exactly what I was looking for,” I remember thinking to myself. Nix felt like the final package manager, the last configuration format I’ll ever have to learn, and perhaps even the last operating system I would ever need. A year later and I still believe it to be the case.

  1. Package Management

At the time of writing, nixpkgs boasts over 60,000 up-to-date packages—three times more than the Arch User Repository, which holds second place. In all my time using Nix, I’ve only encountered one instance where a package I needed was missing. I ended up packaging it myself in under 15 minutes. Writing a derivation in Nix is just that easy.

But it’s worth noting that Homebrew is still more up-to-date when it comes to macOS-specific apps, or “casks” as Homebrew calls them. Thankfully, you can use Homebrew through Nix using nix-darwin. Here’s an excerpt from my Homebrew configuration, written in Nix.

{ ... }: {
  homebrew = {
    enable = true;
    casks = [ "raycast" "utm" "orbstack" ];
  };
}
  1. Development Environment

Almost all of my projects now include a flake.nix file with devShells configured. I pair it with direnv so I don’t have to run nix develop every time. I have not needed to switch between different versions of anything in ages — I just cd into a project directory, Nix would load everything needed to run the project, then put me in a shell and, everything just works.

But there was one time I tried adding services like MySQL and Redis to a nix shell, and it turned into a bit of a challenge. So, I’d still recommend using docker-compose for services like that. I later came across devenv and gave it a shot, but I’m still not as convinced by it compared to docker-compose.

  1. Operating System

I daily-drive macOS because the incredible power efficiency of the MacBook is just too good to give up. However, most of my programming happens on a NixOS VM powered by VMWare Fusion Pro. I do plan to fully switch to NixOS with Asahi Linux on my MacBook once proper hibernation is supported.

For now, I use nix-darwin, which, like NixOS, lets me manage system configurations with Nix. Everything is super declarative. I hardly ever open the Settings app. Need to remap Caps Lock to Control or move the dock to the right? Just configure it with Nix.

{ ... }: {
  system.keyboard = {
    enableKeyMapping = true;
    remapCapsLockToControl = true;
  };
  system.defaults.dock.orientation = "right";
}
  1. Configuration Management

I use home-manager for managing my user-level configurations. I avoided it for the longest time for many reasons but eventually gave in. While it’s not perfect, it’s good enough, and it helped me clean up the mess of duct-tape configurations I had back then. It lets me write more Nix and deal with fewer of the countless configuration formats I don’t have the time (or desire) to learn.

I don’t even have to install Visual Studio Code extensions through the UI. Home Manager enables me to do this.

{ vscode-marketplace, ... }: {
  programs.vscode = {
    enable = true;
    package = pkgs.vscode;
    extensions = with vscode-marketplace; [
      vscodevim.vim
      mkhl.direnv
      frenco.vscode-vercel
    ];
  };
}

There are only a few areas where I don’t use Nix — including Neovim configurations, and Terraform scripts. I know there are amazing tools like NixVim and Terranix out there, but I tweak these configs so often that it’s just not worth the extra time to compile them to their target languages each time. Plus, Lua and Terraform are both easy to work with, so I don’t mind sticking with them.

  1. Scripting Language

I love Nix as a language. It feels like a more flexible, programmable version of JSON. I also appreciate how seamlessly you can integrate bash, making it even more powerful. This is exactly why I’m a fan of Bun for the bun shell. Sure, the error messages are rough, and lacking compared to a language like Rust, but nothing I can’t handle.

Even when I’m not writing Nix shell binaries with something like writeShellScriptBin, I still use the nix-shell shebang. It’s incredibly handy for quickly writing a one-off script without the need to install the entire toolchain.

#! /usr/bin/env nix-shell
#! nix-shell -i python3 -p python3

print("nix rocks")

Fin. See all my configurations at kyswtn/config.