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

Faster linking times on nightly on Linux using `rust-lld`

TL;DR: rustc will use rust-lld by default on x86_64-unknown-linux-gnu on nightly to significantly reduce linking times. Some context Linking time is often a big part of compilation time. When rustc needs to build a binary or a shared library, it will usually …

via Rust Blog May 17, 2024

The Small Web and Science

Over the past few years I’ve been noticing more and more of a discourse about the “small web” in my online communities. It’s cool to see! I mentioned the concept in an earlier post and thought I might write a few more words about it today. As far as I can tel…

via PKGW May 14, 2024

Music Assistant 2.0: Your Music, Your Players

Today, exactly five years ago, I, Marcel, started working on Music Assistant . What began as a quick script, to sync my playlists so I could switch between streaming providers, grew into a beast on its own. Music Assistant is what I’d like to call a “music…

via Home Assistant May 9, 2024