Classic CLI literacy

Updated

July 30, 2026

Classic CLI literacy

Goal: Map classic Nix commands and channel habits to the modern CLI—once—so legacy docs are readable without making classic tools your daily driver.

Important

This chapter is literacy, not a lifestyle change. After it, default back to modern nix + flakes.

Why this chapter matters

Half the internet still says nix-env -iA nixpkgs.hello and nix-shell -p. If you cannot translate, you thrash. If you only learn classic, you fight 2026 defaults. Translation table + one dual run is enough.

Ops motivation: production runbooks, ancient Stack Overflow answers, and coworker muscle memory will keep showing classic commands. Translation skill is cheaper than rewriting the internet.


Theory 1 — Same store, two UIs

Layer Shared Differs
Store /nix/store, derivations, GC roots
Eval Nix language Entry points, purity defaults
UX Flag names, flake awareness, UX polish

Classic tools predate flakes. They still work. They are not the pedagogy default of this book.

           ┌──────────────┐
           │  /nix/store  │
           └──────▲───────┘
                  │
     ┌────────────┴────────────┐
     │                         │
 classic CLI              modern `nix`
 (nix-build, …)         (nix build, …)

Theory 2 — Command map (core)

Classic Modern analogue Notes
nix-build -A hello nix build nixpkgs#hello Attr selection differs
nix-build on default.nix nix build -f . / flake packages
nix-shell -p hello nix shell nixpkgs#hello Ad-hoc tools
nix-shell (dev shell.nix) nix develop (flake) / nix-shell still Project shells
nix-env -iA nixpkgs.hello nix profile install nixpkgs#hello Prefer not to
nix-env -q nix profile list
nix-env -e nix profile remove
nix-instantiate nix eval / derivation show Different shapes
nix-store -q --requisites nix path-info -r Both fine
nix-store -q --tree (still useful) / path-info Keep classic for trees
nix-channel --update nix flake update (project) Different model
nix-collect-garbage nix store gc / nix-collect-garbage Still common
nix-prefetch-url nix store prefetch-file etc. Variants evolve

Intent-first map

Intent Prefer modern Classic you may see
Run once nix run nix-shell -p … --run
Project tools nix develop nix-shell
Realize output nix build nix-build
Pin project flake.lock channel + NIX_PATH
Query closure nix path-info -r nix-store -q --requisites

Theory 3 — Channels as history

Channel model

nix-channel --add https://nixos.org/channels/nixos-26.05 nixpkgs
nix-channel --update
nix-env -iA nixpkgs.hello
Property Effect
Mutable “latest on channel” Drift between machines/days
NIX_PATH / <nixpkgs> Ambient dependency
User or root channels Confusing multi-user stories

Flake model (preferred)

inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
# flake.lock pins exact rev
Property Effect
Lock file Shared truth
Explicit inputs No ambient channel required
Per-project pins Different projects can differ safely

This book: channels = read-only literacy. Do not build new projects on channels.

When channels still appear

Context Why
Old tutorials Written pre-flakes default
Some NixOS install defaults Historical paths
Angle-bracket imports import <nixpkgs> {}

Theory 4 — shell.nix / default.nix legacy layouts

Classic project shell

shell.nix:

{ pkgs ? import <nixpkgs> { } }:
pkgs.mkShell {
  packages = [ pkgs.hello pkgs.git ];
}
nix-shell   # enters shell.nix

Modern

flake.nixdevShells.defaultnix develop

You may keep a thin shell.nix for non-flake users, but your path is flakes.

Dual support pattern (optional)

Some repos provide both: flake for modern users, shell.nix that re-exports for classic. Only do this if you must support both audiences.


Theory 5 — nix-env and imperative profiles

nix-env -iA nixpkgs.hello
nix-env -e hello
nix-env --list-generations

Problems as a primary workflow:

  1. Not recorded in project git
  2. Easy to create unreproducible laptops
  3. Diverges from NixOS declarative model

nix profile is the modern imperative cousin—same caution.

Imperative tool Declarative alternative
nix-env -i flake packages / NixOS systemPackages / HM
Random nix-shell -p forever devShells
Channel update as SoT flake.lock review

Theory 6 — Purity and flakes

Classic ad-hoc eval often sees your cwd and NIX_PATH freely. Flake evaluation is more pure by default (limited access to paths outside the flake). That is why git-tracked files matter (flakes chapter).

When classic examples “just import ./.”, flake equivalents may need self or explicit file inclusion.

Classic habit Flake analogue
import ./default.nix outputs / packages
Ambient <nixpkgs> inputs.nixpkgs
Untracked helper file Must be added to git

Theory 7 — Reading old error messages

Old-doc symptom Translation
attribute 'foo' missing in -A Wrong attr path / channel content
file 'nixpkgs' was not found NIX_PATH / channel not set
nix-env permission errors multi-user profile ownership
Blog says enable flakes differently Check current Nix 2.34 docs
error: experimental Nix feature modern CLI without features

