Keystone SystemsKS Systems

How Home Manager Replaces Homebrew and Dotfiles

The traditional Mac developer setup involves Homebrew for packages and a dotfiles repository for configuration. Home Manager offers a unified, declarative alternative.

The Traditional Mac Dev Setup

A typical workflow:

  1. Install Homebrew
  2. Run brew install git vim tmux ...
  3. Clone your dotfiles repo
  4. Run an install script that symlinks configs

Over time:

  • Packages drift between machines
  • The dotfiles repo grows scripts to handle edge cases
  • "Did I install that with brew or was it a manual install?"
  • Configuration changes happen ad-hoc, sometimes forgotten

What Home Manager Provides

Home Manager manages your user environment through Nix:

# home.nix
{ pkgs, ... }: {
  home.packages = [
    pkgs.git
    pkgs.vim
    pkgs.tmux
    pkgs.ripgrep
    pkgs.fd
  ];

  programs.git = {
    enable = true;
    userName = "Your Name";
    userEmail = "you@example.com";
    extraConfig = {
      init.defaultBranch = "main";
      pull.rebase = true;
    };
  };

  programs.zsh = {
    enable = true;
    shellAliases = {
      ll = "ls -la";
      gs = "git status";
    };
  };
}

Apply with home-manager switch. Everything updates atomically.

Migration Path

Step 1: Install Nix

Use the Determinate Systems installer (see docs):

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

Step 2: Enable Home Manager

Add to your flake or install standalone:

# flake.nix
{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
    home-manager.url = "github:nix-community/home-manager";
    home-manager.inputs.nixpkgs.follows = "nixpkgs";
  };

  outputs = { self, nixpkgs, home-manager, ... }: {
    homeConfigurations."youruser" = home-manager.lib.homeManagerConfiguration {
      pkgs = nixpkgs.legacyPackages.aarch64-darwin;
      modules = [ ./home.nix ];
    };
  };
}

Step 3: Migrate Packages Gradually

Start with packages you use daily. Don't try to migrate everything at once.

# List what Homebrew has installed
brew list

# Add equivalents to home.packages
# Remove from Homebrew as you verify they work
brew uninstall <package>

Step 4: Convert Dotfiles to Nix

Instead of symlinked dotfiles:

# Old: ~/.config/git/config symlinked from dotfiles repo
# New: Generated by Home Manager

programs.git = {
  enable = true;
  # ... configuration
};

# For files without native HM support:
home.file.".config/some-app/config.toml".text = ''
  setting = "value"
'';

Advantages Over Brew + Dotfiles

Single Source of Truth

One repository, one command to apply. No separate package list and config repo to keep in sync.

Atomic Updates

home-manager switch either succeeds completely or fails completely. No half-applied states.

Easy Machine Migration

Clone your config, run home-manager switch. New machine matches old machine exactly.

Version-Pinned Dependencies

Flake lock files pin exact versions. Update when you choose, not when upstream decides.

Rollback

Previous generations remain available. Something break? home-manager generations lists history.

Coexistence Strategy

You don't have to go all-in immediately:

  • Keep Homebrew for GUI apps (or explore nix-darwin for those too)
  • Use Home Manager for CLI tools and configs
  • Migrate incrementally as you gain confidence

The goal is reducing configuration drift and gaining reproducibility, not religious purity.