My experience with Nix

At the beginning of this year, I had read a lot about Nix and felt pretty confident that this wasn’t something for me. That was, until I did a voicecall with my good friend Victor. He was showing me these really neat tricks on how I could use Nix and Nix Flakes to handle configuration files, build software reproducibly and define my own development shell for each project I’m doing - I was blown away.

After that call, I immediately started with the (admittedly stupid) thing that every developer does when learning about a new technology. I started moving all my shit over to it: System configuration, package management, package configuration, etc. - I wanted it all reproducible. In this blog post I will document my experience so far. This is very far from a guide to using Nix, but you might find some useful hints from my mistakes.

Installing Nix

Okay, the first step was to aquire Nix. I use a Macbook Air M1, and I quickly discovered that Apple have made this a hastle. After Catalina, you are not able to write to the root directory of Macs, which meant getting the /nix directory up and running required hacky workarounds. Luckily, these are now intergrated into the installer, which made the install very simple.

$ sh <(curl -L

did the trick for me. A reboot was needed, but then everything was up and running smoothly. Nice!

Setting up nix-darwin

The next step was to get my system built through Nix. This is a pretty wacky concept, but the advantages are incredible. Think of all the times you’ve bought a new computer or needed to do a factory reset. All those preferences you’ve tinkered with to perfection over the years: They do not need to be set up again! A git clone, one simple command, most likely a few minutes of building and you have all your apps and software up and running.

I started with this guide and tinkered a bit according to my system:

  description = "kmaasrud's system";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    darwin.url = "github:lnl7/nix-darwin/master";
    darwin.url.nixpkgs.follows = "nixpkgs";

  outputs = { self, darwin, nixpkgs }: {
    darwinConfigurations = rec {
      mba = darwin.lib.darwinSystem {
        system = "aarch64-darwin";
        modules = [ ./configuration.nix ];

I put some simple system configuration in configuration.nix, built the flake with

$ nix build

and was joyful to see everything working. This gave me a symlinked directory named result, containing a binary sw/bin/darwin-rebuild which I could use to bootstrap the system from the same flake. To my dismay, I was met with an error that said I was missing the /run directory. The error message said I needed to include the line run\tprivate/var/run in /etc/synthetic.conf, which would create the firmlink I needed. I did this without luck and ended up giving up on the whole debacle after a long night of googling. That following morning - when I could think sensibly again - I had an idea only a genius like me could find: I did a reboot

It worked.

Okay, 1-0 to Nix, but I had no reason to give up now. darwin-rebuild switch --flake . was working, and I was off to the races! The following days, weeks, months, I have been tinkering and my Nix system configuration to the core, and I am nowhere near considering myself done. If you are curious, you can explore my config yourself on GitHub.

Nix in projects

The whole system configuration thing is very cool, but I’ve found that the real magic of Nix comes when you start using it in your projects.

Nix serves as a very flexible build tool for your software. The builds are done inside a siloed environment, which means you need to declare all the dependencies of your build. This is immensely useful! Software is built the same on all computers, which especially makes CI a lot easier.

In my project MDoc, which depends on Tectonic (and subsequently a bunch of C/C++ libraries), I am able to do the build in a shell that has all the required dependencies without actually having to manually install said dependencies. They are listed in the following way:

buildInputs = with pkgs; [
] ++ lib.optionals stdenv.isDarwin [

Nix uses this list and makes sure these are linkable when building - so smooth! As you can see, software is built the same on all computers wasn’t an entirely true statement after all, since I need some specific dependencies for Darwin systems. However, when they can be conditionally defined this way, who am I to judge.


Allright, that is a misleading header; My Nix journey has only just begun. What I can say, is that Nix has changed the way I look at software. It would be very hard to go back to the old ways of a mutable system and non-reproducible development environments. I am convinced that Nix is the future - in some way or the other. It just needs a facelift, because there is no way I am accepting nix-* type commands.

Here are some posts from sites I follow

Magit opening on a random window

If everything goes south and I find a better replacement for GNU Emacs, Magit1 would definitely be the hardest to replace. It is deeply ingrained in my daily workflow and I can’t imagine using Git with anything else. Having said that, I always hated how it…

via glorifiedgluer November 14, 2023

Can I be on your podcast?

I am working on rousing the Hare community to get the word out about our work. I have drafted the Hare evangelism guidelines to this effect, which summarizes how we want to see our community bringing Hare to more people. We’d like to spread the word in a way …

via Drew DeVault's blog November 9, 2023

New Features Roll Call: Fall 2023

You should be able to communicate without worrying that every gossip tidbit, meme, or joke you share and who you share it with will get churned up into fodder for targeted ads or used to train an AI model. You also shouldn’t have to strip …

via Signal Blog November 8, 2023