I recently got a new Macbook, and began setting up the Nix package manager toinstall my developer toolset. I mainly did this to try and have a working setup withoutinstalling Homebrew. Since I ran into a few issues, I wanted to brieflydocument what I did and why in case others wanted to try the same.
Why Nix? (and why not Homebrew or MacPorts?)
The short answer: hype.
The long answer: I’ve been frustrated with Homebrew’s user experience for years now, andused this opportunity to start afresh. The default non-Homebrew answer is the venerableMacPorts, which has been around for quite a while. Most people who aren’tfunctional programming or build system nerds should probably use MacPorts, as it has beenaround long enough to have good support documentation floating around the internet.Unfortunately I’m a sucker for hermetic builds 1, so I decided to tryNix.
In addition, Nix has one cool feature I haven’t seen anywhere else: temporarily installingpackages using nix-shell
. For example, I can install ripgrep
inside a temporary shell,and the package is automatically cleaned up when I’m done:
nix-shell -p ripgrep
There’s a lot of cool stuff nix-shell
can do, check out some more exampleshere.
Why not Homebrew?
If Homebrew works for you, then by all means keep using Homebrew! However, I’ve grownfrustrated with it. Homebrew has spent a lot of effort making a software delivery systemwork across many iterations of OSX/MacOS, and countless developers have installed brew
as one of the first things on a new Mac. It is with this acknowledgement of the work theHomebrew developers have put into the software and its resulting success that I offerthese criticisms:
Unpredictable command times. I never know if running
brew install
orbrew upgrade
is going to take 5 seconds or 20 minutes. This usually means knowing if a program ordependency is being downloaded as a prebuilt binary or compiled on the spot. It wouldbe nice to signal to the user if compilation is about to happen, so they can plan theinstall accordingly.Very slow
brew update
times. Homebrew’s core package database is agit repository, which is great for enabling collaboration. Nix usesthe same model, but the difference lies in how the clients get the packagedatabase:brew update
does agit pull
under the hood, whilenix-channel --update
downloads a compressed tarball each time the channel is updated. This means Homebrewupdates can rely on git and GitHub itself as an efficient distribution mechanism,getting delta updates “for free” without setting up extra infrastructure. However,this design choice means that the client has to maintain its own git repo –git gc
can strike at any time, for example, and slow things downtremendously. It is also a nightmare for CI, where the lack ofan updated Homebrew git cache can negatively impact build times. Meanwhile,nix-channel --update
runs are very predictable, even if a bit inefficient – you’redownloading a 16Mb tarball when the channel is updated, but that’s it.Bad interactions when packages are upgraded. I’m not sure exactly why this happens, butoccasionally when certain packages are upgraded (such as
python
from 3.7 to 3.8),related packages can break. Because Nix builds are hermetic,packages should all “work together” regardless of how the system was previously setup.
Why not MacPorts?
To be honest, MacPorts is probably just fine for your needs. I haven’t used it in forever,but my understanding is that it’s reliable and has plenty of built-up community knowledgeon how to fix things. Take a look at it and see if it fits your needs! I also quicklyfound this post which details some differences between it andHomebrew. But hey, if you like adventure, give Nix a shot!
Why not Nix?
I felt it was honest to include a section like this.
I haven’t done a deep evaluation, but my impression is that Nix is the least documentedand least mature of the bunch. Expect to do a bit more digging to solve issues, butonce you get started it generally just works.
Workarounds are currently needed for Catalina and above, especially if you have anolder Mac without a T2 chip.
You’ll need to understand a bit of the Nix Expression Language. It’snot as scary as it sounds, but you will have to edit a
.nix
file to install apackage (there is no CLI equivalent tobrew install
that I am aware of).
Installing Nix
Installing Nix requires two phases: installing Nix itself, and then installingnix-darwin. The installation methods I know of involve curl
ing with aneventual sudo
call inside the script2. You will have to do something extra ifyou’re running Catalina3 or above.
Install Nix:
- Pre-Catalina:
curl -L https://nixos.org/nix/install | sh
- Catalina with a T2 chip:
sh <(curl -L https://nixos.org/nix/install) --darwin-use-unencrypted-nix-store-volume
(according to the site, “unecrypted” is a misnomer as the chip will encrypt itanyway). - Catalina without a T2 chip: follow these instructions4.
Make sure that
nix-build
is now in yourPATH
. You may have to source$HOME/.nix-profile/etc/profile.d/nix.sh
in your shell’src
file to get this towork.- Pre-Catalina:
Install
nix-darwin
:nix-build https://github.com/LnL7/nix-darwin/archive/master.tar.gz -A installer./result/bin/darwin-installer
You should now have
darwin-rebuild
in yourPATH
.
Using Nix
The basic workflow for using Nix is editing ~/.nixpkgs/darwin-configuration.nix
and thenrunning darwin-rebuild switch
to activate those changes.
Editing darwin-configuration.nix
Open up ~/.nixpkgs/darwin-configuration.nix
with your favorite editor. Look atenvironment.systemPackages
: this is the list of packages you’ll want to install.Nix lists are space-delimited (not comma-delimited). The packages listedthere come from the Nixpkgs channel you’re subscribed to. You can list what packages areavailable by running nix-env -qaP
or searching Nixpkgs online. All thepackages are prepended with pkgs.
; I used Nix’s with pkgs;
clause to prepend that bydefault:
environment.systemPackages = with pkgs; [ gitAndTools.gitFull byobu wget ];
Activating your changes
Once you’re done editing, run darwin-rebuild switch
to install your new packages! If youmade an error, darwin-rebuild
will let you know and your current environment will not beaffected.
Updating the package database
If you want to update the package database, you can run
nix-channel --update
Advanced usage
Nix will detect conflicts and error out, such as when two packages install the samebinary. This can happen when python37
and python38
, for example, both want to create abinary called pydoc3
. To resolve this, you can call lowPrio
to declare a package lowpriority and have the other package win.
environment.systemPackages = with pkgs; [ python37 (lowPrio python38) ];
(As a side note: once you’ve installed both, look at how quickly you can uninstall andreinstall each package. It takes between 1 and 2 seconds to uninstall or reinstallpython3.7, which in my opinion is ridiculously fast.)
Further reading
I have to admit that I only have a shallow knowledge of Nix, and the information abovehas made it sufficient as a daily replacement for Homebrew. However, there is a richecosystem around Nix that you may want to explore further. Here are a few links to learnmore:
- What does
nix-darwin
provide? - The Nix Ecosystem
- The Nix Expression Language
- “Nix Pills”, a detailed walkthrough of the Nix language and environment
- Tips and tricks for using
nix-shell
- The online Nixpkgs search
- A helpful Nix cheatsheet
Good luck and have fun!
I hope this guide was helpful! Thanks to the Nix team for making such a cool packagemanager. As I’m relatively new to Nix, feel free to contact me if there’s something I gotwrong or could have explained more clearly. Good luck and have fun with Nix!
From the Google SRE book: a build that is “insensitive tothe libraries and other software installed on the build machine.” This greatlyincreases reliability, because it means that whatever you have installed on yourcomputer is less likely to affect the build process and the resulting program. This issimilar to Dockerizing an application, but less extreme and lessresource-intensive to run. ↩︎
This is just as risky as the classic
curl | sudo sh
.Unfortunately I can’t find another way to easily install Nix, so I guess you candownload https://nixos.org/nix/install and compare its signature beforerunning it. ↩︎Catalina gets a lot of shade for its read-only root filesystem and signedbinaries, but I think it’s a step forward for general purpose computing. What I objectto is that signed binaries must be signed with registered Apple developer accounts todistribute software, and these accounts cost money (anyone can run their own softwarewith the appropriate settings, but distributing software effectively needs an Appledeveloper account). It would be nice if that was a bit distributed, so anyone couldsign and Apple maintained a reputation database or something. I’m annoyed at Apple formixing good security and OS maintenance practices with total, walled-garden lockdown.Admittedly, the web has a lot of the same security benefits, but we’re not at apoint yet where web apps can compete with native applications. Hopefully we’ll getthere one day. ↩︎
Thanks Philipp for figuring all this out and writing it down! ↩︎