Day 24 — Lock discipline

Updated

July 30, 2026

Day 24 — Lock discipline

Stage III · ~4h
Goal: Treat flake.lock as a deliberate pin, not a mysterious generated blob—update one input at a time, read diffs, and explain how locks interact with registries and multi-machine rebuilds.

Important

Blind nix flake update on a production host is how “nothing changed except everything.” Prefer staged, selective updates.

Why this day exists

Flakes solve channel drift only if you respect the lock file. Teams fail when:

  • Someone updates all inputs “because CI was green last week”
  • Lock files are gitignored (never do this for apps/hosts)
  • Two machines use different locks and argue about “Nix being unreproducible”
  • Registries silently redirect nixpkgs away from the flake’s pin

Today is ops hygiene for every later day that adds inputs (Home Manager, sops-nix, …).


Theory 1 — What flake.lock actually pins

For each flake input, the lock records roughly:

Field Meaning
type / owner / repo / url Where the input came from
rev Exact git commit (or equivalent)
narHash Content hash of the fetched tree
lastModified Metadata for humans/tools

Implication: two clones with the same lock should fetch the same input trees (modulo substituted vs rebuilt outputs later). Without the lock, nixos-26.05 is a moving branch tip.

Inputs vs follow-ons

inputs = {
  nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
  home-manager.url = "github:nix-community/home-manager/release-26.05";
  home-manager.inputs.nixpkgs.follows = "nixpkgs";
};

follows means Home Manager reuses your nixpkgs pin instead of its own nested lock entry as a separate copy. That reduces “two nixpkgs versions in one eval” confusion and often saves eval work.


Theory 2 — Update operations (precision toolkit)

Command Effect
nix flake lock Refresh lock metadata without changing intended refs (often no-op if already locked)
nix flake update Update all inputs to latest matching their url refs
nix flake update nixpkgs Update only the nixpkgs input
nix flake update home-manager Update only that input
nix flake metadata Show resolved pins / last modified

Modern CLI also supports updating multiple named inputs; check nix flake update --help on your Nix version.

Mental model

flake.nix   →  policy ("track nixos-26.05")
flake.lock  →  decision ("this commit of that policy")

Changing flake.nix URLs without updating lock can error or keep stale locks depending on change type—always re-lock intentionally and commit both files together when inputs change.


Theory 3 — How to read a lock diff

git diff flake.lock | head -200

