Darwin Deployment
This tutorial guides you through deploying a macOS machine using nix-darwin. You’ll understand how darwin differs from NixOS, set up zerotier mesh networking, and learn the patterns that make darwin management effective.
What you will learn
Section titled “What you will learn”By the end of this tutorial, you will understand:
- How nix-darwin provides declarative macOS configuration
- Why darwin uses different patterns than NixOS (and the practical implications)
- How zerotier mesh networking integrates with darwin via Homebrew
- The structure of darwin machine configurations in this infrastructure
- How to deploy, update, and troubleshoot darwin hosts
Prerequisites
Section titled “Prerequisites”Before starting, you should have:
- Completed the Bootstrap to Activation Tutorial
- Completed the Secrets Setup Tutorial
- A macOS machine (Apple Silicon or Intel)
- Administrator access to the machine
- The repository cloned and direnv activated
Estimated time
Section titled “Estimated time”60-90 minutes for a complete darwin deployment. Updates take 10-15 minutes once the initial setup is complete.
Understanding darwin in this infrastructure
Section titled “Understanding darwin in this infrastructure”Before deploying, let’s understand what makes darwin different and why those differences matter.
What is nix-darwin?
Section titled “What is nix-darwin?”nix-darwin brings NixOS-style declarative configuration to macOS. It manages system preferences, launchd services, homebrew packages, and more through Nix expressions.
Unlike NixOS, which controls the entire operating system, nix-darwin works alongside macOS. Apple controls the kernel, core system services, and many aspects of the user interface. nix-darwin handles the parts that macOS allows external tools to configure.
Darwin vs NixOS in this fleet
Section titled “Darwin vs NixOS in this fleet”The infrastructure includes four darwin machines and four NixOS machines:
Darwin machines:
- stibnite - crs58’s primary workstation (single user)
- blackphos - raquel’s workstation (dual user: raquel primary, crs58 admin)
- rosegold - janettesmith’s workstation (dual user: janettesmith primary, cameron admin)
- argentum - christophersmith’s workstation (dual user: christophersmith primary, cameron admin)
Key differences from NixOS:
| Aspect | Darwin | NixOS |
|---|---|---|
| Deployment | darwin-rebuild switch | clan machines update |
| Secrets (clan vars) | Planned (not yet implemented) | clan vars |
| Secrets (sops-nix) | sops-nix (user secrets) | sops-nix (user secrets) |
| Zerotier | Homebrew cask | Nix package |
| Service management | launchd | systemd |
| Disk management | macOS manages | disko via clan |
These differences shape how you approach darwin configuration and deployment.
Why Homebrew for zerotier?
Section titled “Why Homebrew for zerotier?”You might wonder why zerotier uses Homebrew instead of Nix on darwin. The answer involves macOS security architecture.
Zerotier requires:
- System extension installation (managed by macOS security policies)
- Network extension entitlements (requires Apple notarization)
- Persistent launchd service registration
Nix can build zerotier, but it can’t install system extensions or handle the macOS security prompts required for network extensions. The Homebrew cask includes Apple-notarized binaries and proper installer scripts.
This is a pragmatic choice: Homebrew handles the parts Nix can’t, while Nix manages everything else. The activation script installs zerotier via Homebrew and joins the network automatically.
Step 1: Explore the darwin configuration structure
Section titled “Step 1: Explore the darwin configuration structure”Let’s understand how darwin machines are configured before making changes.
Module hierarchy
Section titled “Module hierarchy”ls modules/machines/darwin/Each darwin machine has its own directory containing:
default.nix- Main configuration importing modules and defining machine-specific settings- Machine-specific overrides as needed
Examine a machine configuration
Section titled “Examine a machine configuration”Look at a typical darwin configuration:
cat modules/machines/darwin/stibnite/default.nixYou’ll see a structure like:
{ config, pkgs, ... }:{ imports = [ # Aggregate imports ];
# Machine identification networking.hostName = "stibnite";
# Homebrew configuration (including zerotier) homebrew = { enable = true; casks = [ "zerotier-one" ]; };
# Activation script for zerotier network join system.activationScripts.postUserActivation.text = '' # Zerotier network join logic '';
# User imports home-manager.users.crs58 = import ../../../home/users/crs58;}Understanding aggregates
Section titled “Understanding aggregates”Darwin configurations use aggregate imports to pull in related features. These aggregates compose modules by functional area:
ls modules/home/all/Common aggregates include:
- aggregate-core - Essential tools everyone needs
- aggregate-shell - Shell configuration (zsh, starship, etc.)
- aggregate-development - Development tools (git, editors, etc.)
- aggregate-ai - AI tooling (claude-code, etc.)
This pattern means changes to an aggregate affect all users who import it, reducing duplication.
Step 2: Verify your machine configuration exists
Section titled “Step 2: Verify your machine configuration exists”Before deploying, ensure your machine has a configuration.
Check for existing configuration
Section titled “Check for existing configuration”ls modules/machines/darwin/If your hostname appears in the list, you have a configuration. If not, you’ll need to create one; see the Host Onboarding Guide.
Verify the configuration builds
Section titled “Verify the configuration builds”# Replace 'stibnite' with your hostnamenix build .#darwinConfigurations.stibnite.system --dry-runA successful dry-run means the configuration is valid Nix. Any errors indicate configuration problems to fix before proceeding.
Step 3: Understand the deployment process
Section titled “Step 3: Understand the deployment process”Darwin deployment differs from NixOS because darwin-rebuild runs on the machine itself rather than being pushed from a controller.
The activation flow
Section titled “The activation flow”- Build: Nix evaluates your configuration and builds derivations
- Switch: darwin-rebuild creates a new generation and updates symlinks
- Activate: Activation scripts run (user creation, service registration, etc.)
- Homebrew: If configured, homebrew packages install or update
- Post-activation: Custom scripts run (like zerotier network join)
Generations and rollback
Section titled “Generations and rollback”Each successful activation creates a new “generation.” You can list and roll back to previous generations:
# List recent generationsdarwin-rebuild --list-generations | head -10
# Roll back to previous generationdarwin-rebuild switch --rollbackThis provides safety: if a change breaks something, you can roll back immediately.
Step 4: Deploy your darwin configuration
Section titled “Step 4: Deploy your darwin configuration”Now deploy the configuration to your machine.
Standard deployment
Section titled “Standard deployment”darwin-rebuild switch --flake .#stibniteReplace stibnite with your hostname.
Or use the convenient just task:
just activateThe just activate command auto-detects your platform and hostname.
First-time deployment
Section titled “First-time deployment”If this is a fresh machine without prior darwin-rebuild history, you might need:
# First time on a new machinedarwin-rebuild switch --flake .#hostname --impureThe --impure flag allows access to files outside the flake during initial setup.
Subsequent rebuilds shouldn’t need it.
Watch the output
Section titled “Watch the output”During deployment, observe:
- Building: Derivations being built or fetched from cache
- Activating: System changes being applied
- Homebrew: Cask installations (like zerotier-one)
- Post-activation: Custom scripts running
If you see errors, note the specific message for troubleshooting.
Step 5: Set up zerotier mesh networking
Section titled “Step 5: Set up zerotier mesh networking”Zerotier connects your darwin machine to the infrastructure’s private mesh network.
Understanding the mesh
Section titled “Understanding the mesh”The zerotier network (ID: db4344343b14b903) creates a virtual Layer 2 network across all machines:
┌─────────────┐ │ cinnabar │ │ (controller)│ └──────┬──────┘ │ ┌──────────────────┼──────────────────┐ │ │ │ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │stibnite │ │blackphos│ │ galena │ │ (peer) │ │ (peer) │ │ (peer) │ └─────────┘ └─────────┘ └─────────┘cinnabar is the zerotier controller, managing network membership. All other machines are peers that connect through the controller.
Verify zerotier installation
Section titled “Verify zerotier installation”After activation, zerotier should be installed via Homebrew:
# Check zerotier is installedwhich zerotier-cli
# Check the service is runningsudo zerotier-cli statusIf zerotier-cli isn’t found, the Homebrew cask may not have installed. Check the activation output for errors, or install manually:
brew install --cask zerotier-oneJoin the network
Section titled “Join the network”The activation script should automatically join the network. Verify:
sudo zerotier-cli listnetworksIf the network isn’t listed, join manually:
sudo zerotier-cli join db4344343b14b903Authorize the machine
Section titled “Authorize the machine”New machines need authorization from the network controller. Contact the network administrator (or if you have controller access):
- Log into the zerotier controller web interface
- Find the pending member by its zerotier address
- Authorize the machine
- Optionally assign a managed IP address
Verify connectivity
Section titled “Verify connectivity”Once authorized:
# Check network status (should show "OK")sudo zerotier-cli listnetworks
# Get your zerotier IP addresssudo zerotier-cli listnetworks | awk '{print $NF}'
# Ping another machine on the meshping cinnabar.zt # If DNS is configuredping 10.144.x.y # Or use the IP directlyStep 6: Configure secrets for darwin
Section titled “Step 6: Configure secrets for darwin”Darwin machines use sops-nix for user-level secrets. Clan vars darwin support is planned but not yet implemented; currently clan vars only operates on NixOS machines.
Ensure your age key exists
Section titled “Ensure your age key exists”ls -la ~/.config/sops/age/keys.txtIf the key doesn’t exist, complete the Secrets Setup Tutorial first.
Verify secrets deployment
Section titled “Verify secrets deployment”After activation, check that secrets deployed:
ls -la ~/.config/sops-nix/secrets/You should see your configured secrets as files with restricted permissions.
Test a secret
Section titled “Test a secret”# View a secret (be careful with sensitive values)cat ~/.config/sops-nix/secrets/github-tokenIf secrets aren’t appearing, check:
- Your age key is in the correct location
- Your public key is in
.sops.yaml - The sops configuration in your home-manager module is correct
Step 7: Verify the complete deployment
Section titled “Step 7: Verify the complete deployment”Let’s confirm everything is working.
System verification
Section titled “System verification”# Check the current generationdarwin-rebuild --list-generations | head -3
# Verify system profileecho $PATH | tr ':' '\n' | grep nix
# Check a nix-installed binary workswhich gitgit --versionHome-manager verification
Section titled “Home-manager verification”# Check home-manager generationshome-manager generations | head -3
# Verify home-manager profilels -la ~/.nix-profile/Network verification
Section titled “Network verification”# Zerotier connectivitysudo zerotier-cli listnetworksping -c 3 cinnabar.zt
# SSH to another machine (if configured)ssh cinnabar.ztComplete checklist
Section titled “Complete checklist”-
darwin-rebuild --list-generationsshows recent generation - Nix-installed tools available in PATH
- Home-manager configuration active
- Secrets deployed to
~/.config/sops-nix/secrets/ - Zerotier connected and authorized
- Can reach other machines on the mesh
What you’ve learned
Section titled “What you’ve learned”You’ve now deployed a darwin machine from start to finish. Along the way, you learned:
- nix-darwin provides declarative macOS configuration alongside (not replacing) macOS
- Homebrew integration handles what Nix can’t (system extensions, notarized installers)
- Zerotier mesh connects darwin machines to the broader infrastructure
- sops-nix provides user-level secrets on both darwin and NixOS platforms
- Generations provide rollback safety for darwin configurations
Next steps
Section titled “Next steps”Now that your darwin machine is deployed:
-
Customize your configuration by modifying your user module in
modules/home/users/ -
Learn about NixOS deployment if you manage servers. The NixOS Deployment Tutorial covers terranix provisioning and clan deployment.
-
Review operational procedures in the guides:
- Host Onboarding Guide for adding more darwin machines
- Secrets Management Guide for secret rotation and sharing
-
Understand the architecture more deeply:
- Deferred Module Composition for module organization
- System-user Integration for user configuration approaches
Troubleshooting
Section titled “Troubleshooting”darwin-rebuild not found
Section titled “darwin-rebuild not found”Ensure your development shell is active:
cd /path/to/infradirenv allowwhich darwin-rebuild“No configuration for hostname”
Section titled ““No configuration for hostname””Your machine needs a configuration entry.
Check modules/machines/darwin/ for your hostname, or create one following the Host Onboarding Guide.
Homebrew cask installation fails
Section titled “Homebrew cask installation fails”Check if Homebrew itself is working:
brew doctorbrew updateIf Homebrew needs installation:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"Zerotier won’t start
Section titled “Zerotier won’t start”macOS security may be blocking the system extension:
- Open System Preferences > Security & Privacy > General
- Look for blocked software from “ZeroTier, Inc.”
- Click “Allow” and restart zerotier
sudo launchctl stop com.zerotier.onesudo launchctl start com.zerotier.oneActivation script errors
Section titled “Activation script errors”Check the specific error message. Common issues:
- Missing Homebrew (
brewnot found) - Network issues during package download
- Permission problems (need
sudofor some operations)
For comprehensive troubleshooting, see the Host Onboarding Guide.