Nix-Darwin: The Ultimate macOS System Management Tool
Introduction
Recently, I've been traveling frequently and using my MacBook Pro instead of my Linux laptops. While the battery life and performance are fantastic, there are certain aspects of macOS that required adjustment, particularly the lack of a default package manager.
Although Homebrew is the most popular third-party package manager for macOS, I've discovered a significantly more powerful alternative: Nix with nix-darwin.
The Power of Nix-Darwin
Imagine having a fresh macOS installation on a new machine. By running just three commands—to download the package manager, fetch your configuration, and install it—you can transform that vanilla system into your perfectly customized environment:
- All your favorite GUI applications installed (Firefox, Obsidian, even Mac App Store apps like Yoink)
- Terminal emulator (Alacritty) set up with your preferred configuration and color scheme
- Shell environment (Zsh) running your custom configuration
- CLI applications (Tmux, Zoxide, Neovim) installed with their respective dotfiles
- System settings configured to your liking (key repeat behavior, Finder defaults, Dock settings)
This is the power of the Nix package manager with nix-darwin, and it has completely transformed how I work on macOS.
What Makes Nix Special?
Nix allows you to specify every application, package, system setting, and configuration on your machine in a declarative manner. This includes:
- CLI tools from the Nix repositories
- Applications from the Mac App Store
- Apps from Homebrew
The configuration is written in a functional language called Nix, which offers tremendous expressiveness (similar to using Lua for Neovim configuration).
The advantages of this approach include:
- Reproducibility: Easily replicate your setup across multiple machines and operating systems
- Consistency: Keep your MacBook and Linux laptops in sync with identical packages and configurations
- Version Control: Store your entire system configuration in Git, gaining all the benefits of source control
- Rollback Capability: Easily revert to previous configurations if something goes wrong
Getting Started with Nix and Nix-Darwin
While Nix offers significant advantages over Homebrew, it does have a steeper learning curve. The declarative approach to managing packages and configurations can be challenging to adapt to initially.
Here's how to set up Nix and nix-darwin on your macOS system:
Step 1: Install the Nix Package Manager
First, you'll need to install the Nix package manager itself. Visit the NixOS website and copy the macOS installation command:
sh <(curl -L https://nixos.org/nix/install)
This installer will create a separate Apple volume and several user accounts. Once installation is complete, verify it's working by running:
nix-shell -p neofetch --run neofetch
The beauty of the nix-shell
command is that it downloads packages and loads them into a temporary environment without permanently installing them. This allows you to run one-off commands without bloating your system.
Step 2: Set Up Nix-Darwin with Flakes
Create a directory to store your Nix configuration:
mkdir -p ~/nix
cd ~/nix
Initialize your nix-darwin configuration with flakes support:
nix run --extra-experimental-features "nix-command flakes" \
github:LnL7/nix-darwin/master -- init
This will create a flake.nix
file in your directory. Open it in your text editor.
Step 3: Configure Your Flake
The flake.nix
file consists of two main sections:
- Inputs: Dependencies for your flake
- Outputs: What the flake produces (in this case, your system configuration)
Here's what to customize:
- Change the description to something personal
- Ensure the architecture is correct (
aarch64-darwin
for Apple Silicon,x86_64-darwin
for Intel) - Update the configuration name (I prefer using a machine-specific name like "mini" for my Mac Mini)
Step 4: Build and Apply Your Configuration
Run the following command to build and apply your nix-darwin configuration:
nix run --extra-experimental-features "nix-command flakes" \
github:LnL7/nix-darwin/master -- switch --flake ~/nix#mini
Verify the installation with:
which darwin-rebuild
Package Management with Nix
Installing CLI Applications
Unlike traditional package managers where you run commands to install packages, Nix takes a declarative approach. To install packages:
- Edit your
flake.nix
file - Find the
environment.systemPackages
list - Add the packages you want to install
- Rebuild your configuration with:
darwin-rebuild switch --flake ~/nix#mini
For example, to install Neovim:
environment.systemPackages = [
pkgs.neovim
pkgs.tmux
];
Finding Available Packages
Discover available packages using the nix search
command:
nix search nixpkgs tmux
Alternatively, visit search.nixos.org to browse packages.
Installing GUI Applications
Nix can install GUI applications in several ways:
1. From the Nix Repository
Add the application to your environment.systemPackages
list:
environment.systemPackages = [
pkgs.neovim
pkgs.alacritty
];
However, applications installed this way won't appear in Spotlight by default, as macOS doesn't index symlinks. To fix this, add the following to your configuration:
system.activationScripts.applications.text = ''
echo "setting up ~/Applications/Nix Apps..."
mkdir -p ~/Applications/Nix\ Apps
for app in $(find ${config.system.build.applications}/Applications -maxdepth 1 -type l); do
src="$(/usr/bin/stat -f%Y "$app")"
cp -r "$src" ~/Applications/Nix\ Apps
done
'';
2. Installing Fonts
Fonts can be installed declaratively by adding them to the fonts.fonts
list:
fonts.fonts = [
(pkgs.nerdfonts.override { fonts = [ "JetBrainsMono" ]; })
];
3. Using Homebrew for Additional Applications
For applications not available in the Nix repository, integrate Homebrew:
- Add the nix-homebrew module to your inputs:
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
darwin.url = "github:lnl7/nix-darwin";
nix-homebrew.url = "github:zhaofengli-wip/nix-homebrew";
};
- Add the module to your configuration:
homebrew = {
enable = true;
onActivation.cleanup = "zap"; # Remove packages not in config
casks = [
"hammerspoon"
"firefox"
"iina"
"the-unarchiver"
];
brews = [
"mas" # Mac App Store CLI
];
masApps = {
"Yoink" = 457622435;
};
};
Updating Packages
To update your packages:
- Update the flake inputs:
nix flake update
- Rebuild your configuration:
darwin-rebuild switch --flake ~/nix#mini
For Homebrew packages, set the following options to automatically update:
homebrew = {
enable = true;
onActivation.autoUpdate = true;
onActivation.upgrade = true;
# ...
};
Configuring macOS System Settings
One of the most powerful features of nix-darwin is the ability to declaratively configure macOS system settings.
For example, to auto-hide the Dock:
system.defaults.dock.autohide = true;
Other useful settings include:
system.defaults = {
dock = {
autohide = true;
persistent-apps = [
"/etc/profiles/per-user/username/bin/alacritty.app"
"/Applications/Firefox.app"
"/Applications/Obsidian.app"
"/System/Applications/Mail.app"
"/System/Applications/Calendar.app"
];
};
finder.FXPreferredViewStyle = "clmv"; # Column view
loginwindow.GuestEnabled = false;
NSGlobalDomain = {
AppleICUForce24HourTime = true;
AppleInterfaceStyle = "Dark";
KeyRepeat = 1; # Fastest
InitialKeyRepeat = 15;
};
};
To discover available options:
- Use the
darwin-help
command - Check the mynixos.com documentation
- Explore the nix-darwin manual pages
Version Control and Backup
For safety and reproducibility, commit your Nix configuration to a Git repository:
cd ~/nix
git init
git add .
git commit -m "Initial nix-darwin configuration"
Consider pushing to a remote repository for backup and easier deployment across multiple machines.
Conclusion
Nix with nix-darwin offers a powerful approach to managing your macOS system. While the learning curve is steeper than traditional package managers, the benefits of declarative, reproducible system configuration are significant.
The ability to completely rebuild your working environment from a single configuration file is invaluable, especially if you work across multiple machines or occasionally need to set up a new system.
In future posts, I'll explore how to manage dotfiles declaratively using the Home Manager module, further enhancing the reproducibility of your development environment.
If you're interested in learning more about Nix, check out Vimjoy's YouTube channel, which offers excellent content on setting up Nix and NixOS that translates well to nix-darwin.
Resources
- NixOS website
- nix-darwin GitHub repository
- Nix Flakes documentation
- mynixos.com - Documentation for Nix options
- Nix package search