I have been managing my dotfiles using Nix with Home Manager for quite a long time. While I have been using it to make my life much easier I know that getting started with it can be quite an uphill battle because of how much you need to get right and the documentation for Nix, Home Manager, and etc. are constantly changing as Nix and the ecosystem are continually improved.
But what is Home Manager? Home Manager is a community project written in Nix that lets you define how you want to configure your dotfiles and what packages you want to be included in your shell. Anything available in Nixpkgs can be included including things like ripgrep, fzf, and even a custom python 3 environment.
Home Manager is a nice way to get started with Nix because it helps keep things in a source control repo, so it’s easy to replicate, and yet not require using NixOS to get the advantages Nix provides. Home Manager works well on macOS, Linux, and WSL. It is also easy to integrate with NixOS if you ever wanted to use that as well.
These instructions will assume you’re using macOS because that is often the trickiest to get setup correctly.
Getting Started Link to heading
First you’ll need to install Nix. This page tells you what command to run (defaults to macOS instructions).
The command is pretty much always:
sh <(curl -L https://nixos.org/nix/install) --daemon
Errors you may see later:
- “NIX_PATH unset”: You are using an old command or missing a
--flake .
flag. This happens because by default channels, the old way of maintaining which version of Nixpkgs you use, is not set by default with flakes. - “home.nix no configuration found” : This is likely related to using the wrong command and not telling it to use your flake. If you don’t tell it where to look it’ll look in the default location which is not what we want.
Enable Nix Flakes and Experimental Commands Link to heading
Setup your ~/.config/nix/nix.conf
to allow flakes.
mkdir -p ~/.config/nix
echo 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf
Creating basic home-manager flake configuration Link to heading
- Create a dotfiles folder and run
git init
thennix flake init
inside it. - Setup your basic
flake.nix
like this:
{
description = "My Home Manager Flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = {nixpkgs, home-manager, ...}: {
homeConfigurations = {
"your.username" = home-manager.lib.homeManagerConfiguration {
# System is very important!
pkgs = import nixpkgs { system = "aarch64-darwin"; };
modules = [ ./home.nix ]; # Defined later
};
};
};
}
Some notes:
- Reminder: we’re assuming macOS with an M1+ processor of some kind.
- To use it on x86_64 linux change
*-darwin
tox86_64-linux
.
- To use it on x86_64 linux change
- Other posts you may find on how to setup home-manager with flakes will tell you to put more options in
homeConfigurations."your.username"
, but that was the old way of doing things and these are the only options are needed. - On Apple M1/M2 systems many recommended in the past using x86_64 dotfiles and relying on Rosetta, but that is not needed. It is possible to install x86_64 packages using Home Manager if needed, but
aarch64-darwin
is perfectly fine.
Home manager config Link to heading
Your home.nix
should look like this:
{pkgs, ...}: {
home.username = "your.username";
home.homeDirectory = "/home/your.username";
home.stateVersion = "24.11"; # Comment out for error with "latest" version
programs.home-manager.enable = true;
}
- Make sure to
git add home.nix
otherwise Nix will ignore it. Once a file is tracked by git flake will stop ignoring it. - Run
nix build .#homeConfigurations."your.name".activationPackage
to build the home-manager config.
your.username
in the examples.When nix builds something it will create a symlink to the result at ./result
. You can look in the ./result/
folder to see what it built and what config files you’ll have. It’s worth exploring this folder a bit, but for now it’ll probably be a little overwhelming.
Activate the config Link to heading
- When building works you can try to activate the new configuration using:
./result/activate
- Restart your shell or run
exec $SHELL -l
.
We’re not using the home-manager
command right now because we haven’t yet installed it, but normally you can build and switch to new versions of your configuration using home-manager {build|switch} --flake .
Right now we’re basically doing some steps manually that home-manager
would do for you, but also are useful for when something goes horrifically wrong and home-manager
doesn’t exist anymore.
Customizing your home configuration Link to heading
- Modify
home.packages
inhome.nix
to add new packages to your environment.
{pkgs, ...}: {
# Add this somewhere in `home.nix`
home.packages = [
pkgs.nixfmt-rfc-style
pkgs.cowsay
];
}
If you have reloaded your shell you can apply these changes by saying home-manager switch --flake .
in the root of your flake files.
Because we’re using flakes we must tell home manager which flake we’re referring to unlike before where it assumed where the config file would be.
Now you can refer to the home-manager docs for other configuration options (of which there are many).
I published my dotfiles which you can look at as an example, although they can get a little hectic as I am routinely changing things.
Extra: How to build the same config for different architectures? Link to heading
If you noticed the homeConfigurations."your.name" = ...
in flake.nix
hardcodes the system variable to aarch64-darwin
.
One problem with this is that building this configuration for linux (x86_64 or ARM) will not work. Normally the way to fix this in nix is to duplicate the configuration for each architecture we want and there are libraries like flake-utils which are widely used to help in situations like this.
To fix this we need to put in legacyPackages
a version of homeConfigurations
for each arch we want to support.
An example flake.nix
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/release-24.11";
nixpkgs-unstable.url = "github:nixos/nixpkgs/master";
home-manager = {
url = "github:nix-community/home-manager/release-24.11";
inputs.nixpkgs.follows = "nixpkgs";
};
flake-utils.url = "github:numtide/flake-utils";
};
outputs = inputs@{self, nixpkgs, flake-utils, home-manager, ...}:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
in {
legacyPackages = {
homeConfigurations = {
"your.name" = home-manager.lib.homeManagerConfiguration {
inherit pkgs;
modules = [ ./home.nix ]; # Defined later
};
};
};
});
}
This will make the following possible things to build:
legacyPackages.x86_64-linux.homeConfigurations."your.name"
legacyPackages.x86_64-darwin.homeConfigurations."your.name"
legacyPackages.aarch64-linux.homeConfigurations."your.name"
legacyPackages.aarch64-darwin.homeConfigurations."your.name"
And these are places where home-manager
and nix build
will be able to look and build the correct version for you.