A few weeks ago, fresh off of a minor victory in my local dev setup, I tweeted this on a whim:
The reaction—120+ likes and 20+ retweets—was modest by broader Twitter standards but pretty strong for a rather vague tweet about Nix. Today, I’d like to follow up on this tweet and show how I’ve used Nix to streamline my laptop environment in ways that have saved me time and made me substantially more productive across programming languages and platforms—and even jobs.
How I used to do things #
Once upon a time, I managed my home environment, across several laptops
and one desktop, the way that many devs do nowadays: I maintained a series dotfiles (for Vim, tmux, and others)
that I updated now and then as needs changed. For executables, I used
brew install whenever the need
arose (I’m a macOS user almost exclusively).
Whenever I needed to change machines—new work laptop, new present to
myself—I eagerly looked forward to building an environment I liked from scratch and in an ad-hoc way, usually with
the help of my
dotfiles Git repo and some kludgy shell scripts. This approach always more or less
“worked” but it was always heavy on time and cognitive effort. The words “reproducible” and “declarative” weren’t
yet on my radar, at least in this domain, but my longing for them certainly was. And then a piercing ray of light
came into my life.
Enter Home Manager #
When I first came upon Nix eight years ago, I was intrigued but it seemed like something for hardcore Linux folks and hardly something that could revolutionize my daily practices. That all changed when I discovered Home Manager and began using it devoutly. Home Manager is a Nix-based tool for managing home environments in a declarative, reproducible way. It enabled me to toss out the tangled mess of dotfiles and shell scripts I previously relied upon in favor of one repo—and not a terribly complex one at that—where I could declare my entire environment with due precision: installed executables, Vim, tmux, and Visual Studio Code configuration, and much more. You can see my now-defunct Home Manager config here.
Home Manager provided me with a qualitatively better way to do things. I liked it so much that I wrote about my journey from Homebrew to Home Manager on my personal blog. I wouldn’t dream of going back to my pre-Home-Manager life. But as you’ll see, Home Manager was not the end of the story for me.
From Home Manager to Nome #
After a few years as a happy Home Manager user, I decided to get more ambitious and turn what I’ve learned about Nix and home environments into a concrete project. So I created a project called Nome , which stands for Nix Home (I was also born in Alaska and have fond memories of one of my first books, the Gnome from Nome ). Nome is a highly customized Nix flake that provides everything that I need for my home environment.
Home Manager is the foundation stone of Nome. I use it to configure Vim, Starship, VS Code, and the rest. But I’ve since gone beyond Home Manager because my needs have changed. Nowadays, I strive to make a clean separation between two things:
- My global setup, meaning executables and configuration that I truly need to have available everywhere in my environment.
- Project-level setups, where the environment is highly specific and
I now use Home Manager only for my global
setup. When it comes to
specific projects, I get as granular as possible. Back in the day, I’d kick off
a new project with
touch Makefile and start defining commands like
dev. But now I have a single command for starting new project,
which is an alias for this:
nix flake init --template github:the-nix-way/nome
This initializes a Nix flake template that provides a least-common-denominator dev environment consisting of a few toolchains that I use frequently:
Each of these toolchains is just a collection of executables. The Rust toolchain, for example, includes a standard Rust toolchain plus cargo edit, cross, rust-analyzer, and other utilities that I’m likely to need.
Here’s the block of the
flake.nix that defines a project’s shell environment:
toolchains = with nome.lib.toolchains;
devops ++ elixir ++ go ++ kubernetes ++ node ++ protobuf ++ rust;
extras = with nome.pkgs; ;
shellHook = "";
I don’t ever need to use all of my toolchains, so I always start by removing
the ones I don’t need. Then, if I need any other specific executables, I add
nome.pkgs is a re-exported Nixpkgs
pinned to a specific commit, so I can put
just about anything here). If I need commands to be run whenever I initialize
the environment, I put those in
shellHook. The result: it now typically takes
me less than a minute to get a project configured to my exact specifications.
Find out more about how Determinate Systems is transforming the developer experience around Nix
Bringing my environment to a new machine #
One of the things I like about the single-project approach I’ve adopted is that it’s rather trivial to port it from machine to machine. Let’s say that I accidentally drop my current laptop into the ocean and have to buy one. Here’s what I would need to do to get that new machine up to speed.
nix build "github:the-nix-way/nome#homeConfiguration.lucperkins.activationPackage"
This creates the initial Home Manager generation on the machine, along with all of my dotfiles, and installs my desired packages with it. I don’t change machines all that often, but it’s nonetheless consoling to know that the effort I’ve put into Nome will never be for naught—unlike those countless hours lost to manual setup in more benighted times.
Nome and NixOS #
Nome is also home to my NixOS configuration. Truth be told, I’m a novice NixOS user, so I only have one configuration that I’ve been slowly building out—and a pretty simple one at that. But thus far it’s been extremely convenient to kick start NixOS on a VM with just one command:
nixos-rebuild switch \
Nome’s future #
Nome is still very much in its youth but I expect it to be a steady and ever-maturing companion as long as I work in software. I even have a rough roadmap of where I want to take it in the near future:
- Refine my NixOS configurations to the point where I can use NixOS as my primary development environment, even on macOS. Mitchell Hashimoto’s illuminating nixos-config project has emboldened me to take this on. I’ll share details on this journey at a later date.
- Add a nix-darwin configuration to define lower-level details of my macOS environment.
- Refactor my Nix sources to be more modular, so that I can share Nix
expressions across, for example, my NixOS configurations, my macOS Home Manager config, and my
- So far my Home Manager configuration assumes my Apple M1 environment
aarch64-darwinin Nix terms) but I’d like to make it more robust by adding a Linux configuration that I can run on a (future) Linux machine.
Nome’s scope #
Nome is intended only as a personal project. If you find inspiration in it,
great! If you want to
git clone it and use it as a template, please feel
free. But don’t be
surprised if you dislike some of my choices or even my entire approach. My hope
isn’t that you follow directly in Nome’s footsteps but rather that it convinces
you to see your home environment as your career-long companion and thus worthy
of a long-running personal project.