This Tool Just Killed Homebrew
Homebrew has dominated macOS package management for years, but a challenger is here to replace it entirely. Discover why developers are switching to Nix for a fully reproducible, declarative Mac setup from a single file.
Your Mac Setup Is a House of Cards
Most macOS developers live inside a precarious stack of tools. Homebrew installs everything from `git` to `ffmpeg`, ASDF or NVM juggle language versions, and some mix of shell scripts, MacUp clones, or private “bootstrap” gists try to glue it all together. Each new Mac becomes a weekend project of copy‑pasting `brew install` commands and praying `brew bundle` still matches reality.
That reality drifts almost immediately. You install a one‑off utility for a client project, tweak a `defaults write` setting, pin Node 18 for one app and Node 20 for another, and forget about it. Six months later, your laptop works, but you have no idea why, and your `Brewfile` or dotfiles only capture about 60% of what actually matters.
Config drift is the quiet tax on every Mac you own. No one tracks which obscure `launchctl` service you disabled, which Python you installed with pyenv versus Homebrew, or which `~/Library` plist you hand‑edited at 2 a.m. Try to recreate that machine on a fresh Mac and you discover that “same setup” is really a rough approximation, not a reproducible state.
Developers keep trying to script their way out of this with ever more elaborate `setup.sh` files and dotfile repos. You might have: - `brew bundle dump` to snapshot packages - ASDF or NVM configs for runtimes - A `bootstrap` script that symlinks dotfiles and runs 20+ commands
All of that remains imperative: “do this, then that, then maybe this other thing if it’s not installed.” Miss one step, or run it on a slightly different macOS version, and the whole process starts to wobble.
The dream looks very different: a single, version‑controlled file that describes your entire environment. One declarative config that says “this Mac has these packages, these apps from the Mac App Store, these language versions, and these system settings,” and a tool that makes reality match the file—on any Mac, at any time.
Compared to that, today’s Homebrew‑centric workflows feel like scripting a house of cards. One wrong upgrade, one missing tap, one silent `brew doctor` warning, and your “standard” setup collapses into hours of manual repair.
The One Config to Rule Them All
Homebrew feels like a command: `brew install vim`, hit enter, hope nothing explodes. Nix feels like a contract. You describe the exact state your Mac should be in, and Nix’s job is to make reality match that description, every time, on any machine.
“Purely functional” sounds academic, but for developers it translates to one thing: predictability. Given the same configuration file and the same inputs, Nix always produces the same outputs—identical versions, identical dependencies, identical system settings. No surprise upgrades, no “works on my machine” ghosts.
Traditional tools like Homebrew live in an imperative world. You run a series of one-off commands: - `brew install vim` - `brew upgrade` - `brew uninstall node`
Your system becomes the sum of every command you’ve ever typed, plus whatever side effects those commands had. Miss a step on a new Mac, and your setup silently drifts from the original.
Nix flips that to a declarative model. You maintain a configuration that states, in code, “this Mac must have `vim`, `neovim`, `nodejs-20`, `python-3.11`, these dotfiles, and these system defaults.” Nix then builds that state from scratch, as if your Mac were a fresh VM, and applies it atomically.
Atomic is the operative word. Nix installs live in content-addressed store paths, so upgrades become transactions: either the new configuration builds successfully, or your system stays exactly as it was. If an update breaks your toolchain, you run a single rollback command and revert the entire environment—binaries, libraries, config—like a local time machine.
Because every package lives in its own isolated prefix, Nix happily installs multiple versions of the same tool without conflict. You can have three different `node` versions, a pinned `python` for one project, and a bleeding-edge `git` for another, all coexisting without shim scripts or version-manager hacks.
Most importantly, Nix treats your whole setup as code. Your Nix files become a personal Infrastructure-as-Code stack: CLI tools, GUI apps, Mac App Store installs, shell config, even `defaults write` tweaks, all under version control. A new Mac stops being a weekend project and becomes a `git clone` and a single build command.
How Nix Conquered the Apple Ecosystem
Nix started life in 2003 as a Dutch PhD project by Eelco Dolstra, an experiment in purely functional package management. That research hardened into the Nix language and then NixOS, a Linux distribution where every package, service, and system tweak flows from declarative Nix configs. By 2010s, NixOS had become a serious alternative for reproducible Linux systems in academia, DevOps, and homelab circles, backed by thousands of contributors and over 80,000 packages in Nixpkgs.
Mac developers mostly watched from the sidelines until Nix Darwin arrived. Built as a layer on top of Nix, Nix Darwin translates those NixOS-style modules into something that understands macOS’s Unix core, Darwin. It manages launch daemons, user agents, shell environments, and system defaults the same way NixOS manages `systemd` units and services.
Relationship-wise, think in three layers. Nix is the engine: the package manager and language. Nixpkgs is the massive repository of software definitions and configuration modules. Nix Darwin is the glue that maps those abstractions onto macOS, exposing options for everything from `brew`-style CLI tools to Dock autohide settings.
Instead of scattering state across `/usr/local`, random dotfiles, and hidden plist tweaks, Nix Darwin centralizes control. A single flake can declare: - Which packages from Nixpkgs to install - Which Mac system defaults to set - Which services to enable or disable
Nix Darwin is not a weekend hack. It lives on GitHub with hundreds of contributors, active issue triage, and regular releases that track new macOS versions. Power users wire it into their My Dotfiles repos, then rebuild entire Macs with one `darwin-rebuild switch` after a clean install.
Anyone who wants to see how this ecosystem fits together can start at the Nix & NixOS Official Website and the Nix Darwin repository. Together, they turn macOS from a hand-tuned snowflake into an artifact you can version, review, and roll back like any other code.
Flakes Aren't Just for Breakfast Anymore
Flakes sound like a breakfast cereal, but in Nix land they are the community’s de facto standard for reproducible configuration. Instead of a grab bag of shell scripts, Brewfiles, and sync tools, a single flake defines exactly what your Mac should look like: tools, services, even which Mac App Store apps you expect to exist. You get one source of truth, and Nix handles the rest.
At the center sits `flake.nix`, a plain text manifest written in the Nix language. It has two big ideas: inputs and outputs. Inputs describe what your config depends on; outputs describe what your config produces.
Inputs usually include: - `nixpkgs` (the main Nix package collection) - Other community Flakes (like `nix-darwin` modules) - Your own shared Flakes for work or side projects
Outputs turn those ingredients into something you can actually run. A `flake.nix` might expose: - `packages` for CLI tools and language runtimes - `darwinConfigurations` for full macOS setups - `devShells` for per-project environments
Run `darwin-rebuild switch --flake .#your-hostname` and Nix builds the outputs declared in your flake, then activates them on your Mac. Change `vim` to `neovim`, add `zellij`, rebuild, and your system flips to the new state in one shot. No manual `brew install`, no wondering which version manager controls which binary.
If `flake.nix` is the recipe, `flake.lock` is the vacuum‑sealed pantry. This JSON file pins every input to an exact Git revision or version, so when you and your teammates build from the same repo, you get identical toolchains and configs. No “works on my machine” surprises, no silent upgrades because `nixpkgs` moved on overnight.
There is a catch: Flakes remain officially “experimental” in Nix, hidden behind a feature flag. Reality on the ground looks different. Almost every modern Nix guide, starter template, and serious config — including popular setups like Nix Darwin on Mac — assumes you use Flakes because they are stable enough, powerful, and finally make Nix’s reproducibility story feel complete.
Building Your Immortal Mac, Step-by-Step
Installing Nix on macOS starts like any other dev tool: a curlable installer, a progress bar, and a brief wait while it wires itself into your shell. After that, the workflow immediately diverges from the Homebrew world. You create a dedicated `~/.nix` (or similar) directory, then drop in a starter Nix Darwin flake pulled from GitHub or a template like My Dotfiles.
That flake file becomes your Mac’s source of truth. It declares which packages, services, and system settings should exist on the machine, from CLI tools to Mac App Store apps. Instead of ad‑hoc `brew install` runs, you edit a single Nix expression that describes the entire system state.
Everything orbits one command: `darwin-rebuild switch`. Nix reads your flake, evaluates the configuration, builds whatever is missing, and atomically switches your Mac to that new state. No hunting through shell history, no guessing which dotfile toggled what.
Change a line in the config, run `darwin-rebuild switch`, and the machine snaps to match. In the video, the host replaces `vim` with neovim in the package list and adds `zellij` (misspelled as “zelage” in the transcript) even though it was never installed before. After the rebuild, `nvim` is available system‑wide, `zellij` runs instantly, and the old Vim binary no longer comes from Nix Darwin.
That edit‑and‑switch loop turns configuration into code review rather than ritual. Want to test a tool temporarily? Add it to the flake, rebuild, try it, then remove it and rebuild again; Nix garbage‑collects the unused versions. Every change is explicit, version‑controllable, and repeatable across Macs.
Compared to a traditional Homebrew setup, this replaces a sprawl of commands and scripts. A typical fresh machine might involve: - 30–80 `brew install` and `brew install --cask` calls - A `brew bundle dump` or MacUp export - Manual edits to dotfiles and macOS defaults
With Nix Darwin and flakes, you encode that entire ritual once. A new Mac only needs Nix installed and the flake cloned; `darwin-rebuild switch` reconstructs your environment in one shot, from CLI tools to system defaults and Mac App Store apps, no scavenger hunt required.
Nix Manages More Than You Think
Homebrew thinks in terms of command-line tools and casks; Nix thinks in terms of entire worlds. Once you have a Nix Darwin flake in place, your Mac stops being a one-off snowflake and starts behaving like a reproducible build artifact, from the shell you use to the way your Dock animates.
Most people discover Nix through `nixpkgs` and its tens of thousands of CLI packages, but the scope runs much wider. A single flake can declare your development tools, GUI apps, system services, fonts, and even launch agents, all pinned to exact versions so a fresh Mac in 2025 matches your 2023 laptop bit-for-bit.
Mac App Store integration is where jaws usually drop. With Nix Darwin wired up, you can declare apps by App ID and Nix will pull them from the Mac App Store as long as they’re tied to your Apple ID purchase history. Your config can literally say “install `409201541`” and you get Pages, or “`497799835`” and you get Xcode, no manual App Store clicking required.
That move quietly replaces a big chunk of what MacUp or hand-rolled backup scripts tried to solve. Instead of exporting a Brewfile and hoping you remember which App Store apps you used, your flake becomes the canonical list of everything that should exist on the machine, regardless of how Apple distributes it.
Nix Darwin modules push even deeper by treating macOS preferences as code. You can declaratively set Dock behavior, key repeat rate, trackpad tap-to-click, Finder visibility options, and hot corners using structured Nix options rather than brittle `defaults write` commands. A `darwin-rebuild switch` becomes the equivalent of clicking through a dozen System Settings panes in one shot.
That turns manual post-install rituals into a single commit. New machine? Clone your dotfiles, run one command, and your Dock auto-hides, key repeat hits your preferred 15 ms cadence, and your shell, fonts, and window manager appear exactly as before.
Anyone curious how far this goes can browse nix-darwin – Nix-based system configuration for macOS. Between those modules and a good flake, Nix doesn’t just replace Homebrew; it quietly absorbs backup tools, setup checklists, and years of muscle memory into a single, replayable script.
You Don't Have to Break Up with Homebrew
Homebrew diehards do not have to flip a table and rebuild everything in Nix on day one. A far less stressful strategy treats Nix as the conductor and Homebrew as one of the orchestra sections. You keep your existing tools, but you hand scheduling and state over to a single config file.
Modern Nix Darwin setups can actually manage Homebrew itself. In a flake, you enable the Homebrew module, point it at your existing installation, and then declare `brew.packages` and `casks` right alongside your Nix packages. When you run `darwin-rebuild switch`, Nix calls `brew install`, `brew uninstall`, and `brew upgrade` for you, so your Brew world becomes just another reproducible output.
This hybrid approach creates an ideal migration path. You can start by moving core CLI tools — `git`, `node`, `python`, `fzf`, `ripgrep` — into `environment.systemPackages` or Home Manager, while leaving GUI apps as Brew casks. Over time, you replace Brew formulae with Nix equivalents as you find them in nixpkgs.
Nixpkgs currently exposes more than 80,000 packages, but Homebrew Cask still wins on sheer coverage of Mac‑specific apps. That long tail includes obscure utilities, niche menu bar tools, and weird one‑off vendor installers that may never land in nixpkgs. Declaring those as Brew casks under Nix means you do not lose anything while you standardize the rest of your stack.
A typical “bridge” config ends up with three layers managed from one flake:
- Nix packages for all core CLI tools and language runtimes
- Nix modules for system defaults and dotfiles
- Homebrew casks for GUI apps and edge‑case utilities
You still run `darwin-rebuild switch` as your single source of truth. Behind the scenes, Nix updates itself, applies macOS defaults, syncs your dotfiles, and then drives Homebrew to install or prune whatever you listed, all without you touching `brew` directly.
Why Everyone Isn't Using Nix (Yet)
Most people bounce off Nix at exactly the point this video leans in: the “really, really steep learning curve.” Homebrew takes one command to install `wget`; Nix greets you with a new language, a new mental model, and a directory tree that looks like your Mac swallowed a hash function. Power users call that trade a bargain; everyone else calls it a weekend lost to documentation.
First wall: the Nix language itself. It looks a bit like JSON and a bit like Haskell, then does things neither of those do. You write attribute sets, lambdas, and modules just to say “install `neovim`,” and small syntax mistakes often surface as 10‑line errors about “attribute ‘packages’ missing.”
Second wall: functional programming concepts you never asked for. Nix insists everything be pure and immutable, so instead of “run this script,” you define derivations that describe what a build should be. That pays off in reproducibility, but it forces you to think in terms of inputs, outputs, and evaluation, not “run a bash one‑liner and hope.”
Then comes the Nix store, the `/nix/store` forest of content‑addressed paths that make Nix magic and maddening. Every build result lives under a hash like `/nix/store/abcd1234-neovim-0.9.1`, which guarantees isolation but makes ad‑hoc tinkering feel hostile. You do not “just edit a config file”; you edit a flake, rebuild, and let Nix garbage‑collect old generations.
Debugging derivations adds another layer of pain. When a package fails to build, you are staring at Nix expressions, builder scripts, and sometimes cross‑platform quirks inherited from NixOS. Homebrew errors usually say “missing dependency”; Nix errors often say “value is a function while a set was expected” and expect you to know why.
For simple installs, Nix absolutely looks like overkill. If you only need `node`, `python`, and `ffmpeg`, Homebrew’s `brew install` feels faster, clearer, and far less intrusive than enabling flakes, wiring Nix Darwin, and committing a `flake.nix` just to get a shell. The video even shows that initial setup stretching across multiple commands, flags, and config edits before anything fun happens.
Among fans, Nix has earned a backhanded compliment: the “worst package manager, except for all the others.” You pay upfront in complexity, obscure docs, and head‑scratching errors, and in return you get a system that will recreate your entire Mac on demand. For many developers, that power finally outweighs the pain—just not on day one.
The God-Tier Workflow Awaiting You
God-tier payoff with Nix looks like this: a brand-new Mac goes from shrink wrap to fully armed dev workstation in under 10 minutes. You sign into Git, clone your My Dotfiles repo, run a single `darwin-rebuild switch --flake .`, and watch your editor, shells, language toolchains, fonts, and even Mac App Store apps reappear exactly as before. No hunting down installers, no “oh right, I forgot to install jq.”
Under the hood, Nix treats your entire environment as data. Your Nix Darwin flake declares every package, service, and macOS default as code, so the state of your machine becomes a pure function of one Git commit. Roll back that commit and Nix atomically rolls back your tools, configs, and system tweaks in one shot.
For teams, this flips onboarding from a multi-hour ritual into a single documented command. A new hire pulls the repo, runs the flake, and lands in an environment identical to production and to every other engineer’s laptop—same `node` version, same `postgres`, same CLI tools, same dotfiles. “Works on my machine” dies when “my machine” is a reproducible Nix build.
Day-to-day work gets safer too. Want to try Python 3.13, a bleeding-edge `clang`, or a new `zellij` config? You pin them in a branch, run `darwin-rebuild`, and Nix builds the new generation alongside the old one. If anything misbehaves, a single rollback command restores the previous generation, with no dangling symlinks or half-uninstalled packages.
That atomic model encourages experimentation you’d never risk on a hand-tuned Homebrew setup. You can maintain: - A stable “work” flake - A “beta” flake with new tools - Per-project flakes with pinned dependencies
Switching between them becomes a matter of seconds, not a weekend of reinstalling Xcode Command Line Tools.
Developers already document this workflow in the wild; guides like Nix on macOS – the better Homebrew walk through replacing Homebrew entirely with declarative Nix configs. Once you experience a fresh Mac materializing from a Git commit, every ad-hoc setup script feels like bashing your environment together with rocks.
Will You Take the Red Pill?
Curious but Nix-pilled only in theory? Start by treating Nix like a smarter, disposable virtualenv rather than a new religion for your Mac. Install Nix, open a project folder, and run `nix-shell -p <package>` to spin up an isolated environment that disappears when you close the shell. No global installs, no `brew uninstall` cleanup, no risk to your existing stack.
Use that one command as your training wheels. Need `jq`, `nodejs`, and `postgresql` for a side project? Run `nix-shell -p jq nodejs postgresql` and work inside that shell. You get reproducible tools per project without touching your system PATH or rewriting your dotfiles.
Once that feels normal, start capturing these ad‑hoc shells in simple `.nix` files checked into your repo. A minimal `shell.nix` that pins `nodejs-20_x` and `pnpm` already beats a README line that says “install Node somehow.” Teammates run `nix-shell` and land in the exact same environment, regardless of how cursed their Homebrew setup looks.
When you’re ready to see what a full declarative Mac looks like, raid other people’s configs. Browse My Dotfiles from the video author on GitHub: My Dotfiles. Then search GitHub for `nix-darwin flake.nix` and study how people wire up:
- `nix-darwin` modules for system services
- `home-manager` for user-level dotfiles
- `mas` integration for Mac App Store apps
Treat those repos like living documentation. Copy a tiny piece—a font, a single CLI tool, one system default—into your own flake and run `darwin-rebuild switch`. If it breaks, you revert a file and you are back, no `brew doctor` séance required.
So here’s the uncomfortable question: does the short-term comfort of `brew install` justify a future where your Mac remains a one-off snowflake you pray never dies? Or are you ready to spend a weekend learning Nix so your next laptop, next job, and next clean install become a one-command ritual instead of a week-long scavenger hunt?
Frequently Asked Questions
What is Nix and why is it different from Homebrew?
Nix is a functional package manager that uses a declarative configuration file to define your entire system state. Unlike Homebrew's imperative commands (`brew install`), Nix builds your environment based on a manifest, ensuring it's reproducible and consistent everywhere.
Can Nix completely replace Homebrew on macOS?
Yes, for many developers, Nix (via Nix Darwin) can replace Homebrew, version managers, and dotfile managers. It can even manage Homebrew itself to install GUI apps or packages not yet in the Nixpkgs repository, offering a single point of control.
What are Nix Flakes and why are they important?
Nix Flakes are the modern, standard way to define Nix projects. They are declarative manifests that lock dependencies, making your configurations self-contained and perfectly reproducible, which is a major advantage over traditional package management.
Is Nix actually hard to learn?
Yes, Nix has a notoriously steep learning curve due to its functional programming language and new concepts like derivations and the Nix store. However, advocates argue the initial investment pays off with unparalleled control and reproducibility.