Nix is my favorite package manager to use on macOS

Nix is My Favorite Package Manager for macOS
Homebrew is the most popular package manager on macOS, and for good reason. However, personally, I believe that Nix is more powerful and offers capabilities that go far beyond what traditional package managers provide.
Why I Switched from Homebrew to Nix
Recently, I've been doing a lot of travel and rather than using my Linux laptops, I've been using my MacBook Pro. While the battery life and performance are really fantastic, there are a couple of quirks that I've had to get used to, especially when coming from Linux. One of these is the lack of a default package manager.
Fortunately, there are a few third-party options when it comes to macOS, with the most popular one being Homebrew. However, in my case, I actually prefer to use something else—one that I think is a lot more powerful.
Let me show you why with a practical demonstration.
The Power of Declarative Configuration
Here I have a fresh copy of macOS on a brand new machine. By running only three commands:
- One to download the package manager
- The second to download my configuration
- The third to install it
Once this third and final command finishes, this fresh macOS system will now have all of my apps, packages, and system settings applied.
This includes:
- All of my favorite GUI applications such as Firefox, Obsidian, and even Yoink (which comes from the Mac App Store)
- My favorite terminal emulator Alacritty with my configuration applied, including my favorite color scheme Tokyo Night
- My shell running Zsh with my Zenful configuration and all custom settings applied
- All of my favorite CLI applications including tmux, zoxide, and even Neovim, all with their respective dotfile configurations set up
In addition to installing packages and their configurations, the package manager has also configured my Mac system settings such as:
- Setting up faster key repeat behavior
- Ensuring that new Finder windows default to column view
- Configuring dock settings such as enabling autohide and defining persistent apps
All of this is achieved by using the Nix package manager with Nix Darwin.
What Makes Nix Special
After having used it for a couple of months now, I can safely say it's forever changed the way I work on macOS for the better. Nix allows you to specify every application, package, system setting, and configuration on your machine in a declarative manner. This includes:
- CLI tools
- Applications from the Mac App Store
- Apps from Homebrew itself
This is done using a functional language called Nix, which allows for a high level of expressibility in your Nix configuration. It's very similar to using Lua when it comes to configuring Neovim.
Key Benefits
Reproducibility: Because of this declarative nature, it allows you to easily reproduce your configuration across multiple machines and even across multiple operating systems. In my case, I'm able to keep my MacBook Pro and Linux laptops in sync with their installed packages and configurations, making sure that my environment is consistent across each machine.
Version Control: Additionally, because my entire system configuration is stored in Git, I get all of the benefits that come with using version control. This means if I accidentally make a change to my configuration that causes something to go wrong, I can easily just roll it back.
System Rollbacks: Not only that, but we can also use the Nix package manager itself to roll back to a previous version of our system configuration, returning our system to a working state.
The Catch
Nix provides a lot more functionality than just using Homebrew by itself, but there is a catch: Nix has somewhat of a steep learning curve, and the declarative nature of managing packages and configuration can be a challenging mindset to migrate to.
Getting Started with Nix on macOS
In this article, we're going to take a look at:
- The foundations of getting Nix working on macOS with the Nix Darwin module
- How to install packages from the Nix repository
- How to install applications from both the Mac App Store and Homebrew
- How to use Nix to configure Mac system settings
Note: For all the other features that Nix provides, such as managing dotfiles, I'll be saving those for another article.
Installing Nix
To set up Nix and Nix Darwin is actually rather simple and only takes a couple of steps. For this demonstration, I'm using a fresh M1 Mac Mini running the latest version of macOS (Sonoma 14.6 at the time of recording, though I've confirmed everything works with macOS Sequoia as well).
Step 1: Install the Nix Package Manager
We first need to install the Nix package manager using a single command from the downloads page of the NixOS website under macOS:
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
This command will prompt you to answer a couple of questions before installing Nix on your system.
Important note: The Nix installer is rather involved as it creates a separate Apple volume and a number of user accounts. This isn't really too much of an issue but definitely speaks to the complexity mentioned earlier.
Once the installer is finished, we can check that it's working by running:
nix shell nixpkgs#neofetch --command neofetch
Understanding Nix Shell
Before we move on, I want to take a minute to appreciate how awesome the nix shell
command is. As you just saw, we used it to download and run neofetch without actually installing neofetch onto our system. If you try to run neofetch again after exiting, you'll see that the command isn't found.
This is because the nix shell
command downloads packages and loads them into a temporary environment rather than installing them system-wide. This makes it useful for running one-off commands without bloating your system.
Setting Up Nix Darwin
Now that we have the Nix package manager installed, let's add the Nix Darwin module.
Create Configuration Directory
First, create a new directory to store your configuration:
mkdir ~/nix
cd ~/nix
Note: You can set this to wherever you want. The Nix Darwin documentation suggests using ~/.config/nix
, but either location works fine.
Choose Your Approach: Flakes vs Channels
When it comes to Nix Darwin, there are two approaches:
- Using Nix channels
- Using Nix flakes
My personal preference is to use the flakes approach because it provides:
- Better composability through the use of Git
- Improved reproducibility through the use of a lock file
- More decentralized behavior
- Easier installation of packages from third-party sources like GitHub repositories
Initialize Nix Darwin with Flakes
To use Nix Darwin with Nix flakes, run:
nix flake init -t nix-darwin --extra-experimental-features "nix-command flakes"
This creates a new file called flake.nix
in your directory.
Understanding the Flake Configuration
Let's examine the flake.nix
file structure:
{
description = "Zenful Darwin system flake";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
nix-darwin.url = "github:LnL7/nix-darwin";
nix-darwin.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = inputs@{ self, nix-darwin, nixpkgs }:
let
configuration = { pkgs, ... }: {
# Your configuration goes here
};
in
{
darwinConfigurations."simple" = nix-darwin.lib.darwinSystem {
modules = [ configuration ];
};
};
}
Key Components
Description: A simple string describing your flake.
Inputs: Specifies the dependencies of the flake:
nixpkgs
: The Nix packages repository (set to unstable branch for most recent versions)nix-darwin
: The Nix Darwin repository- The
follows
configuration keeps both repositories in sync
Outputs: Contains your system configuration, including:
- Local variables defined in the
let
expression - The main configuration function where most changes will take place
- Darwin configuration outputs
Installing Your Configuration
Before installing, make a couple of changes to the flake:
1. Change the Configuration Name
Replace "simple" with something more descriptive. I prefer to use something memorable like "mini" for my Mac Mini:
darwinConfigurations."mini" = nix-darwin.lib.darwinSystem {
modules = [ configuration ];
};
2. Set the System Architecture
For Apple Silicon Macs, change the system architecture:
nixpkgs.hostPlatform = "aarch64-darwin";
3. Build and Apply the Configuration
Run the installation command:
nix run nix-darwin --extra-experimental-features "nix-command flakes" -- switch --flake ~/nix#mini
After completion, verify the installation:
which darwin-rebuild
Managing Packages with Nix
Now we're ready to start managing packages! Here's where Nix differs significantly from other package managers.
The Declarative Approach
Unlike Homebrew where you use brew install
, Nix works declaratively. While Nix does provide a nix-env
command similar to traditional package managers, I recommend against using it as it goes against Nix's main strength: its declarative nature.
Installing CLI Packages
To install packages, add them to the environment.systemPackages
list in your flake.nix
:
environment.systemPackages = [
pkgs.neovim
pkgs.tmux
pkgs.zoxide
];
Then rebuild your configuration:
darwin-rebuild switch --flake ~/nix#mini
Due to Nix's declarative nature, anything specified in this configuration will be installed, and anything that isn't will be removed. This is great for reducing system bloat.
Discovering Packages
You can search for packages using:
nix search nixpkgs
Or use the web interface at search.nixos.org.
Installing GUI Applications
Nix can also install GUI applications, and there are several approaches:
From the Nix Repository
This is the preferred approach as it works cross-platform:
environment.systemPackages = [
pkgs.alacritty
pkgs.firefox
];
Fixing Spotlight Integration
By default, Nix uses symlinks which aren't indexed by Spotlight. To fix this, we need to generate macOS aliases instead.
First, add the required packages and configuration:
environment.systemPackages = [
pkgs.makeWrapper
];
system.activationScripts.applications.text = let
env = pkgs.buildEnv {
name = "system-applications";
paths = config.environment.systemPackages;
pathsToLink = "/Applications";
};
in
pkgs.lib.mkForce ''
# Set up applications.
echo "setting up /Applications..." >&2
rm -rf /Applications/Nix\ Apps
mkdir -p /Applications/Nix\ Apps
find ${env}/Applications -maxdepth 1 -type l -exec readlink '{}' \; | \
while read src; do
app_name=$(basename "$src")
echo "copying $src" >&2
${pkgs.mkalias}/bin/mkalias "$src" "/Applications/Nix Apps/$app_name"
done
'';
You can find this activation script in the description.
Installing Fonts
Fonts can be installed declaratively as well:
fonts.fonts = [
(pkgs.nerdfonts.override { fonts = [ "JetBrainsMono" ]; })
];
Handling Unfree Applications
Non-open source applications require special configuration:
nixpkgs.config.allowUnfree = true;
Now you can install applications like Obsidian:
environment.systemPackages = [
pkgs.obsidian
];
Integrating with Homebrew
While I try to use the Nix repository as much as possible, not every Mac app is available there. For missing applications, we can integrate Nix with Homebrew using the Nix Homebrew module.
Adding Nix Homebrew
First, add it to your flake inputs:
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
nix-darwin.url = "github:LnL7/nix-darwin";
nix-homebrew.url = "github:zhaofengli-wip/nix-homebrew";
};
Then add it to your Darwin configuration modules:
modules = [
configuration
nix-homebrew.darwinModules.nix-homebrew
{
nix-homebrew = {
enable = true;
enableRosetta = true; # For Apple Silicon
user = "your-username";
};
}
];
Managing Homebrew Packages
Configure Homebrew declaratively:
homebrew = {
enable = true;
onActivation.cleanup = "zap"; # Removes packages not specified in config
casks = [
"hammerspoon"
"firefox"
"the-unarchiver"
];
brews = [
"mas" # Mac App Store CLI
];
};
Installing Mac App Store Applications
For Mac App Store apps, use the masApps
option:
homebrew.masApps = {
"Yoink" = 457622435;
};
To find app IDs, you can:
- Copy the share link from the Mac App Store and extract the ID
- Use the
mas
CLI tool:mas search "app name"
Note: You need to be logged into the App Store and have purchased the app already.
Updating Packages
To update your packages:
Update the flake inputs:
bashnix flake update
Rebuild your configuration:
bashdarwin-rebuild switch --flake ~/nix#mini
For Homebrew packages, you can enable automatic updates:
homebrew = {
onActivation.autoUpdate = true;
onActivation.upgrade = true;
};
Configuring System Settings
One of Nix Darwin's most powerful features is the ability to configure macOS system settings declaratively.
Basic System Configuration
system.defaults = {
dock.autohide = true;
dock.persistent-apps = [
"${pkgs.alacritty}/Applications/Alacritty.app"
"/Applications/Firefox.app"
"/Applications/Obsidian.app"
"/System/Applications/Mail.app"
"/System/Applications/Calendar.app"
];
finder.FXPreferredViewStyle = "clmv"; # Column view
loginwindow.GuestEnabled = false;
menuExtraClock.Show24Hour = true;
NSGlobalDomain = {
AppleInterfaceStyle = "Dark";
KeyRepeat = 2;
};
};
Finding More Options
To discover available system settings:
- Use the Nix Darwin documentation
- Run
darwin-option
command for CLI documentation - Check mynixos.com for a hierarchical view of all options
Version Control Your Configuration
Don't forget to commit your configuration to Git:
git init
git add .
git commit -m "Initial Nix Darwin configuration"
Consider pushing to a remote repository like GitHub so you don't lose your configuration and can easily sync across machines.
Useful Links
What's Next?
This covers the basics of using Nix and Nix Darwin for package management and configuring macOS settings. In the next article, we'll explore how to add dotfiles into your Nix configuration and manage them declaratively using the fantastic Home Manager module.
If you can't wait that long, I recommend checking out Vim Joyer's YouTube channel, which has excellent content on setting up Nix and NixOS that easily translates to Nix Darwin.
The declarative approach to system management might feel different at first, but once you experience the power of reproducible, version-controlled system configurations, it's hard to go back to traditional package managers. Nix Darwin has fundamentally changed how I work on macOS, and I believe it can do the same for you.