Skip to content

Lesson 06 — Installing Packages and Updating the System

The Big Picture

In traditional Linux you have two separate mental models: install a package (apt install foo) and update the system (apt upgrade). In NixOS these are unified — both are just "change the config and rebuild."

There is no persistent package database that diverges from what's declared in your config. What's in your .nix files IS the system.


Installing Packages

Where to put a package

First question: is this for me (the user) or the system (all users)?

Use case Where to declare it Option
Personal tool I use in my shell Home Manager home.packages
App every user needs NixOS environment.systemPackages
Something that needs a system service NixOS usually a programs.* or services.* option

When in doubt, use home.packages. It's easier to move something to system-level than to reverse a system-level install.


Adding a package in Home Manager

Open your home-manager packages file and add the package name to the list.

Ian's example at vendor/dotfiles-main/home-manager/packages.nix:

home.packages = with pkgs; [
  bat        # cat with syntax highlighting
  ripgrep    # fast grep
  fd         # fast find
  lazygit    # terminal git UI
  jq         # JSON processor
];

The with pkgs; lets you write bare names instead of pkgs.bat, pkgs.ripgrep, etc.

After editing, apply:

home-manager switch --flake .#yourusername

To find the right package name before adding it:

nix search nixpkgs ripgrep        # search by keyword
nix-env -qaP 'bat'                # alternate search
Or browse: https://search.nixos.org/packages


Adding a package in NixOS

Open your configuration.nix (or a dedicated packages module) and add to environment.systemPackages:

environment.systemPackages = with pkgs; [
  git
  wget
  curl
];

Ian keeps this short (vendor/dotfiles-main/nixos/configuration.nix:61) — just cachix, git, home-manager, and a few tools. Most packages live in home-manager.

After editing, apply:

sudo nixos-rebuild switch --flake .#yourhostname


Using an unstable package

When the stable channel has an old version and you need a newer one, use the unstable pkgs set.

Ian's pattern (from vendor/dotfiles-main/flake.nix): 1. The unstable pkgs set is instantiated in flake.nix and passed as extraSpecialArgs 2. Modules receive it as a function argument alongside pkgs

# In your module — note unstable is a separate argument from pkgs
{ pkgs, unstable, ... }:
{
  home.packages = with pkgs; [
    ripgrep                    # from stable
    unstable.zed-editor        # from unstable (newer version needed)
    unstable.claude-code       # not in stable yet
  ];
}

Real examples from Ian's packages: unstable.zed-editor, unstable.zoom-us, unstable.claude-code, unstable.gemini-cli.


Using a programs.* option instead of a raw package

Many common tools have dedicated Home Manager modules that do more than just install the package — they also write config files, set environment variables, enable shell integrations, etc.

Raw package approach (less recommended):

home.packages = [ pkgs.git ];
# You still need to manage ~/.gitconfig yourself

Module approach (better):

programs.git = {
  enable = true;
  userName = "Marcus";
  userEmail = "marcus@wilsoz.com";
  extraConfig.pull.rebase = true;
};
# Home Manager generates ~/.gitconfig for you

Check if a module exists before adding to home.packages: - https://home-manager-options.extradomain.net/ — full Home Manager option search - man home-configuration.nix if Home Manager is installed

Common ones worth knowing about: programs.git, programs.fish, programs.bash, programs.zsh, programs.neovim, programs.tmux, programs.starship, programs.direnv, programs.ssh.


One-off installs without modifying config

Need a package right now without committing to having it permanently?

# Open a shell with ripgrep available — gone when you exit
nix shell nixpkgs#ripgrep

# Run a command with a package without installing it
nix run nixpkgs#cowsay -- "hello"

# Try a package temporarily (adds to profile, removable)
nix-env -iA nixpkgs.ripgrep

nix shell and nix run are the clean temporary options. Avoid nix-env -i for permanent installs — it creates state outside your config files and breaks reproducibility.


Allowing unfree packages

Some packages (Slack, Zoom, Chrome, Discord) have proprietary licenses. NixOS blocks them by default.

In NixOS (configuration.nix):

nixpkgs.config.allowUnfree = true;

In Home Manager (home.nix):

