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:
To find the right package name before adding it:
Or browse: https://search.nixos.org/packagesAdding a package in NixOS¶
Open your configuration.nix (or a dedicated packages module) and add to environment.systemPackages:
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:
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):
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):
In Home Manager (home.nix):
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 inflake.lock. - Update: Move that pin to a newer commit. Gives you newer package versions from that channel.
- Generation: Every time you run
nixos-rebuild switchorhome-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):
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:
Ian enables this automatically:
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