Combining NixOS and Home Manager
Combining NixOS and Home Manager
Two rebuild commands (nixos-rebuild and home-manager switch) means two nixpkgs, two lock files, and a rollback that restores the kernel but not ~/.bashrc. The boring default on a NixOS workstation is: Home Manager as a NixOS module, one flake, one nixos-rebuild switch.
Mental model
flake.nix
nixosConfigurations.desk-vm
├── NixOS modules (boot, users, services)
└── home-manager.nixosModules.home-manager
└── home-manager.users.deskadmin = { … };
nixos-rebuild switch --flake .#desk-vm then:
- Builds the system closure.
- Builds the user’s Home Manager generation.
- Activates both.
- Records one system generation that includes the home generation.
--rollback restores both.
| Option | Why |
|---|---|
home-manager.useGlobalPkgs = true; |
One nixpkgs: the system’s. No second eval. |
home-manager.useUserPackages = true; |
User packages land in the NixOS user profile, not a disconnected HM profile |
home-manager.users.<name> |
Must match users.users.<name> |
Standalone Home Manager remains correct on a foreign distro (next chapter). On NixOS, the module wins.
Worked examples
Case 1: Minimal unified module
Save as unified.nix:
# unified.nix
{ config, pkgs, ... }:
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.deskadmin = { pkgs, ... }: {
home.stateVersion = "26.05";
programs.git = {
enable = true;
userName = "Desk Engineer";
userEmail = "desk@corp.internal";
};
home.packages = [ pkgs.jq ];
};
}The user deskadmin must already exist in NixOS (users.users.deskadmin.isNormalUser = true;). Home Manager will not create a system user.
Case 2: Flake wiring
Save as flake.nix:
# flake.nix
{
description = "Desk workstation";
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 }: {
nixosConfigurations.desk-vm = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
home-manager.nixosModules.home-manager
./unified.nix
];
};
};
}sudo nixos-rebuild switch --flake .#desk-vmOutput:
building the system configuration...
activating the configuration...
Starting Home Manager activation
…
One command. jq is on the user’s PATH after login.
Case 3: Split files per user
Save as home/deskadmin.nix:
# home/deskadmin.nix
{ pkgs, ... }:
{
home.stateVersion = "26.05";
programs.bash.enable = true;
home.packages = with pkgs; [ ripgrep jq ];
}In unified.nix:
# unified.nix
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.deskadmin = import ./home/deskadmin.nix;
}Do not invent a second flake output homeConfigurations.deskadmin on the same NixOS host unless you have a reason to apply it separately (you probably do not).
Shared user modules (git, editor) that every human should get:
# unified.nix fragment
{
home-manager.sharedModules = [ ./home/common.nix ];
home-manager.extraSpecialArgs = { deskDomain = "desk.internal"; };
}extraSpecialArgs is how a home module receives flake inputs (inputs.nixpkgs is already pkgs when useGlobalPkgs is on). Do not import the system config into home unless you must — it is a cycle magnet.
Case 4: Backup files on clobber
{
home-manager.backupFileExtension = "bak";
}If a real file is sitting where Home Manager wants a symlink, the switch copies it to *.bak instead of aborting. Useful once during migration. If you see new .bak files every week, something is still writing in place.
Case 5: Rollback includes $HOME
sudo nixos-rebuild switch --rollbackThe previous system generation’s Home Manager generation comes back. ~/.config/git/config points at the old store path. You do not run home-manager switch --rollback in addition.
The trap
The trap is keeping a standalone home-manager switch --flake .#deskadmin cron on a NixOS box that also imports the module. Two tools write ~/.bashrc. The last one to run wins. Disable the standalone CLI in muscle memory once the module is on.
The other trap is useGlobalPkgs = false “so I can pin a newer git.” You now evaluate nixpkgs twice and can mix library ABIs in one user session. If you need a newer git, overlay it on the system nixpkgs.
The boring rule
- On NixOS: Home Manager as a NixOS module. One flake. One rebuild.
useGlobalPkgs = true;useUserPackages = true;.home-manager.users.<name>matchesusers.users.<name>.follows = "nixpkgs"on the home-manager input.sharedModulesfor team defaults.backupFileExtensionat the home-manager. NixOS option (not only insidehome.nix) so clobber policy is one place.- Rollback is
nixos-rebuild switch --rollback. Stop running two CLIs.
Try this
- Wire Case 2 on a lab VM, add
pkgs.helloto the user’shome.packages, switch, runhelloasdeskadmin. ls /nix/var/nix/profiles/systemand find the home-manager generation referenced from the current system.- Change git
userEmail, switch, then--rollbackand confirm the old email is back. - Grep your flake for
homeConfigurations. If it exists andnixosConfigurationsapplies the same user, delete the duplicate output.