nixpkgs.config.allowUnfree = true;

Ian has both set. You need both if you install unfree packages in both places.

To allow only specific unfree packages rather than all of them:

nixpkgs.config.allowUnfreePredicate = pkg:
  builtins.elem (lib.getName pkg) [
    "slack"
    "zoom"
    "discord"
  ];


Updating the System

Concepts: channels, inputs, and generations

  • Channel / input: Where nixpkgs comes from. In a flake this is inputs.nixpkgs. It's pinned to a specific git commit in flake.lock.
  • Update: Move that pin to a newer commit. Gives you newer package versions from that channel.
  • Generation: Every time you run nixos-rebuild switch or home-manager switch, a new generation is created. The old one is kept and you can boot into it.

Update workflow

Step 1: Update flake inputs

# Update all inputs (nixpkgs, home-manager, etc.)
nix flake update

# Update only nixpkgs
nix flake update nixpkgs

# Update only one specific input
nix flake update home-manager

This rewrites flake.lock to point to newer commits. No packages are downloaded yet.

Step 2: Build and test without switching

# NixOS — build but don't activate
sudo nixos-rebuild build --flake .#yourhostname --show-trace

# Home Manager — build but don't activate
home-manager build --flake .#yourusername --show-trace

If the build fails, you haven't changed anything. Fix the issue and rebuild.

Step 3: Switch

# NixOS
sudo nixos-rebuild switch --flake .#yourhostname

# Home Manager (run as your user, not root)
home-manager switch --flake .#yourusername

Both commands activate the new configuration. NixOS creates a new boot entry. Home Manager relinks all managed files.


The --show-trace flag

Always add --show-trace when something fails. Without it, Nix error messages can be cryptic. With it, you get the full evaluation stack:

sudo nixos-rebuild switch --flake .#nixos --show-trace
home-manager switch --flake .#igray --show-trace

Boot entries and rollback

After each nixos-rebuild switch, a new entry appears in the boot loader. You can see all generations:

# List NixOS generations
sudo nix-env --list-generations --profile /nix/var/nix/profiles/system

# List Home Manager generations
home-manager generations

Rollback to previous generation:

# NixOS rollback
sudo nixos-rebuild switch --rollback

# Or reboot and choose the previous entry from the boot menu

This is one of the best things about NixOS — a bad update is never catastrophic. Reboot, pick the previous generation from the boot menu, you're back.

Ian's config limits boot entries to 5 (vendor/dotfiles-main/nixos/configuration.nix:152):

boot.loader.systemd-boot.configurationLimit = 5;

Without this the boot menu grows unbounded.


Garbage collection

Old generations accumulate in /nix/store and take up disk space. Clean them up:

# Delete generations older than 30 days
sudo nix-collect-garbage --delete-older-than 30d

# Delete ALL old generations (only keeps current)
sudo nix-collect-garbage -d

# Home Manager garbage collection
home-manager expire-generations "-30 days"

Ian automates this in vendor/dotfiles-main/nixos/configuration.nix:29:

nix.gc = {
  automatic = true;
  dates = "weekly";
  options = "--delete-older-than 30d";
  persistent = true;
};

persistent = true means if the machine was off when the scheduled collection was supposed to run, it will run at next boot.

A good practice: keep at least a few generations around in case you need to roll back. 30 days is reasonable.


Optimising the Nix store

The store can have duplicate files across different builds. Hardlink them together to save space:

sudo nix-store --optimise

Ian enables this automatically:

nix.settings.auto-optimise-store = true;


Quick Reference

# Find a package
nix search nixpkgs <name>

# Try a package without installing
nix shell nixpkgs#<name>

# Update all flake inputs
nix flake update

# Build NixOS config (no switch)
sudo nixos-rebuild build --flake .#hostname --show-trace

# Apply NixOS config
sudo nixos-rebuild switch --flake .#hostname --show-trace

# Apply Home Manager config
home-manager switch --flake .#username --show-trace

# Roll back NixOS
sudo nixos-rebuild switch --rollback

# List generations
sudo nix-env --list-generations --profile /nix/var/nix/profiles/system
home-manager generations

# Garbage collect
sudo nix-collect-garbage --delete-older-than 30d