Lesson 08 — nix-darwin: Declarative macOS Configuration¶
Relevant Hardware¶
| Machine | When |
|---|---|
MacBook Air 2013 11" (macair) |
Runs NixOS Linux — NOT relevant here |
| Mac Studio | Future — will set up eventually |
| MacBook Pro (work) | Undecided — see the "just nix-packages" section below |
What is nix-darwin?¶
nix-darwin is to macOS what NixOS is to Linux — a way to declare your entire system configuration in Nix and apply it atomically. Instead of clicking through System Settings or running brew install commands you'll forget, you describe what you want and darwin-rebuild switch makes it so.
It lives at github.com/nix-darwin/nix-darwin.
What it can and can't manage¶
macOS owns the boot loader, kernel, and filesystem, so nix-darwin can't touch those. What it can manage:
| Can manage | Can't manage |
|---|---|
| System packages | Boot loader / kernel |
| Homebrew (formulae, casks, App Store) | Filesystem layout |
| launchd daemons and agents | SIP-protected files |
| System defaults (Dock, Finder, screenshots) | Most deep macOS internals |
| Fonts | |
| Shell config, environment variables | |
| Touch ID for sudo | |
| A Linux builder VM |
The Homebrew and system defaults management are nix-darwin's killer features — things you'd otherwise configure by hand and forget.
nix-darwin vs. Just Nix Packages¶
There are two levels of Nix on macOS:
Level 1: Plain Nix (no nix-darwin)
- Install the Nix package manager on macOS
- Get access to nixpkgs — packages, nix shell, nix run, dev environments
- Nothing touches your system configuration
- ~/.nix-profile gets packages, macOS stays untouched
- Good for: work MacBook Pro where you want minimal footprint
Level 2: nix-darwin
- Builds on top of plain Nix
- Adds declarative system config: packages, services, system settings
- darwin-rebuild switch applies everything atomically with rollback
- Good for: personal Macs where you want full reproducibility
For the work MacBook, plain Nix is the safer choice. IT can't tell it's there, nothing in system config changes, and you get reproducible dev environments for your projects.
For the Mac Studio, nix-darwin makes sense — full declarative setup means you can reproduce the config on any Mac. The MacBook Air runs NixOS Linux so nix-darwin doesn't apply there.
Installing Nix on macOS¶
The Determinate Systems installer is the recommended path. It enables flakes by default, handles macOS upgrades better than the official installer, and works well with nix-darwin:
After installing, verify:
Bootstrap nix-darwin (First Time)¶
nix-darwin isn't installed yet, so you use nix run to bootstrap it. First initialise a config:
This generates a starter flake.nix. Edit it, then bootstrap:
# Apple Silicon (M-series)
nix run nix-darwin/nix-darwin-26.05#darwin-rebuild -- switch --flake .
# Intel Mac
# Same command — the platform is set inside your config, not here
After the first run, darwin-rebuild is in your PATH and you use it for all future applies:
A Minimal flake.nix¶
{
description = "My macOS config";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
nix-darwin = {
url = "github:nix-darwin/nix-darwin/nix-darwin-26.05";
inputs.nixpkgs.follows = "nixpkgs";
};
home-manager = {
url = "github:nix-community/home-manager/release-26.05";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, nix-darwin, home-manager, ... }: {
darwinConfigurations."your-hostname" = nix-darwin.lib.darwinSystem {
system = "aarch64-darwin"; # Apple Silicon
# system = "x86_64-darwin"; # Intel Mac
modules = [
./configuration.nix
home-manager.darwinModules.home-manager
];
};
};
}
configuration.nix — What You Can Do¶
System packages¶
Homebrew integration¶
This is the big one. nix-darwin can manage your entire Homebrew setup declaratively:
homebrew = {
enable = true;
# Remove packages not listed here on next rebuild
onActivation.cleanup = "zap";
taps = [ "homebrew/cask-fonts" ];
# CLI tools (brew install)
brews = [
"mas" # Mac App Store CLI
];
# GUI apps (brew install --cask)
casks = [
"firefox"
"1password"
"ghostty"
"raycast"
"wezterm"
];
# Mac App Store apps (requires mas)
masApps = {
"Xcode" = 497799835;
};
};
onActivation.cleanup = "zap" removes any Homebrew package not declared here. Keeps things clean.
System defaults (the System Settings you always forget)¶
system.defaults = {
dock = {
autohide = true;
autohide-delay = 0.0;
show-recents = false;
tilesize = 48;
orientation = "left";
};
finder = {
AppleShowAllExtensions = true;
FXPreferredViewStyle = "clmv"; # column view
ShowPathbar = true;
ShowStatusBar = true;
_FXShowPosixPathInTitle = true;
};
NSGlobalDomain = {
AppleInterfaceStyle = "Dark";
KeyRepeat = 2;
InitialKeyRepeat = 15;
"com.apple.swipescrolldirection" = false; # natural scrolling off
};
screensaver.askForPasswordDelay = 10;
loginwindow.GuestEnabled = false;
};
Touch ID for sudo¶
One of nix-darwin's most loved features:
No more typing your password for sudo. Uses your fingerprint.
Fonts¶
Shell and environment¶
environment.shells = [ pkgs.fish ];
programs.fish.enable = true;
environment.variables = {
EDITOR = "nvim";
};
Linux builder VM¶
If you need to build Linux binaries on your Mac (useful for cross-compilation or NixOS configs):
This manages a background NixOS VM automatically. Wimpy uses this on his momin MacBook.
Applying Changes¶
# Apply after editing configuration.nix
darwin-rebuild switch --flake .
# Build but don't switch (test first)
darwin-rebuild build --flake .
# Update all flake inputs
nix flake update
# Roll back
darwin-rebuild switch --rollback
How Wimpy Does It¶
Wimpy's config (vendor/nix-config-main/) supports macOS alongside NixOS in the same flake. His darwin/ directory follows the same _mixins pattern as the NixOS side:
darwin/
├── _mixins/
│ ├── desktop/ ← macOS-specific desktop config
│ └── features/ ← fonts, network, etc.
├── momin/ ← his MacBook M3 Pro
└── default.nix
The flake outputs both nixosConfigurations and darwinConfigurations from the same registry. The noughty module system gates modules by host.is.darwin vs host.is.linux, so shared config (Home Manager, fonts, shell) works on both platforms.
That's the payoff of a single-repo multi-platform approach: you write something once and both platforms pick it up where applicable.
Platform Strings¶
You'll see these in flake.nix system = declarations:
| Mac | Platform string |
|---|---|
| Apple Silicon (M1, M2, M3, M4…) | aarch64-darwin |
| Intel | x86_64-darwin |
The Mac Studio is Apple Silicon (aarch64-darwin). The MacBook Air runs NixOS Linux, not macOS.