Day 25 — Home Manager on NixOS
Day 25 — Home Manager on NixOS
Stage III · ~4h
Goal: Wire Home Manager as a NixOS module (not standalone-first), manage one user’s shell/git/ssh client config, and rebuild with a single nixos-rebuild switch --flake activation story.
House default for this volume: Home Manager NixOS module on NixOS hosts. Standalone HM is for non-NixOS machines or special cases—know it exists; do not make it your lab’s primary path.
Why this day exists
Stage II put packages on the system (environment.systemPackages). That is correct for daemons and multi-user tools—and wrong as the only place for personal developer config. Home Manager (HM) owns:
- User programs (
programs.git,programs.bash/zsh/fish, …)
- Dotfiles and XDG (Day 26)
- User systemd services (Day 27)
Doing HM as a NixOS module means: one flake, one rebuild, one generation story for system + user.
Theory 1 — Three ways people run Home Manager
| Mode | How it activates | When to use |
|---|---|---|
| NixOS module | Part of nixos-rebuild |
Default on NixOS (this book) |
| Standalone | home-manager switch --flake ... |
Other distros; or isolated user experiments |
| nix-darwin module | macOS analog | Out of scope here |
Why module mode first
- Aligns HM with system generations (easier mental model for labs)
users.users.<name>andhome-manager.users.<name>stay in one repo
- Avoids “system switched but HM forgotten” dual-track ops
Standalone still matters later if you develop on Ubuntu with HM-only—but your NixOS lab should not require two tools for every change.
Theory 2 — Input wiring (26.05-aligned)
# flake.nix (inputs section — merge with your layout)
{
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 = { self, nixpkgs, home-manager, ... }: {
nixosConfigurations.lab = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./hosts/lab
home-manager.nixosModules.home-manager
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
# home-manager.extraSpecialArgs = { inherit inputs; };
home-manager.users.alice = import ./homes/alice;
}
];
};
};
}| Option | Meaning |
|---|---|
useGlobalPkgs = true |
HM uses the same pkgs as the system (consistent overlays later) |
useUserPackages = true |
User packages install into user profile via HM |
home-manager.users.alice = ... |
That user’s HM module tree |
Version match: HM release-26.05 with nixpkgs nixos-26.05. Mismatched releases are a common source of obscure option errors.
Theory 3 — System user vs HM user
You still need a NixOS user:
# modules/common/users.nix
{
users.users.alice = {
isNormalUser = true;
extraGroups = [ "wheel" ];
# shell can be set here OR via HM programs.* — pick one source of truth
};
}HM configures that user’s home:
# homes/alice/default.nix
{ pkgs, ... }:
{
home.username = "alice";
home.homeDirectory = "/home/alice";
home.stateVersion = "26.05"; # set once; do not casually bump
programs.home-manager.enable = true;
programs.git = {
enable = true;
userName = "Alice Example";
userEmail = "alice@example.invalid";
};
programs.bash = {
enable = true;
enableCompletion = true;
};
home.packages = with pkgs; [
ripgrep
fd
];
}home.stateVersion
Like NixOS system.stateVersion, this is a compatibility pin for HM stateful defaults—not “current release marketing.” Set it to the HM release you first adopt for that user; read release notes before bumping.
Theory 4 — What belongs in HM vs NixOS
| Concern | Prefer |
|---|---|
sshd, firewall, kernel, system users |
NixOS modules |
| Interactive shell, prompt, git, editor config | HM |
| CLI tools only you use | HM home.packages |
| Libraries/services shared by system units | NixOS |
| Secrets for user apps | Stage IV patterns (not plaintext in HM) |
Anti-pattern: dumping everything into environment.systemPackages because HM felt optional. Opposite anti-pattern: putting nginx only in HM.
Theory 5 — Activation model (what rebuild does)
On nixos-rebuild switch with HM module:
- System config builds
- HM config for each defined user builds
- Activation scripts run: system switch + HM activation for users
Failures can be system or HM. Read the traceback: home-manager-alice.service style units often appear in logs.
journalctl -u home-manager-alice.service -b --no-pager | tail -80(Username substitution as appropriate.)
Theory 6 — Programs modules vs raw packages
HM programs.* modules often:
- Install the package
- Generate config files
- Wire shell integration
Example: programs.git.enable = true vs only home.packages = [ pkgs.git ] (no managed config). Prefer programs modules when they exist for tools you configure.
Discover options:
- Home Manager option search
- Or eval/docs from your locked HM input
Worked example — Minimal useful user
# homes/alice/default.nix
{ pkgs, lib, ... }:
{
home.username = "alice";
home.homeDirectory = "/home/alice";
home.stateVersion = "26.05";
programs.home-manager.enable = true;
programs.git = {
enable = true;
userName = "Alice";
userEmail = "alice@example.invalid";
extraConfig = {
init.defaultBranch = "main";
pull.rebase = true;
};
};
programs.ssh = {
enable = true;
# Do not put private keys in the flake. Config only:
matchBlocks = {
"lab-remote" = {
hostname = "192.0.2.10"; # documentation range example
user = "alice";
};
};
};
programs.bash = {
enable = true;
shellAliases = {
ll = "ls -la";
".." = "cd ..";
};
};
home.packages = with pkgs; [ htop ];
}Private keys stay in ~/.ssh/ with correct permissions—never in git, never as world-readable store strings (Stage IV deep dive).
Lab 1 — Add the input and lock
cd /path/to/your-flake
# edit flake.nix inputs + outputs as in Theory 2
nix flake lock
# or: nix flake update home-manager
git diff flake.lock | head -80Confirm follows for nixpkgs.
Lab 2 — Stub → real home module
mkdir -p homes/aliceWrite homes/alice/default.nix with git + bash + one package. Wire home-manager.nixosModules.home-manager and home-manager.users.<you>.
Ensure users.users.<you> exists on the system side with matching name and home directory.
Lab 3 — Rebuild and verify
sudo nixos-rebuild switch --flake .#labAs the target user:
git config --global --list | head
which rg || which htop
echo "$HOME"Check HM generation awareness (command names vary slightly by setup):
home-manager generations 2>/dev/null || true
ls -la ~/.local/state/nix/profiles 2>/dev/null || trueLab 4 — Change only HM, rebuild once
Edit programs.git.userName, rebuild, confirm new value. You should not need a separate home-manager switch if module mode is correct.
Lab 5 — Deliberate failure drill
Introduce a typo option:
programs.git.enabl = true; # intentional typoRebuild, read the error, fix. Note whether the failure is clearly HM evaluation.
Lab 6 — Journal: boundary decisions
List 10 tools you use daily. Mark each NixOS / HM / either. Resolve at least the ambiguous ones with a one-line reason.
Common gotchas
| Symptom / mistake | What to do |
|---|---|
| HM release ≠ nixpkgs release | Align release-26.05 with nixos-26.05 |
| User name mismatch | users.users.x must match home-manager.users.x |
home.homeDirectory wrong |
Must match actual home path |
Infinite recursion with useGlobalPkgs |
Check for pkgs/HM cycles; simplify modules |
| Expecting standalone CLI only | Module mode activates via nixos-rebuild |
| Private keys in flake | Remove immediately; rotate if committed |
Bumping home.stateVersion casually |
Read HM release notes first |
| Shell set in three places | One source of truth (NixOS users.users.shell or HM) |
Checkpoint
- HM input pinned with
followsnixpkgs
- HM NixOS module imported in flake
- At least one user has git + shell via HM
- Single
nixos-rebuild switch --flakeactivates system + HM
- No private key material in the repo
home.stateVersionset consciously
Commit
git add .
git commit -m "day25: home-manager as NixOS module"Write three personal gotchas before continuing.
Tomorrow
Day 26 — HM files & XDG: home.file, xdg.configFile, permissions, and migrating real dotfiles without turning the store into a secret dump.