Theory 8 — GC and store tools (both eras)

Task Classic Modern
GC nix-collect-garbage nix store gc
Delete gens aggressively nix-collect-garbage -d same caution
Query refs nix-store -q --references nix path-info
Roots nix-store --gc --print-roots still common

Do not GC aggressively on machines with important generations until the host generations chapter.


Theory 9 — Decision policy for this book

Situation Policy
New personal project Flakes + modern CLI only
Reading a 2019 blog Translate; do not adopt channels
Coworker pastes nix-env Offer modern equivalent once
Debugging store graphs Either store tool family is fine
NixOS host management nixos-rebuild --flake (host part)

Worked examples bank

Example A — Build hello both ways

# modern
nix build nixpkgs#hello --print-out-paths --no-link

# classic-ish
nix-build -E 'with import <nixpkgs> { }; hello' --no-out-link

(If <nixpkgs> missing, skip classic path or install channel only for the drill.)

Example B — Shell both ways

nix shell nixpkgs#hello -c hello
nix-shell -p hello --run hello

Example C — Query store both ways

P=$(nix build nixpkgs#hello --print-out-paths --no-link)
nix-store -q --references "$P"
nix path-info -r "$P" | head

Example D — Profile vs run

# avoid if possible
# nix-env -iA nixpkgs.jq
# prefer
nix run nixpkgs#jq -- --version

Example E — Channel list observation

nix-channel --list || true

Empty can be good on a flakes-first machine.


Lab 1 — Build the translation table

mkdir -p ~/lab/nixos-book/concepts/classic-literacy
cd ~/lab/nixos-book/concepts/classic-literacy

Create CLASSIC-MODERN.md with at least 10 rows you personally verified (or attempted):

Task Classic command Modern command Result notes
run hello

Lab 2 — Dual path for a project shell

Take your flakes chapter project (or recreate):

Modern (already works):

cd ~/lab/nixos-book/concepts/flakes/myproject
nix develop -c git --version

Classic parallel: write shell.nix that mirrors the tool list using <nixpkgs> or a pinned fetch if you know how—only for the drill:

{ pkgs ? import <nixpkgs> { } }:
pkgs.mkShell {
  packages = [ pkgs.git pkgs.hello pkgs.jq ];
}
nix-shell --run 'git --version'

Journal: which is easier to share with a teammate on another machine?


Lab 3 — Channel inspection (optional, disposable)

Only if you are willing to touch channels on a lab account:

nix-channel --list
# do not --update production machines casually

If empty: good—note “no channels; flakes only.”


Lab 4 — Doc archaeology

Find one external tutorial that uses classic CLI (search your history or web). Rewrite its first three commands into modern equivalents in your notes without running random untrusted scripts as root.


Lab 5 — Commitment statement

Write three sentences:

  1. When I will still use classic tools
  2. When I will refuse to (prefer modern)
  3. How I will handle future blog posts that only show nix-env

Exercises

  1. Expand your table to 15 rows including GC and store queries.
  2. Translate nix-env -iA nixpkgs.hello into three modern alternatives (run/shell/profile).
  3. Explain why nix-shell -p is not a project SoT.
  4. Convert a classic default.nix package build to a flake packages output (sketch).
  5. Document NIX_PATH on your machine; plan to ignore it for new work.
  6. Pair-read an old tutorial with a highlighter: classic vs still-valid concepts.
  7. Practice nix-store -q --tree vs modern path-info on the same path.
  8. Write a “please use flakes” snippet for your team README.
  9. List risks of nix-collect-garbage -d in classic blogs.
  10. Map nixos-rebuild classic channel workflows to --flake (preview host part).
  11. Deliberately avoid installing channels even if a blog demands it—note the workaround.
  12. Quiz yourself: flashcard classic → modern for one week of reading.

Common pitfalls

Symptom Theory
Following classic blog on NixOS host May fight flakes/module setup
Installing channels “to fix” flakes Usually wrong direction
nix-shell vs nix develop confusion Different project conventions
Profile pollution from drills Remove packages / use disposable user
Assuming channels == 26.05 pin Channel lag vs flake lock rev
Mixing both as SoT Pick flakes for new work
Treating literacy chapter as daily driver Revert to modern defaults

Checkpoint

  • Personal 10-row translation table
  • Same task run modern + classic once
  • Can explain channels vs flake locks
  • Can explain why nix-env is not the goal
  • Default workflow remains modern
  • Commitment statement written

Concepts part — exit criteria

Before the NixOS host part:

  • Three faces of Nix clear
  • Closures and GC roots clear
  • Attrsets + functions + multi-file Nix workable
  • Derivation idea + trivial build done
  • Modern CLI fluent
  • Project flake + lock + devShell done
  • Classic docs are readable, not authoritative

Further depth (this book)

Topic Chapter / part
Host management CLI Part NixOS host
Packaging classic attrs Part Packaging
Deeper flake layout Part Home and flake layout
Restart Concepts map Concepts index