As I said in the introduction to this series, I’m not coming to Nix completely blind. I used Nix for a brief period five years ago, but I basically haven’t touched it since then.

So the first thing I did was try to write down everything I could remember about Nix, as I think that it’s pretty important context that will give me an advantage understanding some concepts. Mostly I did this for myself: it was nice to think back on what I had had trouble with back then, and what I wanted to make sure I understood this time around.

But I also did this for the hypothetical reader that is treating my journey as an elaborate UAT for the Nix documentation. It’s important to know where I’m starting from, but my vague memory of Nix is such a small fraction of my prior knowledge. I have lots of other experiences that will inform the way I read the documentation, and that I suspect will make it easier for me to understand Nix than the “median” developer.

I didn’t think to write any of this kind of prior knowledge down, so I’ll give a brief summary of where I’m coming from:

  • I would self-identify as a fan of “functional programming.”
  • I worked as an OCaml developer for a few years.
  • I have done a fair amount of Haskell in my free time.
  • I run macOS on my personal computer. I use Homebrew as my package manager.
  • I don’t like Linux the way that some people like Linux, or care very much about computers at a low level.
  • I like high-level abstractions and simplicity and building things, and I don’t like /usr/local/opt or side channel attacks or compiling kernels from source.
  • I know some things about computers because you need to know some things about computers in order to maintain a stock exchange, but I don’t spend my free time very close to “the metal.”
  • Once when I was little I thought I had a pet bee, because it would let me pet its fuzzy back, but when I tried to pick it up it stung me.
  • I’m not afraid of bees or anything; the experience was more humbling than traumatizing.
  • Once I took a drink of soda from a can that a bee had crawled into and I very briefly had a bee in my mouth.

I feel like you probably get the idea. Let’s open up my diary and get started:

What do I remember?

I remember that NixOS was very different from Nix. In NixOS, there was a little file that described everything that should be installed, and what services should be running, and it was great. You could migrate to a new machine by basically copying that file, and NixOS would install the things that you wanted and everything just worked. It was magical, in a good way.

But I also remember that that wasn’t how you used Nix, the package manager. It was, bizarrely, not declarative. Instead you used the nix-env command to imperatively install packages for yourself. Except you didn’t actually use nix-env, you used a third-party tool called nox, because nix-env barely worked for reasons I can’t quite remember. I think it like… was super slow, because it had to evaluate a ton of stuff in order to figure out what packages were available? And nox just cached the result of that evaluation so you could install software without waiting sixty seconds? I do not really remember.

I see that the nox repository on GitHub hasn’t been updated in three years, so hopefully the new nix command supersedes it.

I remember there being a total of three different ways to get software on my NixOS box:

  • System-wide services, like sshd or nginx that are run by systemd. These are declared in the file /etc/nixos/configuration.nix and NixOS installed them (somehow).
  • User software that I use interactively, like rg or git. I installed these commands with nix-env -i on a per-user basis.
  • Project-specific software, only visible when running in a nix-shell. Declared in a file called shell.nix in the project’s directory. This is where I would put things like rustc or whatever – all the build dependencies for a project.

I’m going to try to pretend that NixOS doesn’t exist, until I have a good understanding of Nix itself. So I won’t say anything about the first point there, although I do remember some interesting things about how the configuration worked.

I remember wishing that I had something like a user.nix in my home directory where I could just declare the “user” programs I want to have. That way I could check it into my dotfiles repo and all my personal computers would sync up. And it would be easy to remove software – just delete it from the file and run nix-resync-stuff or whatever. I don’t know if there is a way to do that. I will have to look into it.

I think that nix-shell is the really killer use case of Nix, the package manager – having git installed via Nix might be cool or whatever, but ultimately it’s not going to be any different than installing it via any other package manager. But having a deterministic, stable dev environment on any machine, running natively without having to share a VM? That’s the main reason I was drawn to Nix in the first place.

Anyway. What else do I remember?

I remember that Haskell was weird. I remember that, at some point, all of Hackage was like… part of the Nix package repository? And like… maybe there was some kind of way, before Stack came out, to get consistent builds by using the versions in the Nix repo? But I started using Nix after Stack, and I sort of remember being really confused how they interacted – Stack supported Nix builds, which was great for things like external dependencies – but Stack did not use the Haskell packages that exist in Nix? It just uses Hackage directly and has its own cache? And then at some point the Haskell packages came out of Nix altogether, because there were like 90% of the repository? I don’t remember if that actually happened. I may have dreamed it.

I remember that it was super weird to me how “dependencies” in Nix meant a package, not a package-version pair. How my “deterministic” packages were actually determined by when I happened to update the Nix package “channel,” which is something I did completely out-of-band. I remember finding this troubling. I don’t actually remember how this works, or if there’s a way to pin a specific version of a dependency, or the package repository, or what.

I remember that there were specific “versioned” releases of nixpkgs, but you always wanted to use the “unstable” version. I do not remember why.

I remember running out of space in /tmp on NixOS while trying to build Haskell projects. A lot.

I remember liking NixOS enough that I installed nix on my laptop and tried to switch away from Homebrew.

I remember Nix on macOS1 not working at all unless I set a specific environment variable – possibly unrelated to Nix? – that had something to do with SSL certificates, and then it would work. But it took a long time to figure it out. I remember being very frustrated.

I remember that Nix was missing a lot of packages I had installed via Homebrew.

I remember the word “derivation,” which is a fancy Nix word for “package,” I think. I don’t remember why Nix needed its own fancy word.

That’s all I can really remember.

  1. Though it was called OS X back then. ↩︎