---
title: "Declarative macOS with Nix: A Practical Guide"
url: https://monogram.io/blog/declarative-macos-with-nix-a-practical-guide
date: 2026-01-21
authors: ["Julien"]
---

# Declarative macOS with Nix: A Practical Guide

**Published:** Jan 21, 2026 | **Authors:** Julien, Full-Stack Developer | **Categories:** Engineering

---

If you've been using a Mac for development, you're probably familiar with the ritual: install Homebrew, `brew install` your tools, scatter dotfiles across your home directory, and hope you remember what you did when it's time to set up a new machine.

I recently migrated my entire macOS setup to Nix, and while the learning curve was real, the payoff is worth it. This guide walks through why you'd want to do this and how to actually pull it off.

### Why Bother?

The pitch for Nix is simple: **declarative, reproducible system configuration**. Instead of running commands and hoping you remember them later, you define your entire setup in version-controlled config files. New machine? Clone your repo, run one command, done.

Here's what sold me:

**Single source of truth:** One directory contains your entire system config

**Reproducible:** The same config produces the same system, every time

**Atomic updates:** Changes are transactional—if something breaks, roll back instantly

**Version controlled:** Git tracks every change to your setup

**Scalable for Agencies:** For teams, this solves onboarding. We can spin up new machines for coworkers with identical, secure environments instantly. It eliminates the "it works on my machine" friction because everyone is essentially running the exact same machine.

#### The AI Advantage

There is a modern bonus to this approach: Nix is incredibly AI-friendly. Because your system state is defined explicitly in code, AI tools (like Claude Code or Cursor) can easily read, understand, and maintain your environment. Instead of an agent having to guess if a tool is hidden in global npm, a Homebrew cellar, a DMG manual install, or the Mac App Store, it simply looks at your config file. It knows exactly where everything is, making automated updates and dependency management reliable rather than a guessing game.

The tradeoff is complexity. Nix has its own language, its own package manager, and concepts that take time to internalize. But if you've ever spent an afternoon trying to recreate your setup on a new laptop, that complexity starts looking like a fair trade.

### The Stack

We'll use three tools:

**nix-darwin**: System-level macOS configuration (packages, system preferences, services)

**home-manager**: User-level config (dotfiles, shell, programs)

**Flakes**: The modern way to pin dependencies and make builds reproducible

The key insight: we're not going all-or-nothing. Homebrew stays for GUI apps (managing casks declaratively through nix-darwin), while CLI tools move to Nix.

### Getting Started

First, install Nix. The Determinate installer is the smoothest option:

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

Create your config directory:

```bash
mkdir -p ~/.config/nix/{hosts/mbp,home,dotfiles}
cd ~/.config/nix
git init
```

### The Flake

The `flake.nix` is your entry point. It declares inputs (where to get nix-darwin, home-manager, and nixpkgs) and outputs (your system configuration):

```bash
{
  description = "My nix-darwin configuration";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    nix-darwin = {
      url = "github:LnL7/nix-darwin/master";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = inputs@{ self, nixpkgs, nix-darwin, home-manager }:
    {
      darwinConfigurations."mbp" = nix-darwin.lib.darwinSystem {
        system = "aarch64-darwin";
        modules = [
          ./hosts/mbp/configuration.nix
          home-manager.darwinModules.home-manager
          {
            users.users.yourname = {
              name = "yourname";
              home = "/Users/yourname";
            };
            home-manager.useGlobalPkgs = true;
            home-manager.useUserPackages = true;
            home-manager.backupFileExtension = "backup";
            home-manager.users.yourname = import ./home/home.nix;
          }
        ];
      };
    };
}
```

### System Configuration

The `hosts/mbp/configuration.nix` handles system-level stuff—packages, Homebrew casks, and macOS preferences:

```bash
{ pkgs, ... }:

{
  system.primaryUser = "yourname";

  # CLI tools (replaces brew install)
  environment.systemPackages = with pkgs; [
    bat fzf delta gh lazygit lsd ripgrep tree
  ];

  # GUI apps (declarative Homebrew)
  homebrew = {
    enable = true;
    onActivation.cleanup = "zap";
    casks = [ "raycast" "slack" "linear-linear" "ghostty" ];
  };

  # macOS preferences
  system.defaults = {
    dock.autohide = true;
    finder.AppleShowAllFiles = true;
    NSGlobalDomain.KeyRepeat = 2;
  };

  # Determinate installer manages Nix itself
  nix.enable = false;

  system.stateVersion = 5;
  nixpkgs.hostPlatform = "aarch64-darwin";
}
```

