Nix on macOS: one flake, every machine
I run the same development environment on a MacBook Pro, inside WSL2 on a Windows machine, and on NixOS VMs. Not “similar” — identical. Same packages, same shell config, same editor plugins. One change propagates everywhere.
The tool that makes this possible is Nix, and the config lives at github.com/slimslenderslacks/nixos-config.
Getting started on macOS
The same config works on any machine — darwin, WSL2, NixOS — but if you’re on a Mac, here’s the shortest path:
-
Install Nix (with flakes):
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install -
Clone the repo and run
make:git clone https://github.com/slimslenderslacks/nixos-config cd nixos-config NIXNAME=macbook-pro-m1 make
That’s it. On first run, darwin-rebuild is bootstrapped directly from the build result — nothing needs to be pre-installed beyond Nix itself.
I now have a fully reproducible working environment that I can stamp out anywhere. The github repo is public — and that’s fine — this is not how I manage credentials or secrets. The output of this process is a fully configured enviroment but any secrets are intentionally left unbound.
The problem Nix solves
Most approaches to environment management are per-platform: Homebrew on macOS, apt on Linux, something ad-hoc on WSL. Dotfiles help with shell config, but they don’t manage the tools themselves. The result is drift — the thing that works on your laptop doesn’t quite work on the server.
Nix takes a different approach. Every package is described as a pure function of its inputs, stored in an immutable path like /nix/store/abc123-ripgrep-14.1.0/. No global state, no install-time side effects. This means a Nix expression that builds ripgrep on macOS is the same expression that builds it on NixOS or inside WSL — the output is guaranteed to be identical.
One flake, three platforms
The entire config is driven from a single flake.nix. A central function called mksystem.nix takes a machine name and a few flags (darwin, wsl) and produces either a darwinSystem or nixosSystem. Critically, all three platform outputs share the same home-manager.nix — the file that defines my actual user environment: packages, dotfiles, shell config, neovim.
graph LR
subgraph Inputs["flake inputs"]
nixpkgs["nixpkgs 25.05"]
nix_darwin["nix-darwin"]
home_manager["home-manager"]
overlays["overlays"]
nixpkgs ~~~ nix_darwin ~~~ home_manager ~~~ overlays
end
subgraph Fn["mkSystem ( machine, platform )"]
direction TB
machines["machines/ — hardware specifics"]
users["users/slim/ — packages · dotfiles · shell"]
end
macos["macOS\nnix-darwin"]
wsl["WSL2\nroot tarball"]
linux["NixOS / any Linux VM"]
Inputs --> Fn
Fn -->|output| macos
Fn -->|output| wsl
Fn -->|output| linux
The platform-specific layers (darwin.nix, a machine .nix file) handle only what’s truly platform-specific: Homebrew casks on macOS, kernel settings on NixOS, WSL-specific tweaks. Everything else — the tools I actually use day to day — lives in home-manager.nix and is identical everywhere.
How macOS fits in
macOS doesn’t run NixOS, so there’s no NixOS init system or service manager to manage. Instead, nix-darwin fills that role. It provides a darwin-rebuild switch command analogous to nixos-rebuild switch, a module system for macOS system config, and integration with Homebrew for GUI apps and macOS-specific tools that work better as casks.
The Nix daemon itself is installed separately — I use the Determinate Systems nix-installer, which sets up /nix and enables flakes. After that, nix-darwin takes over and manages everything else declaratively.
What changes when you update
When I add a package to home-manager.nix and run make switch on the Mac, the same change is waiting for the WSL and NixOS environments the next time I rebuild them. The flake lock pins all inputs, so every machine builds against the same version of nixpkgs. No surprises.
The config is on GitHub. It’s a fork of Mitchell Hashimoto’s nixos-config, adapted for an Apple Silicon MacBook as the primary machine with WSL and NixOS VMs in the mix.