Look for:

  1. Which input changed ("nixpkgs": { block)
  2. Old rev vs new rev
  3. narHash change (expected when rev changes)
  4. Nested inputs (if not follows)

Optional deep inspection:

nix flake metadata
nix flake metadata --json | jq '.locks.nodes.nixpkgs'

Map rev to a GitHub compare URL when investigating regressions:

https://github.com/NixOS/nixpkgs/compare/<old>...<new>

Theory 4 — Pin strategies (pick a house rule)

Strategy When Risk
Track release branch (nixos-26.05) + lock commits Default for this book Branch tip moves; you choose when to update
Pin exact rev in flake.nix Freeze hard for a milestone Manual bumps; easy to forget security updates
Track nixos-unstable Bleeding edge lab Breakage rate higher
Multiple flakes (host vs tools) Advanced separation Operational complexity

Recommended for 90DaysOfX lab: track nixos-26.05 (and matching HM release), commit lock, update one input per session, rebuild + smoke-test before updating the next.

Security vs stability

Updating nixpkgs is how you receive package and module fixes. Never freeze a public-facing host forever “because it works.” Discipline is when and how, not whether.


Theory 5 — Registries (light literacy)

Flake registries map short names (nixpkgs) to URLs for ad-hoc commands like nix run nixpkgs#hello.

Concern Practice
Global registry Convenient for exploration
Your host flake Should use explicit inputs, not hope the registry matches
CI Prefer locked flake in repo over registry luck
nix registry list

If global nixpkgs points at unstable while your flake pins 26.05, that is fine for one-off nix run—as long as you never assume those are the same pin.


Theory 6 — Multi-machine and “reproducible”

Same lock + same system (x86_64-linux) → same evaluation of inputs. Realization may still:

  • Download substitutes vs build locally
  • Differ if impure eval sneaks in (builtins.currentSystem misuse, unpinned fetchTarball, IFD surprises)
  • Differ across architectures

Lock discipline is necessary, not always sufficient, for bit-identical everything—but it is the main lever you control daily.


Theory 7 — Commit policy for locks

Do Don’t
Commit flake.lock with the change that needs it Gitignore lock for NixOS host flakes
One logical update per commit when possible Mix lock bumps with huge unrelated refactors
Note why you updated in commit body “chore: update” with no smoke test

Example commit body:

flake: update nixpkgs (26.05 tip)

Smoke: sshd active, rebuild switch OK, hello from devShell OK.

Worked example — Selective update narrative

Monday:  update nixpkgs only → rebuild lab → OK
Tuesday: add home-manager input → lock appears → rebuild
Friday:  update home-manager only → HM activation issue → revert lock hunk

Reverting a bad lock is often:

git checkout HEAD~1 -- flake.lock
sudo nixos-rebuild switch --flake .#lab

That is faster than debugging three simultaneous input moves.


Lab 1 — Snapshot current pins

Suggested workspace: your Stage III flake.

cd /path/to/your-flake
nix flake metadata
cp flake.lock /tmp/flake.lock.day24.bak
git status

Journal:

  1. List every input name and its locked rev (short hash is fine)
  2. Confirm flake.lock is tracked by git
  3. Note whether any input uses follows

Lab 2 — Read the lock structure

# human overview
nix flake metadata

# if jq available
nix flake metadata --json | jq '.locks.nodes | keys'

Open flake.lock and identify:

  • The root node’s inputs
  • The nixpkgs node’s rev and narHash
  • Any nested nixpkgs_2 style duplicates (candidate for follows later)

Lab 3 — Selective update of one input

Only if you accept rebuild churn today. On a disposable lab host:

nix flake update nixpkgs
git diff --stat flake.lock
git diff flake.lock | head -120

Build before switch:

nixos-rebuild build --flake .#lab
# if build OK:
sudo nixos-rebuild switch --flake .#lab

Smoke checklist:

systemctl is-system-running || true
systemctl is-active sshd
nix --version

If break: restore backup lock and re-switch.


Lab 4 — Prove “update all” is louder

Do not leave the system on a broken all-update. On a throwaway branch:

git checkout -b experiment/flake-update-all
nix flake update
git diff --stat flake.lock

Count how many inputs / revs moved. Then:

git checkout main   # or your real branch name
git branch -D experiment/flake-update-all  # if you discard
# ensure flake.lock restored

Write one sentence: why selective update is the default for hosts.


Lab 5 — Document house rules

Create docs/lock-policy.md (or journal):

# Lock policy (lab)

- Track nixpkgs nixos-26.05
- Commit flake.lock always
- Update one input per change window
- Smoke: rebuild + SSH + one app path
- Revert lock on failure before deep debug

Optional: add a shell alias or just muscle memory—not a requirement.


Lab 6 — Registry awareness (read-only)

nix registry list

Compare:

nix flake metadata

Note if the registry’s nixpkgs rev differs from the flake’s. No need to change registries today.


Common gotchas

Symptom / mistake What to do
Everyone’s host differs after clone Missing or uncommitted flake.lock
follows not set → two nixpkgs Add inputs.foo.inputs.nixpkgs.follows = "nixpkgs"
Updated lock, forgot rebuild switch / boot to activate
CI uses nix flake update every run Pin lock in repo; update in deliberate PRs
“I’m on 26.05” but lock is months old Expected until you update; schedule updates
Restored flake.nix but not lock Always restore the pair when debugging inputs
Private input auth fails after update Token/SSH access issue, not lock format

Checkpoint

  • Can explain what rev + narHash buy you
  • Can update one named input and read the lock diff
  • Have a written house lock policy
  • Know how to revert flake.lock quickly
  • Understand registry ≠ flake pin
  • follows planned or applied for future HM input

Commit

git add .
git commit -m "day24: lock discipline and update policy"

Write three personal gotchas before continuing.


Tomorrow

Day 25 — Home Manager on NixOS: add Home Manager as a NixOS module so system and user activation share one rebuild story.