Skip to content

Lesson 03 — NixOS vs Home Manager

The Split

Both NixOS and Home Manager configure software declaratively with Nix, but they operate at different levels:

NixOS Home Manager
Runs as root / system your user
Config lives in /etc/nixos/ (or in your flake) ~/.config/home-manager/ (or in your flake)
Manages system services, kernel, users, firewall, boot dotfiles, user packages, user services
Applied with sudo nixos-rebuild switch home-manager switch
Config file configuration.nix home.nix

The Rule of Thumb

NixOS → anything that needs root, affects all users, or is a system service.
Home Manager → anything that is per-user: shell config, editor, terminal, user packages.

Concrete Examples

This belongs in NixOS (system-level):

# Enable the Docker daemon (needs a socket, runs as root)
virtualisation.docker.enable = true;

# Add a user
users.users.marcus = {
  isNormalUser = true;
  extraGroups = [ "wheel" "docker" ];
};

# Firewall
networking.firewall.enable = true;

# System-wide packages (available to all users)
environment.systemPackages = with pkgs; [ git curl wget ];

This belongs in Home Manager (user-level):

# Git config for MY user
programs.git = {
  enable = true;
  userName = "Marcus";
  userEmail = "marcus@example.com";
};

# My shell
programs.fish.enable = true;

# My packages (installed in my profile, not system-wide)
home.packages = with pkgs; [ ripgrep fd bat eza ];

# A dotfile managed declaratively
home.file.".tmux.conf".source = ./tmux.conf;

How Ian Splits It

Ian's config (vendor/dotfiles-main/) follows this cleanly:

  • nixos/configuration.nix — system: hostname, timezone, fonts, docker, bluetooth, users, boot
  • home-manager/home.nix — imports all the home-manager/*.nix modules
  • home-manager/git.nix — git config
  • home-manager/ghostty.nix — terminal emulator config
  • home-manager/nixvim.nix — neovim (via nixvim module)
  • home-manager/packages.nix — user packages
  • home-manager/sh.nix — shell (fish + starship)
  • etc.

Each concern has its own file. They're all imported by home.nix. This is the standard modular pattern.

Ian runs them standalonenixos-rebuild switch and home-manager switch are separate commands.

How Wimpy Splits It

Wimpy's config (vendor/nix-config-main/) uses a _mixins pattern — small composable modules grouped by concern rather than by level:

nixos/_mixins/
  desktop/hyprland.nix
  desktop/sway.nix
  services/tailscale.nix
  users/martin.nix
  ...
home/_mixins/
  apps/firefox.nix
  apps/obs.nix
  console/fish.nix
  ...

The split is the same (system vs user), but the directory names make the purpose obvious. Hosts pick which mixins they need. Wimpy also runs standalone but uses just (a task runner) to wrap the commands: just home and just host.

How Your Config (nixproject) Splits It

~/nixproject/ uses the same NixOS / Home Manager split but with a key difference: Home Manager runs as a NixOS module, not standalone.

nixproject/
├── modules/nixos/       ← SYSTEM layer (root, both machines)
│   ├── common.nix       users, networking, Tailscale, Nix settings, binary caches
│   └── desktop.nix      SDDM, Sway, KDE Plasma, PipeWire, keyring, fonts
├── modules/home/        ← USER layer (marcus, both machines)
│   ├── default.nix      entry point — imports all the files below
│   ├── cli.nix          bat, eza, ripgrep, fd, zoxide, atuin, git
│   ├── shell.nix        zsh, aliases, starship prompt
│   ├── sway.nix         Sway WM keybinds and layout
│   ├── hyprland.nix     Hyprland (only activates on the EliteBook)
│   ├── terminal.nix     foot, tmux
│   ├── theming.nix      Catppuccin Mocha everywhere
│   ├── apps.nix         Firefox, Joplin, Zoom, Jellyfin…
│   ├── selfhosted.nix   Nextcloud, Bitwarden, Nheko, Thunderbird
│   ├── streaming.nix    Mixxx, Audacity, OBS, EasyEffects
│   ├── editors.nix      neovim, helix
│   └── webapps.nix      PWA shortcuts (Grafana, Linkwarden…)
└── hosts/
    ├── elitebook/       machine-specific: AMD microcode, Hyprland, fingerprint reader
    └── macair/          machine-specific: kernel pin, Broadcom Wi-Fi, FaceTime cam

The wiring happens in flake.nix via mkHost. Home Manager is imported as a NixOS module there, so the user layer and system layer build together:

mkHost = host: extraModules: nixpkgs.lib.nixosSystem {
  modules = [
    ./hosts/${host}
    ./modules/nixos/common.nix
    ./modules/nixos/desktop.nix
    home-manager.nixosModules.home-manager    # ← HM plugged in here
    {
      home-manager.users.marcus = import ./modules/home;
    }
  ] ++ extraModules;
};

One command rebuilds everything:

sudo nixos-rebuild switch --flake ~/nixproject#elitebook

That applies the system config AND the Home Manager user environment in one shot. No separate home-manager switch needed.

NixOS Modules

A NixOS module is a .nix file that returns an attribute set with a specific schema:

{ config, pkgs, lib, ... }:   # receive the standard args
{
  # Declare options this module provides (optional)
  options.myModule.enable = lib.mkEnableOption "my module";

  # The actual configuration (conditional on the option)
  config = lib.mkIf config.myModule.enable {
    environment.systemPackages = [ pkgs.something ];
  };
}

In practice, simple modules often skip the options section and just declare config directly:

{ pkgs, ... }:
{
  environment.systemPackages = with pkgs; [ git neovim ];
  services.openssh.enable = true;
}

Home Manager Modules

Same idea, different option namespace. Home Manager provides options under programs.*, services.*, home.*:

{ pkgs, ... }:
{
  programs.starship = {
    enable = true;
    settings = {
      format = "$all";
    };
  };

  home.packages = with pkgs; [ ripgrep ];
  home.stateVersion = "25.05";   # important — set once, don't change
}

home.stateVersion — Don't Touch This

This is a common source of confusion. home.stateVersion (and system.stateVersion in NixOS) records the version you first installed with. It tells NixOS/Home Manager what migration steps to skip.

Set it once when you install. Never update it. Updating it doesn't give you new features — it just breaks migrations.

Running Both Together

When using a flake with both NixOS and Home Manager, you have two choices:

Approach Command(s) Who uses it
Standalone HM sudo nixos-rebuild switch then home-manager switch Ian, Wimpy
HM as NixOS module sudo nixos-rebuild switch (does both) Your nixproject

The standalone approach lets you iterate on your user config faster without touching the system. The module approach is simpler — one command, one rebuild, always in sync.