Reproducible macOS Configurations with Nix
What is Nix?
Nix is a powerful package manager 1 for Linux/Unix systems that offers a declarative approach to system configuration. With Nix, you define your system’s state in a configuration file (known as a flake), ensuring that your setup is reproducible across different machines.
A key component of the Nix ecosystem is Nixpkgs, the default repository of Nix packages, which contains thousands of searcheable packages that you can easily integrate into your configuration.
Why Use Nix on macOS?
If you use macOS, you are likely familiar with Homebrew. Homebrew is a great package manager, and it does this one thing very well. Nix, on the other hand, is a comprehensive system configuration solution.
It offers many advanced features, such as binary caching, package pinning, package overrides, automatic runtime dependencies, and custom package sources.
One particularly interesting part of the Nix ecosystem for macOS users is nix-darwin
, a set of tools and configurations that allows you to manage your macOS system configuration using Nix. This is what we will go over in this post.
Installing Nix
There are multiple ways to install Nix on your macOS, but I found that the most reliable way is to use the DeterminateSystems/nix-installer script by running 2
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
Setting up nix-darwin
Once you have Nix installed, you can set up nix-darwin
by running the following command:
# Create and navigate to a new directory for your nix-darwin configuration
cd $(mkdir -p ~/.config/nix-darwin-config && echo $_)
# initialize a new nix flake with the nix-darwin template
nix flake init -t nix-darwin
This last command will create a flakes.nix file in your nix-darwin-config
directory. This file is where you will define your system configuration. You will want to change the following configuration options:
nixpkgs.hostPlatform
toaarch64-darwin
(for Apple Silicon) orx86_64-darwin
(for Intel Macs).nixpkgs.config.allowUnfree
totrue
if you want to allow unfree packages (e.g. steam).
you can also add any system package you need in the environment.systemPackages
array, for example:
environment.systemPackages =
[ pkgs.vim
pkgs.pet
pkgs.lazygit
pkgs.lazydocker
pkgs.fzf
];
This is a minimal configuration to get you started. You can add more packages and customizations as needed. A full list of available options can be found in the nix-darwin manual.
I like to keep my configuration in a git repository, along with my dotfiles, so I can easily share it across machines.
Your configuration file will probably look something like this:
{
description = "Victor's MacOS Nix System flake";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
nix-darwin.url = "github:LnL7/nix-darwin";
nix-darwin.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = inputs@{ self, nix-darwin, nixpkgs }:
let
configuration = { pkgs, ... }: {
# List packages installed in system profile. To search by name, run:
# $ nix-env -qaP | grep wget
environment.systemPackages =
[ pkgs.vim
pkgs.fzf # Fuzzy finder
pkgs.pet # Snippet manager
pkgs.lazydocker # Docker TUI
pkgs.lazygit # Git TUI
pkgs.ripgrep # Faster grep
pkgs.zoxide # Directory jump tool (z)
pkgs.pyenv # Python version manager
pkgs.gh # GitHub CLI
pkgs.go # Go programming language
pkgs.kubectl # Kubernetes CLI
pkgs.kubectx # Kubernetes context switcher
pkgs.tenv # OpenTofu, Terraform, Terragrunt and Atmos version manager
pkgs.delta # Terminal git diff viewer with syntax highlighting
pkgs.curl # Command line tool for transferring data with URL syntax
pkgs.jq # Command line JSON processor
pkgs.yq # Command line YAML processor
pkgs.just # Command runner, similar to make
pkgs.vscode # Visual Studio Code
pkgs.neovim # Vim-fork focused on extensibility and usability
pkgs.helix # Vim-like modal text editor
pkgs.k9s # Kubernetes CLI to manage your clusters in styles
pkgs.pandoc # Universal document converter
pkgs.python3 # Python 3 programming language
pkgs.rustc # Rust programming language
pkgs.rustup # Rust toolchain installer
pkgs.lorri # Nix shell manager
pkgs.htop # Interactive process viewer
pkgs.tree # Display directories as trees
pkgs.jetbrains-mono # JetBrains Mono font
pkgs.ffmpeg # Multimedia framework
pkgs.alacritty # GPU-accelerated terminal emulator
];
# allowUnfree is required to install some packages that are not "free" software.
nixpkgs.config.allowUnfree = true;
# Auto upgrade nix package and the daemon service.
services.nix-daemon.enable = true;
# nix.package = pkgs.nix;
# Necessary for using flakes on this system.
nix.settings.experimental-features = "nix-command flakes";
# Create /etc/zshrc that loads the nix-darwin environment.
programs.zsh.enable = true; # default shell on catalina
# programs.fish.enable = true;
# Set Git commit hash for darwin-version.
system.configurationRevision = self.rev or self.dirtyRev or null;
# Used for backwards compatibility, please read the changelog before changing.
# $ darwin-rebuild changelog
system.stateVersion = 4;
system.defaults = {
dock.autohide = true;
dock.mru-spaces = false; # Most Recently Used spaces.
finder.AppleShowAllExtensions = true;
finder.FXPreferredViewStyle = "icnv"; # icon view. Other options are: Nlsv (list), clmv (column), Flwv (cover flow)
screencapture.location = "~/Pictures/screenshots";
screensaver.askForPasswordDelay = 10; # in seconds
};
# The platform the configuration will be used on.
nixpkgs.hostPlatform = "aarch64-darwin";
nix.extraOptions = ''
extra-platforms = x86_64-darwin aarch64-darwin
'';
};
in
{
# Build darwin flake using:
# $ darwin-rebuild build --flake .#simple
darwinConfigurations."simple" = nix-darwin.lib.darwinSystem {
modules = [ configuration ];
};
# Expose the package set, including overlays, for convenience.
darwinPackages = self.darwinConfigurations."simple".pkgs;
};
}
Applying the Configuration
To apply your configuration, run the following command:
nix run nix-darwin -- switch --flake .
to update your configuration, you can run:
nix flake update
darwin-rebuild switch --flake .
Applying the same configuration on another machine
To apply the same configuration on another machine, you will need to:
- Install
nix
, andnix-darwin
as described above. nix run nix-darwin -- --flake github:your-github-username/your-repository#your-config
3
-
Actually, Nix is more than just a package manager. It is a complete system configuration solution that can be used to manage your entire system configuration, including user environments, system services, and more. Nix is a functional language, a package manager, a Linux distribution, and more. This can be confusing at first. Here we will focus on 3 aspects only: the package manager and the system configuration (with
nix-darwin
), and the programming language used to define the configuration. ↩︎ -
check the repository for a more up-to-date installation instruction. ↩︎
-
replace
your-github-username/your-repository#your-config
with your git repository and branch. ↩︎