TL;DR
The author migrated a macOS development environment from imperative Homebrew workflows to a declarative, immutable setup using Nix and nix-darwin. Benefits include reproducibility, rollbacks and ephemeral shells; trade-offs are a steep learning curve and friction with some GUI apps.
What happened
The author spent months moving a complete macOS workflow into Nix and nix-darwin to avoid the "imperative rot" that comes from ad-hoc installs (curl | sh, brew install, etc.). Nix treats a system as a pure function of its configuration: packages are stored under /nix/store with unique hashes, multiple versions can coexist, and previous system generations can be booted or listed with nix-env. Flakes (flake.nix and flake.lock) pin exact dependency commits so the same configuration builds identically on multiple machines. The writer uses darwin-rebuild switch –flake . to apply changes, and relies on nix shell for ephemeral, non-persistent development shells. Because some macOS GUI apps expect /Applications and self-updates, the author adopts a hybrid approach: manage core tooling and settings with nix-darwin while delegating big GUI apps to Homebrew via a Nix homebrew module. The post ends with practical starting steps and links to configuration examples.
Why it matters
- Reproducibility: flake.lock pins exact commits so builds are repeatable across machines.
- Safety: previous system generations remain available for rollbacks if an update breaks the setup.
- Cleaner temporary workflows: nix shell provides ephemeral environments that leave no global state behind.
- Declarative system settings: nix-darwin can configure macOS preferences (Finder, Dock, trackpad) as code.
- Practical trade-offs: significant upfront learning and occasional macOS GUI compatibility issues.
Key facts
- Nix stores packages in /nix/store with unique hashes, allowing side-by-side versions.
- System generations can be listed and rolled back; example command shown is sudo nix-env –list-generations -p /nix/var/nix/profiles/system.
- Nix flakes use a flake.nix (blueprint) and flake.lock (time-capsule) to pin dependency commits.
- darwin-rebuild switch –flake . applies a flake-based configuration on macOS with nix-darwin.
- nix shell <pkg> creates an ephemeral shell with that package available; exiting removes the environment.
- nix shell is not equivalent to a container or VM—it's not fully sandboxed and runs on the host kernel.
- Some GUI macOS apps expect to live in /Applications and self-update; these can conflict with the read-only Nix store.
- A hybrid model can be used: manage core tools and settings with nix-darwin and use Homebrew (via a Nix module) for certain casks.
- Author recommends using the Determinate Nix Installer for macOS multi-user setup and committing flake.lock as source of truth.
What to watch next
- The steep learning curve: expect time investment to understand the Nix language, links, and interactions with macOS volumes.
- Compatibility of GUI apps that self-update or expect /Applications; monitor which apps require Homebrew or manual handling.
- Maintenance of flake.lock: keep it versioned and committed to ensure reproducible builds across machines.
Quick glossary
- Nix: A package manager and build system that treats packages and system configuration as pure functions, enabling reproducibility.
- nix-darwin: A set of tools and modules that apply Nix-based configuration management to macOS system and user settings.
- Flake: A reproducible Nix project format containing a flake.nix (declaration) and flake.lock (pinned dependency commits).
- Nix store: The directory (typically /nix/store) where Nix places built packages, each with a unique hash-based path.
- Ephemeral shell (nix shell): A temporary environment that exposes specified packages without installing them globally; the environment is removed when you exit.
Reader FAQ
Can nix-darwin configure macOS preferences like Dock or Trackpad?
Yes. nix-darwin can express many Finder, Dock and system preferences in configuration files.
Will GUI apps installed via Nix appear in /Applications and update themselves?
Not reliably. Many GUI apps expect to live in /Applications and self-update; the author recommends using Homebrew casks (via a Nix module) for those.
Is nix shell equivalent to running a container or VM?
No. nix shell provides an isolated environment for variables and paths but runs on the host kernel and is not a sandboxed container.
How do I begin migrating my macOS setup to Nix?
Start by using the Determinate Nix Installer, initialize a flake and home-manager, modularize configuration files, and commit your flake.lock.
Going immutable on macOS by Antonin December 31, 2025 · 8 min read Table of contents The problem to solve: imperative rot System immutablility, and the Nix store The…
Sources
- Going immutable on macOS, using Nix-Darwin
- Reproducible macOS Configurations with Nix – Victor Pierre
- Going declarative on macOS with Nix and Nix-Darwin
- Declarative macOS Configuration Using nix-darwin And home …
Related posts
- How should a team support a colleague who frequently introduces typos?
- FreeBSD: Home NAS, Part 1 — Configuring a ZFS Mirror (RAID1)
- Round the tree, yes, but not round the squirrel — a Perelman paradox