Don’t get me wrong: I have nothing against Homebrew, the macOS package manager that most devs who run Apple laptops use on a near-daily basis. It’s served me admirably all these years and I’ve even written some Homebrew recipes.
But I’ve become convinced that there’s a better way—or at least a way that suits my needs better. I recently switched from Homebrew to Home Manager, the Nix-based user environment manager created by Robert Helgesson, and I’ve been extremely pleased with the results so far. Home Manager has enabled me to establish a fully declarative approach to managing my laptop environment, including my dotfiles.
You can see my current Home Manager configuration in the lucperkins/nix-home-config repo.
What I do like about Homebrew
The two things I like most about Homebrew are its comprehensiveness and its ease of use. There are tons of packages, many of which have multiple versions; it’s rare that you can’t find what you need. And when you install Homebrew using the simple script on the site it pretty much Just Works out of the gate. Virtually all of the “issues” I’ve had with Homebrew have been rooted in user error or missing versions of packages. For me it’s been an indispensable tool for years and years. Kudos to the creators and maintainers!
What Homebrew doesn’t get you
The only real “problems” with Homebrew are that (a) you have to manually manage your packages via brew install
, brew rm
, and other brew
commands and (b) it doesn’t enable you to manage other assets, like dotfiles (for tools like vim and tmux). Item (b) is no surprise, of course, because Homebrew doesn’t even purport to offer that. But I have always yearned for something that can do both in one go.
Enter Home Manager
I’ve been a big fan of Nix for years. Admittedly, I tend to be a bigger fan of the philosophy behind it—a totally declarative approach to dependency management rooted in functional programming—than of the ecosystem’s tools. I find Nix the language to be unnecessarily opaque (though it’s grown on me) and setup can be daunting. I even once intended to write a book on Nix but ultimately concluded that I just wasn’t quite up to the task.
Anyway, I came across Home Manager for Nix a while back and filed it away in my memory until Burke Libbey’s excellent YouTube series reawakened my interest. So I gave it a try, started hacking on a config, and soon I had a light bulb moment.
You can see my current Home Manager config in my lucperkins/nix-home-config repo on GitHub, which includes usage instructions in case you want to give my config a spin and make your own modifications. Clone and fork away!
The end result:
- I have a long list of packages that Home Manager installs for me. All I have to do is run
home-manager switch
and installation just works. - The Git configuration that I used to manually manage in
~/.gitconfig
and~/.gitignore
is now encapsulated in one git.nix file. - My Neovim config is all in one file (no more
.vimrc
) - My tmux config lives in tmux.nix
- My shell config for both zsh and Fish lives in shell.nix, including all my shell aliases and everything I’d ordinarily put in my
.zshrc
file.
One little snag
The one thing I had to come up with my own solution for is environment variables that I don’t want to commit to Git. I certainly don’t want to be sharing my HEROKU_API_KEY
or STRIPE_API_KEY
with the world. So I placed all my private env vars in a file at ~/.env
and added this in my .zshrc
:
if [ -e ~/.env ]; then
source ~/.env
fi
There may be a more elegant, Nix-y approach to that but this works fine.
Exceptions
Although I now use Home Manager as a full replacement for Homebrew, there are still some things that I manage manually:
- I install rustup using Home Manager but manage Rust versions manually using that tool.
- In principle you could use Home Manager to install and run Docker and PostgreSQL, but experimentation has shown that it’s much easier to just use Docker for Mac and Postgres.app, respectively.
How it works
I won’t get too deep into the weeds of how Nix works in general, but I will say a bit about Home Manager. Basically Home Manager is a set of Nix functions that convert your supplied configuration into a unitary, symlinked filesystem under /nix/store
. Home Manager creates a “home” directory with a path like this:
/nix/store/7zd60q7jzzg827z4ig7fyxsgh4yv5ym4-home-manager-path
All the packages specified in your configuration are placed under the bin
directory there, each of which is symlinked to a specific package in your Nix store. Here’s what this looks like:
readlink $(which home-manager)
/nix/store/l0bd74aj0i25gmd72ayng63m0ma95qsg-home-manager-path/bin/home-manager
ls /nix/store/l0bd74aj0i25gmd72ayng63m0ma95qsg-home-manager-path/bin
# A long list of symlinked packages
which ruby
/Users/lucasperkins/.nix-profile/bin/ruby
readlink $(which ruby)
/nix/store/l0bd74aj0i25gmd72ayng63m0ma95qsg-home-manager-path/bin/ruby
To update your environment, run home-manager switch
. This builds a brand new Home Manager directory and generates new dotfiles for you and quite a lot else. I’d recommend running tree
on that directory to see how it’s structured. If you poke around long enough you’ll find your generated config files, linked libraries, man pages, and more.
The beauty of Nix is that every time you update your configuration, even a little tiny bit, you get a brand new /nix/store/***-home-manager-path
directory with a new hashed path. This ensures that nothing is ever linking to the wrong place and that you never get collisions. To garbage collect your old paths, just run nix-collect-garbage
.
If you’re ever curious whether a package is available in the main nixpkgs store, you can just run, for example, nix search jq
. Thus far I’ve been able to get everything I need through nixpkgs, with the exception of a handful of things I’ve need to install using tools like pip and yarn.
Conclusions
If Homebrew works for you, great. Stick with it. It’s a really good tool. But if you’re feeling adventurous and want to try out a fully declarative approach to your macOS environment, Home Manager is a fantastic option.
I should also note that there’s no reason you can’t use both. In fact, I may end up needing to re-install Homebrew one of these days. But for now I’m humming along without it and having a lot of fun tweaking my settings, confident that Nix’s purity will forge a safe and worry-free path.