Day 24 — Lock discipline
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.
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
nixpkgsaway 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 -200Look for:
- Which input changed (
"nixpkgs": {block)
- Old
revvs newrev
narHashchange (expected when rev changes)
- 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 listIf 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.currentSystemmisuse, unpinnedfetchTarball, 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 .#labThat 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 statusJournal:
- List every input name and its locked
rev(short hash is fine)
- Confirm
flake.lockis tracked by git
- 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
rootnode’s inputs
- The
nixpkgsnode’srevandnarHash
- Any nested
nixpkgs_2style duplicates (candidate forfollowslater)
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 -120Build before switch:
nixos-rebuild build --flake .#lab
# if build OK:
sudo nixos-rebuild switch --flake .#labSmoke checklist:
systemctl is-system-running || true
systemctl is-active sshd
nix --versionIf 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.lockCount 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 restoredWrite 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 debugOptional: add a shell alias or just muscle memory—not a requirement.
Lab 6 — Registry awareness (read-only)
nix registry listCompare:
nix flake metadataNote 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+narHashbuy you
- Can update one named input and read the lock diff
- Have a written house lock policy
- Know how to revert
flake.lockquickly
- Understand registry ≠ flake pin
followsplanned 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.