# Moving from Homebrew to NixOS

Home Lab · 2026-10-07 · 3 min read · by Matt Oddie
Canonical: https://mattoddie.dev/homelab/homebrew-to-nixos/

> How to move from Homebrew to NixOS: turn a Brewfile into a flake, map packages, replace brew services with systemd and give each project its own dev shell.

Homebrew keeps a machine's tools in a list you add to over time. NixOS keeps the whole machine in a file you rebuild from. Moving between them is mostly a translation job, and it goes faster if you treat it as one.

## Take an inventory

Before installing anything, write down what Homebrew is doing for you today. `brew bundle dump` writes every tap, formula, cask and Mac App Store app to a Brewfile, which becomes the checklist for the rest of the move.

On the old machine:

```sh
# Everything installed, as a checklist
brew bundle dump --file=Brewfile

# Only the formulae you asked for, not their dependencies
brew leaves

# Background services Homebrew is running
brew services list
```

`brew leaves` matters more than the full list. Dependencies come along automatically in Nix as well, so you only need to translate the packages you chose. In a typical Brewfile that removes well over half the lines.

## Map the names

Most command-line tools have the same name in nixpkgs. The exceptions are versioned formulae, where Homebrew uses `@` and nixpkgs uses an underscore or a separate attribute. [search.nixos.org](https://search.nixos.org/packages) or `nix search nixpkgs <name>` settles any doubt.

| Homebrew | nixpkgs | Where it goes |
| --- | --- | --- |
| `ripgrep`, `fd`, `jq`, `gh` | same names | `home.packages` |
| `node@22` | `nodejs_22` | project dev shell |
| `python@3.12` | `python312` | project dev shell |
| `awscli` | `awscli2` | `home.packages` |
| `postgresql@16` + `brew services` | `services.postgresql` | `configuration.nix` |
| `git` and its config | `programs.git` | home-manager |
| casks such as `visual-studio-code` | `vscode` (unfree) | `home.packages` |

The third column is the real decision. NixOS gives you three places to put a package, and choosing well keeps the configuration tidy.

- **System configuration** for services and anything every user needs: databases, Docker, SSH, fonts.
- **home-manager** for your own tools and dotfiles. It replaces both `brew install` and the dotfiles repo you were symlinking by hand.
- **Per-project dev shells** for language runtimes. A project pins its own Node or Python version, so nothing needs to be installed globally.

## Start from a flake

A flake pins the exact nixpkgs revision in `flake.lock`, which is the equivalent of Homebrew never upgrading anything until you say so. This is a minimal one that wires home-manager into the system build, so one command updates both.

flake.nix:

```nix
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
    home-manager.url = "github:nix-community/home-manager/release-26.05";
    home-manager.inputs.nixpkgs.follows = "nixpkgs";
  };

  outputs = { nixpkgs, home-manager, ... }: {
    nixosConfigurations.workbench = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        ./configuration.nix
        home-manager.nixosModules.home-manager
        {
          home-manager.useGlobalPkgs = true;
          home-manager.useUserPackages = true;
          home-manager.users.matt = import ./home.nix;
        }
      ];
    };
  };
}
```

The Brewfile's command-line tools then become a list in `home.nix`, and tools with configuration get a `programs.*` module instead of a dotfile:

home.nix:

```nix
{ pkgs, ... }: {
  home.stateVersion = "26.05";

  home.packages = with pkgs; [
    ripgrep fd jq gh awscli2 vscode
  ];

  programs.git = {
    enable = true;
    settings.user.name = "Matt Oddie";
    settings.init.defaultBranch = "main";
  };

  programs.direnv = {
    enable = true;
    nix-direnv.enable = true;
  };
}
```

## Replace brew services with systemd

Everything in `brew services list` becomes a NixOS service option. These are better than their Homebrew equivalents in one respect: the service, its config file, its user and its data directory all come from the same few lines, so rebuilding a machine brings the whole service back.

configuration.nix (excerpt):

```nix
{ pkgs, ... }: {
  nixpkgs.config.allowUnfree = true;  # vscode and friends

  services.postgresql = {
    enable = true;
    package = pkgs.postgresql_16;
    ensureDatabases = [ "app_dev" ];
  };

  virtualisation.docker.enable = true;
  users.users.matt.extraGroups = [ "docker" ];
}
```

## Move runtimes into projects

With Homebrew, one version of Node serves every project on the machine. On NixOS each project can carry a small flake with a `devShells.default` listing what it needs, and direnv loads it when you `cd` into the directory. Adding `use flake` to the project's `.envrc` is the whole setup. Two projects on different Node versions stop being a problem you manage.

If you already use mise or asdf with a `.tool-versions` file, that keeps working on NixOS too. It's a reasonable halfway step while you convert projects one at a time.

> **Downloaded binaries won't run.** NixOS has no `/lib64/ld-linux-x86-64.so.2`, so prebuilt binaries from release pages, npm postinstall scripts and editor extensions fail with "No such file or directory". Set `programs.nix-ld.enable = true;` in the system configuration and most of them start working.

## What doesn't translate

- **Mac App Store entries** in the Brewfile have no equivalent. Find the Linux version or an alternative for each.
- **Apps that update themselves** fight the read-only Nix store. Install them from nixpkgs and let the flake update them, or run them as a Flatpak.
- **Taps** for niche tools sometimes have no nixpkgs package. Check for a flake in the tool's repository first, since many projects now ship one, and package it yourself only as a last resort.

## The new upgrade loop

`brew update && brew upgrade` becomes two commands. Update the lock file, then rebuild:

In the flake directory:

```sh
nix flake update
sudo nixos-rebuild switch --flake .#workbench

# Something broke? Go back to the previous generation
sudo nixos-rebuild switch --rollback
```

The rollback is what you couldn't do with Homebrew. Every rebuild is a generation you can return to from the command line or the boot menu, so an upgrade that breaks something takes one command to undo. Keep the flake in Git and work down the Brewfile line by line. Once every entry is either translated or deliberately dropped, the move is done.
