Skip to content

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:

curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install

After installing, verify:

nix --version
nix run nixpkgs#hello


Bootstrap nix-darwin (First Time)

nix-darwin isn't installed yet, so you use nix run to bootstrap it. First initialise a config:

mkdir -p ~/.config/nix-darwin
cd ~/.config/nix-darwin
nix flake init -t nix-darwin

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:

darwin-rebuild switch --flake .

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

environment.systemPackages = with pkgs; [
  git
  ripgrep
  fd
  jq
  curl
];

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:

security.pam.services.sudo_local.touchIdAuth = true;

No more typing your password for sudo. Uses your fingerprint.

Fonts

fonts.packages = with pkgs; [
  nerd-fonts.fira-code
  nerd-fonts.jetbrains-mono
];

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):

nix.linux-builder.enable = true;

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.


Sources