Home Manager and the Module System
Home Manager and the Module System
NixOS configures the machine (kernel, systemd, users, /etc). Home Manager configures one user (dotfiles, user packages, user services). The boring default is: home.packages for binaries, programs.<name> for software that has a module, and home.stateVersion left at the birth release.
Mental model
Home Manager is the NixOS module system aimed at $HOME:
| Field | Effect |
|---|---|
home.username / home.homeDirectory |
Who this config is for |
home.stateVersion |
Birth release. Do not bump casually. |
home.packages |
On the user PATH (~/.nix-profile/bin) |
programs.<name>.enable |
Typed module: writes dotfiles and maybe a user systemd unit |
home.file / xdg.configFile |
Raw files into $HOME / ~/.config |
Two ways to apply it:
| Mode | Command | When |
|---|---|---|
| Standalone | home-manager switch --flake .#deskadmin |
Nix-on-Linux, macOS, or a user who must not touch NixOS |
| NixOS module | nixos-rebuild switch |
The desk workstation is NixOS (next chapters in this part) |
Standalone is how you start on a foreign distro. The NixOS module is how you stay once the machine is yours.
home.nix
├── home.packages → ~/.nix-profile
├── programs.git → ~/.config/git/config
└── xdg.configFile → ~/.config/…
│
▼
home-manager switch
Worked examples
Case 1: A standalone home.nix
Save as home.nix:
# home.nix
{ pkgs, ... }:
{
home.username = "deskadmin";
home.homeDirectory = "/home/deskadmin";
home.stateVersion = "26.05";
home.packages = with pkgs; [
bat
fzf
jq
ripgrep
];
programs.bash.enable = true;
programs.home-manager.enable = true;
# Existing ~/.config/git/config is renamed, not clobbered.
home.backupFileExtension = "hmbak";
}On a machine with the home-manager CLI:
home-manager switch -f home.nixOutput:
Starting Home Manager activation
Activating checkFilesChanged
Activating installPackages
…
Home Manager switch complete!
which jq
jq --versionjq resolves under ~/.nix-profile/bin or ~/.local/state/nix/profiles/home-manager/….
Case 2: A flake with homeConfigurations
Save as flake.nix:
# flake.nix
{
description = "Desk user environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
home-manager = {
url = "github:nix-community/home-manager/release-26.05";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, home-manager }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in
{
homeConfigurations.deskadmin = home-manager.lib.homeManagerConfiguration {
inherit pkgs;
modules = [ ./home.nix ];
};
};
}inputs.nixpkgs.follows prevents Home Manager from pulling a second nixpkgs.
home-manager switch --flake .#deskadminPin both inputs in flake.lock. The same lock on a second laptop is the same jq.
Case 3: User packages are not system services
home.packages = [ pkgs.nginx ]; puts nginx on your PATH. It does not open port 80 and it does not start a daemon. Daemons belong in NixOS services.* (or a user systemd module you wrote on purpose).
# wrong mental model
home.packages = [ pkgs.openssh ]; # a binary, not sshd# NixOS, not Home Manager
services.openssh.enable = true;Case 4: Generation rollback for $HOME
home-manager generations
home-manager switch --rollbackOutput:
2026-09-09 10:11 : id 42 -> /nix/store/…-home-manager-generation
2026-09-08 18:02 : id 41 -> /nix/store/…-home-manager-generation
Switching to generation 41
Broken programs.neovim? Roll back. You do not rm ~/.config/nvim.
Case 5: stateVersion is a birth certificate
home.stateVersion = "26.05";This is not “track 26.05.” It tells Home Manager which defaults your state dirs were created with. When you upgrade nixpkgs to a newer release, you keep stateVersion until a release note says a specific option needs a bump.
The trap
The trap is managing the same program in both NixOS and Home Manager (environment.systemPackages = [ git ] and programs.git.enable = true with conflicting userName). Two git binaries, two configs, a week of “why is my email wrong.”
Pick a layer: git config for humans → Home Manager. git as a system tool for root scripts → NixOS. Not both with different emails.
The other trap is running standalone Home Manager against a different nixpkgs than NixOS on the same box. follows in the flake, or the NixOS module with useGlobalPkgs (later chapter).
A third: home.file.".gitconfig".text = … and programs.git.enable = true. Two writers, last activation wins. Use the programs.git module; home.file is for files that have no module.
The boring rule
- Home Manager owns
$HOME. NixOS owns the machine. home.packagesfor extra CLIs.programs.<name>when a module exists.home.fileonly when no module exists.home.backupFileExtensionso the first switch does not refuse a pre-existing dotfile.- Flake +
follows = "nixpkgs"so there is one nixpkgs. home.stateVersionstays at the birth release.- Roll back with
home-manager switch --rollback, not by deleting dotfiles.
Try this
- Add
pkgs.hellotohome.packages, switch, runhello, remove it, switch again, and confirmwhich hellofails. readlink -f $(which jq)and confirm the path is under/nix/store.- Run
home-manager generationsand identify the current id. - In the flake, omit
inputs.nixpkgs.followson purpose,nix flake metadata, and notice a second nixpkgs. Putfollowsback.