The `homebrew.onActivation.cleanup = "zap"` is aggressive—it removes any cask not in your list. Great for keeping things tidy, but be aware of what you're opting into.

### Home Manager

The `home/home.nix` handles user-level config—your shell, git, and dotfiles:

```bash
{ config, pkgs, lib, ... }:

{
  programs.zsh = {
    enable = true;
    shellAliases = {
      cat = "bat --style=header,grid";
      ls = "lsd";
      lg = "lazygit";
    };
    initContent = ''
      eval "$(fnm env --use-on-cd --shell zsh)"
      # ... rest of your shell config
    '';
  };

  programs.git = {
    enable = true;
    settings = {
      user.name = "Your Name";
      user.email = "you@example.com";
      push.autoSetupRemote = true;
    };
  };

  programs.starship.enable = true;

  home.stateVersion = "24.11";
}
```

### Building and Activating

With your config files in place:

```bash
nix build .#darwinConfigurations.mbp.system
sudo ./result/sw/bin/darwin-rebuild switch --flake .#mbp
```

The first run takes a while—Nix downloads and builds everything. Subsequent rebuilds are fast since unchanged packages are cached.
If you hit errors about existing files, Nix is being cautious. You might need to:

```bash
sudo mv /etc/zshenv /etc/zshenv.before-nix-darwin
```

### Day-to-Day Usage

Installing a new CLI tool:

Add it to `environment.systemPackages` in your config

Run `darwin-rebuild switch --flake ~/.config/nix#mbp`

Installing a new GUI app:

Add it to `homebrew.casks`

Run the same rebuild command

To search for packages:

```bash
nix search nixpkgs ripgrep
```

Or use [search.nixos.org](https://search.nixos.org/packages) for a faster web interface.

### The New Machine Test

The real payoff comes when you set up a new Mac:

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

# Clone your config
git clone git@github.com:you/nix-config.git ~/.config/nix

# Build and activate
cd ~/.config/nix
nix build .#darwinConfigurations.mbp.system
sudo ./result/sw/bin/darwin-rebuild switch --flake .#mbp
```

That's it. Your entire development environment, installed and configured.

![Image](https://cdn.sanity.io/images/zep746qw/production/8b0a2cd36aeff2f610125c66465c834a891d6b7c-3024x1964.png)

### Gotchas

A few things I ran into:

**nix-darwin updates frequently**: Options get renamed. When you see deprecation warnings, check the [nix-darwin options search](https://daiderd.com/nix-darwin/manual/index.html)

**Secrets stay manual**: SSH keys, GPG keys, API tokens—these don't go in your repo. Transfer them separately

**Shell needs reloading**: After `darwin-rebuild switch`, open a new terminal to see changes

### Worth It?

For me, yes. There's something satisfying about `git diff`-ing your system configuration.

This isn't the only way to manage a Mac setup—tools like chezmoi or plain dotfile repos work fine for many people. But if reproducibility matters to you, Nix is worth the learning curve.

The full config from this guide is on [GitHub](https://github.com/JulienMartel/nix-config). Go fork it and make it yours.

## Contact Us

[Build what comes next](https://monogram.io/#contact) | Email: [hello+llm@monogram.io](mailto:hello+llm@monogram.io)

Provide your contact details and claim a time to meet. We'll help you do the rest.

Monogram is an Applied AI Studio based in Atlanta that designs and deploys production AI systems — connecting models, data, workflows, and enterprise software to automate real work.

The contact form collects: name, email, company, estimated budget (USD), a project description, and how you heard about Monogram.

Not sure where to start? Tell us what you're trying to automate or build — we'll help you figure out where AI fits.


---

**More content:** [Home](/.md) | [About](/about.md) | [Work](/work.md) | [Engineering](/engineering.md)

Monogram helps companies, organizations, and firms adopt AI by designing and building proof of concepts (POCs), MVPs, intelligent workflows, and production-ready AI applications.

Website designed and developed by [Monogram](https://monogram.io)