Imagine you could share bits of your dotfiles with coworkers. Not technically isolated bits like, "my bash profile," but topical bits, like "my vault configuration." It might set some variables, download some files, install a peculiar version of a package.

This is possible with Nix, a functional language for describing and composing packages. In Nix, my dev environment config is just a package composed of a bunch of other packages, such as Docker and OpenJDK 11.

I can configure Nix in interesting ways. I want a boring stable version of Docker but a bleeding edge version of Kakoune with some custom patches. Nix allows me to write and install an overlay which modifies or replaces a package in the main repository. Then, I get the Docker and Kakoune versions I want, but also other packages that want these things depend on my modified versions.

An Example of Sharing Derivations

Overlays are powerful. I’ve started a private overlay repo for work that has pre-configured packages for daily life there.

We use Vault. To configure Vault, you normally need to edit your .bash_profile and set VAULT_ADDR to the address of the correct server. You also need to download a CA certificate from somewhere, put it somewhere on your system, and set VAULT_CACERT to its path. After that, vault works.

While there are ways to set environment variables with nix-darwin, Nix has interesting ideas about how to compose software packages that tell us to do this differently: We replace the vault package with a wrapper that calls the original package’s vault program with VAULT_ADDR and VAULT_CACERT set correctly [1].

After writing and sharing the overlay, other people at my work can install it and avoid configuring Vault.

Other Strategies

I’ve not gotten all the details on how to share overlays, packages, and necessary machine config hammered out quite yet. It works, but it feels like there are better ways to do it.

History: How I Got Here

My dotfiles repo contains dotfiles, but also scripts to symlink them, bootstrap my package managers, install packages, and change system settings.

It started as hand-rolled bash twiddling Homebrew and running defaults, but it’s always bothered me that this is a lot of work for one person. Surely this is a common problem.

When I got a linux dev server, I found I was duplicating my config — linux package names are slightly different from Homebrew package names, and subtle differences in tooling meant the same scripts didn’t work in both places. Very annoying.

I sought a package manager that worked on both, and found Nix. In fact, I’d already used Nix a little bit to make reproducible dev environments, but it could install the same packages on Mac OS and NixOS, a different Linux or BSD, or it can create a Docker image.

Built on Nix, nix-darwin has modules for declaratively [2] configuring a Mac. NixOS is a Linux distribution configured the same way.

In short, Nix is a reproducible way to store my config, and I’ve been gradually migrating to it ever since.


1. If these variables are already set, the wrapper doesn’t override them, essentially making the config default and still allowing the user to use other servers.
2. Well, it’s actually functional. Declarative has the problem of creating a lot of repetitive boilerplate that is difficult to maintain. "Pure functional" keeps things reproducible while allowing us to remove boilerplate.