Table of Contents
- Chapter 5: Package Management
- Three Levels of Packages
- Level 1: System packages (all users, all hosts)
- Level 2: User packages across all hosts (home-manager)
- Level 3: Host-specific user packages
- How user packages merge
- Profiles
- Programs vs. Packages
- Desktop Applications
- External Flake Packages
- Custom Desktop Entries
- Finding Packages
- The nixos-hardware Flake
- Unfree Packages
- Binary Caches
- Package Versioning
Chapter 5: Package Management
This chapter covers how software gets installed on your NixOS system -- the different methods, when to use each, and how to find what you need.
Three Levels of Packages
Your configuration installs packages at three distinct levels:
Level 1: System packages (all users, all hosts)
File: modules/common/system.nix
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
htop
ncdu
git
wget
curl
rsync
ripgrep
jq
tmux
vim
just
age
sops
ssh-to-age
yubikey-manager
# ... and more
];
}
Everything in environment.systemPackages here is available to every user on every host, because modules/common/system.nix is imported by both modules/desktop/environment.nix (desktop hosts) and modules/server/base.nix (server hosts).
Desktop-specific packages (Firefox, AMD GPU tools) are added in modules/desktop/environment.nix directly, not in system.nix. Firefox uses programs.firefox.enable = true; rather than a package entry — the programs module does more than just install the binary (policies, desktop entries, MIME types).
Level 2: User packages across all hosts (home-manager)
File: home/nimmo.nix
User packages for nimmo are managed by home-manager, which is configured in flake.nix and uses home/nimmo.nix:
{ config, pkgs, inputs, isServer, ... }:
{
home.packages = with pkgs; [
element-desktop
trilium-desktop
# ... plus conditional packages based on isServer
] ++ (lib.optionals (!isServer) [
# desktop-only packages skipped on server hosts
]);
}
The isServer flag (passed from flake.nix via extraSpecialArgs) lets home/nimmo.nix install different packages on server hosts than on desktop hosts. Claude Code and other CLI tools are available everywhere; desktop launchers and GUI-only tools are skipped on headless machines.
Home-manager uses home.packages instead of users.users.<name>.packages. Both end up in the user's PATH, but home-manager provides additional capabilities like managing dotfiles, shell configuration, and program-specific settings.
The file home/nimmo.nix is the primary location for nimmo's user packages.
Level 3: Host-specific user packages
File: Varies by scope
For packages specific to certain hosts, they are managed via capability profiles:
# In modules/profiles/gaming.nix
home-manager.users.${user}.home.packages = with pkgs; [
discord
mangohud
powertop
];
These packages are only available on hosts that import the gaming profile. Discord and MangoHud do not get installed on lena.
For packages on a single host only, they go in that host's default.nix (e.g., hosts/lena/default.nix).
How user packages merge
Nimmo's packages come from multiple sources that all end up in PATH:
- Home-manager profiles (e.g.,
modules/profiles/ai-desktop.nix,modules/profiles/gaming.nix) — installed on hosts that import the profile - Desktop apps (
modules/desktop/apps.nix) — installed on all desktop hosts - System packages (
modules/common/system.nix) — available to all users on all hosts
On electra, nimmo gets gaming + AI + maker profile packages. On lena, nimmo gets ai-desktop + maker. On vega (server), nimmo gets ai-agents only (no desktop launchers). Package overlap is fine — lists are concatenated and deduplicated.
Profiles
Location: modules/profiles/
Profiles are curated groupings of packages and services for a particular class of workload. They are regular modules imported by host configurations.
| Profile | Purpose | Used by |
|---|---|---|
ai-agents.nix |
Claude Code, Codex CLI, nixd LSP, Claude Code MCP plugin (nixos), credentials | vega; also imported by ai-desktop |
ai-desktop.nix |
Imports ai-agents + opencode + desktop launchers, SOPS-backed OpenRouter credentials, host-specific Ollama providers | electra, lena |
gaming.nix |
Steam, GameMode, MangoHud, Discord | electra igpu + dgpu |
maker.nix |
3D printing, electronics, CAD | electra (all tiers), lena |
The ai-desktop profile imports ai-agents internally, so importing ai-desktop gives you both the CLI tools and the opencode desktop launcher. Hosts that only want CLI tools without opencode and desktop launchers (e.g., vega) import ai-agents directly.
The former ai-development and ai-tools profiles have been consolidated: ai-desktop covers what both did for desktop machines, and ai-agents covers the subset needed on servers.
Programs vs. Packages
Some software has both a programs.<name>.enable option and a package in nixpkgs. There is an important difference:
Using programs.*.enable
programs.firefox.enable = true;
programs.steam.enable = true;
programs.gamemode.enable = true;
The programs module does more than just install the package:
- Firefox: Sets up policies, creates desktop entries, configures MIME types
- Steam: Enables 32-bit support, sets up the sandbox, configures pressure-vessel
- GameMode: Installs the daemon, sets up polkit rules, creates the system service
Always check if a programs.<name>.enable option exists before adding a package manually. You can search at https://search.nixos.org/options.
Using environment.systemPackages
environment.systemPackages = with pkgs; [
htop
ripgrep
wget
];
This just installs the package binary. No extra system integration. Use this for simple tools that do not need special NixOS configuration.
Desktop Applications
File: modules/desktop/apps.nix
High-level desktop applications available on all desktop/laptop hosts:
# Enable nix-ld so pre-built desktop tooling and language servers can run
programs.nix-ld.enable = true;
environment.systemPackages = with pkgs; [
kdePackages.kate # Text editor
libreoffice-qt6-fresh # Office suite
kdePackages.okular # Document viewer
kdePackages.kcalc # Calculator
vlc # Media player
kdePackages.gwenview # Image viewer
kdePackages.spectacle # Screenshots
nextcloud-client # Cloud sync
bitwarden-desktop # Password manager
element-desktop # Matrix client
trilium-desktop # Notes
];
Both electra and lena import this module.
External Flake Packages
Some packages come from external flakes rather than nixpkgs. Your configuration uses several.
claude-code-nix
Declared as an input in flake.nix:
inputs = {
claude-code-nix.url = "github:sadjow/claude-code-nix";
claude-code-nix.inputs.nixpkgs.follows = "nixpkgs";
};
Used in a profile module (e.g., modules/profiles/ai-agents.nix) or directly in home/nimmo.nix:
{ config, pkgs, inputs, ... }:
{
home.packages = [
inputs.claude-code-nix.packages.${pkgs.stdenv.hostPlatform.system}.claude-code
];
}
The pattern is always:
inputs.<flake-name>.packages.${pkgs.stdenv.hostPlatform.system}.<package-name>
Breaking this down:
inputs.<flake-name>— the flake input.packages— the packages output of that flake.${pkgs.stdenv.hostPlatform.system}— the platform (e.g.,x86_64-linux).<package-name>— usuallydefault, but can be a specific name likeclaude-code
Note that you cannot use with pkgs; for external flake packages because they are not in pkgs. You must use the full inputs. path.
nur (Nix User Repository)
The NUR provides community-maintained packages not in nixpkgs. Your configuration uses it for declarative Firefox extension management in home/nimmo.nix:
programs.firefox.profiles.default.extensions.packages =
with inputs.nur.legacyPackages.${pkgs.stdenv.hostPlatform.system}.repos.rycee.firefox-addons; [
bitwarden
consent-o-matic
];
Browse available Firefox addons at: https://nur.nix-community.org/repos/rycee/
Custom Desktop Entries
You can create desktop launchers for applications using makeDesktopItem. In home/nimmo.nix:
home.packages = with pkgs; [
(makeDesktopItem {
name = "claude-code";
desktopName = "Claude Code";
exec = "konsole -e claude";
icon = "utilities-terminal";
categories = [ "Development" ];
terminal = false;
})
];
This creates a .desktop file so the application appears in your KDE application menu.
Finding Packages
Online search
The fastest way: https://search.nixos.org/packages
Type a name, see available packages with their attribute names, versions, and descriptions.
Command-line search
# Search for a package
nix search nixpkgs firefox
# Detailed JSON output
nix search nixpkgs --json firefox | jq
# Check exact version
nix eval nixpkgs#firefox.version
Finding the right attribute name
Sometimes the package name differs from the binary name:
| What you want | Package attribute |
|---|---|
btop |
btop |
MangoHud |
mangohud |
LibreOffice |
libreoffice-qt6-fresh |
VLC |
vlc |
When in doubt, search on https://search.nixos.org/packages.
The nixos-hardware Flake
The laptop hosts use nixos-hardware modules for vendor-specific optimizations:
# electra (hosts/electra/default.nix)
inputs.nixos-hardware.nixosModules.framework-16-amd-ai-300-series
# lena (hosts/lena/default.nix)
inputs.nixos-hardware.nixosModules.lenovo-ideapad-16ahp9
These modules apply optimizations specific to the hardware — kernel parameters, power management, driver selection. Vega does not use a nixos-hardware module (generic server hardware). Browse available hardware modules at: https://github.com/NixOS/nixos-hardware
Unfree Packages
Some packages (Steam, Discord, NVIDIA drivers) are not free software. NixOS does not install them by default. Your configuration allows them globally:
# In hosts/common/nix-settings.nix
nixpkgs.config.allowUnfree = true;
Without this line, any unfree package would cause a build error.
Binary Caches
Building packages from source is slow. Binary caches provide pre-built packages. Your configuration uses several:
# In hosts/common/nix-settings.nix
substituters = [
"https://nix-cache.nimmog.uk"
"https://claude-code.cachix.org"
"https://cache.nixos.org"
];
cache.nixos.org— the official NixOS cache, has most packagesclaude-code.cachix.org— a third-party cache for the Claude Code packagenix-cache.nimmog.uk— a self-hosted nix binary cache that pre-builds all flake configurations
When Nix needs a package, it checks the caches first. If a pre-built binary exists, it downloads it (fast). Otherwise, it builds from source (slow). The self-hosted binary cache means that after a push to git, electra and lena can typically download pre-built packages rather than building from source.
Package Versioning
On NixOS, you do not choose individual package versions. The version of every package is determined by which revision of nixpkgs you are using (pinned in flake.lock).
When you run nix flake update, you get the latest versions of everything. This is different from apt/pacman where you update packages individually.
If you need a specific version of a package, you would need to use an overlay or pin a specific nixpkgs revision. This is an advanced topic not currently used in your configuration.