The Nix Package Manager in Practice

Updated

September 12, 2026

The Nix Package Manager in Practice

Nix the language is useless without Nix the tool. The boring default on Nix 2.35 is: nix shell for five minutes, nix profile only for personal CLIs that are not a project, and nix search against a 26.05 pin — not nix-env -iA.

Project compilers still belong in devShell / direnv. This chapter is the user profile and one-shot PATH.

Mental model

~/.nix-profile is a generation of symlinks into /nix/store. It is not /usr/bin. Adding jq does not delete the previous jq; rollback is another generation.

Command Lifetime
nix shell nixpkgs#jq This process
nix run nixpkgs#jq -- --version One command
nix profile install nixpkgs#jq Until rollback/remove
nix search nixpkgs jq Query

Unqualified nixpkgs# follows your registry (often a moving flake). For desk work prefer a pinned flake:

nix shell github:NixOS/nixpkgs/nixos-26.05#jq

or nix shell .#jq inside a repo.

Worked examples

Case 1: One-shot

nix shell github:NixOS/nixpkgs/nixos-26.05#cowsay --command cowsay "Desk system ready"

Output:

 ____________________
< Desk system ready >
 --------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

Nothing new in nix profile list.

Case 2: nix run

nix run github:NixOS/nixpkgs/nixos-26.05#hello

Output:

Hello, world!

run builds (or substitutes) and executes the default app. Good for nix run .#desk-api.

Case 3: Profile install + list

nix profile install github:NixOS/nixpkgs/nixos-26.05#jq
nix profile list
which jq
readlink -f "$(which jq)"

Output (shape):

Name:               jq
Flake attribute:    packages.x86_64-linux.jq
Store path:         /nix/store/…-jq-…

readlink -f $(which jq) is a store path. On NixOS, prefer Home Manager home.packages over a growing profile if the user already has HM.

Case 4: Rollback

nix profile history
nix profile rollback
which jq || echo 'jq gone'

If jq was the only package in that generation, it disappears from PATH. The store path remains until GC.

nix profile remove jq   # if your 2.35 CLI uses names; else remove by index from `nix profile list`

Case 5: Search against 26.05, not the registry drift

nix search github:NixOS/nixpkgs/nixos-26.05 jq

Output (shape):

* legacyPackages.x86_64-linux.jq
  jq: Command-line JSON processor

First search can be slow (eval). After that, 26.05 is cached. nix search nixpkgs jq without a pin is whatever the flake registry says today.

Pin the user registry so unqualified nixpkgs# matches the desk:

nix registry pin nixpkgs github:NixOS/nixpkgs/nixos-26.05
nix registry list

CI still uses the repo flake.lock, not your laptop registry. The pin only stops surprise nix shell nixpkgs#jq on a workstation.

nix profile upgrade --all

On Nix 2.35 this rebuilds profile packages against whatever they were installed from. Prefer deleting a profile package and using Home Manager / the project flake instead of living on upgrade --all. Compilers still do not belong here.

The trap

The trap is nix-env -iA nixpkgs.jq. Unpinned, fights flakes, surprising rollbacks. nix-env is legacy. nix profile or Home Manager.

The other trap is nix profile install of go, nodejs, rustc. The next repo wants another version. Put those in the project devShell.

A third trap: nix shell nixpkgs#jq with an unlocked registry while CI uses 26.05. Pin the URL or the repo flake.

The boring rule

  • nix shell / nix run for trials. Pin nixos-26.05.
  • nix profile for personal CLIs (jq, ripgrep). Not for compilers.
  • nix profile rollback when an install was a mistake.
  • No nix-env.
  • Project tools in the flake, not in the user profile.

Try this

  1. nix shell github:NixOS/nixpkgs/nixos-26.05#fastfetch --command fastfetch (or hello).
  2. nix search github:NixOS/nixpkgs/nixos-26.05 postgresql | head.
  3. Install hello into the profile, run it, rollback, confirm which hello.
  4. nix profile list and remove anything that is a compiler.