Understanding the Flakes Input System

Updated

September 12, 2026

Understanding the Flakes Input System

Two humans, two nixpkgs, two glibcs. The boring default is: inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05", follows so nested flakes share it, and flake.lock committed.

Mental model

inputs = {
  nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
  home-manager = {
    url = "github:nix-community/home-manager/release-26.05";
    inputs.nixpkgs.follows = "nixpkgs";
  };
};
Piece Job
url Where to fetch (github, git+ssh, path, tarball)
follows Reuse this flake’s input instead of nested default
flake.lock rev + narHash for every input

nix flake update moves every input. nix flake update nixpkgs moves one. Do not update everything on Friday without flake check.

master / nixpkgs-unstable is not a pin you can name in an incident. 26.05 is. Home Manager’s train is release-26.05, not master.

Worked examples

Case 1: follows in the tree

Save as flake.nix:

# flake.nix
{
  description = "Desk infrastructure flake with shared nixpkgs";

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

  outputs = { self, nixpkgs, home-manager }: {
    formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixfmt-rfc-style;
  };
}

flake-utils is optional sugar; skip it unless you already depend on it.

nix flake lock
nix flake metadata

Output (shape):

Inputs:
├───home-manager: github:nix-community/home-manager/…
│   └───nixpkgs follows input 'nixpkgs'
└───nixpkgs: github:NixOS/nixpkgs/nixos-26.05/…

follows input 'nixpkgs' is the line you want. Without it, home-manager may lock a second nixpkgs.

Case 2: One-input update

nix flake update home-manager

Output (shape):

• Updated input 'home-manager'

nixpkgs did not move. That is the point. A lock PR that also bumps sops, disko, and nixpkgs is three incidents in one.

Case 3: Audit the lock

nix flake metadata --json | jq -r '.locks.nodes.nixpkgs.locked.rev'
nix flake metadata --json | jq -r '.locks.nodes.nixpkgs.locked.narHash'

Put that rev in the PR description when you bump 26.05.

Case 4: path: for a sibling repo

# flake.nix fragment
{
  inputs.desk-tooling.url = "path:../desk-tooling";
  inputs.desk-tooling.inputs.nixpkgs.follows = "nixpkgs";
}

path: is not copied to a cache the same way as git+ssh. Fine on a laptop. CI should use a git URL + lock (git+ssh://git.desk.internal/desk-tooling.git?ref=refs/tags/…).

Case 5: Check after a bump

nix flake update nixpkgs
nix flake check -L

If check fails, git checkout -- flake.lock and try a smaller step. The lock is git; treat it as code.

git diff --stat flake.lock

If flake.lock is uncommitted, there is no team.

When you add sops / disko / home-manager, give each a follows:

# flake.nix fragment
{
  inputs.sops-nix.url = "github:Mic92/sops-nix";
  inputs.sops-nix.inputs.nixpkgs.follows = "nixpkgs";
  inputs.disko.url = "github:nix-community/disko";
  inputs.disko.inputs.nixpkgs.follows = "nixpkgs";
}
nix flake metadata | grep -c 'nixpkgs:' 

One nixpkgs: line (plus follows children). Three unlocked nixpkgs nodes is the eval-RAM incident.

The trap

The trap is three copies of nixpkgs because nobody wrote follows. Eval RAM and download time explode. nix flake metadata every time you add an input.

The other trap is not committing flake.lock. CI then evals whatever nixos-26.05 means this morning.

The boring rule

  • nixos-26.05 (and matching release-26.05 for HM). Not master.
  • inputs.<dep>.inputs.nixpkgs.follows = "nixpkgs";
  • Commit the lock. Update one input per PR when you can.
  • metadata to see follows. Count nixpkgs nodes: one.
  • path: locally; git URLs in CI.

Try this

  1. nix flake metadata on this flake; count nixpkgs nodes. Should be one.
  2. Remove follows, nix flake lock --update-input home-manager, metadata again, put follows back.
  3. nix flake update home-manager and git diff flake.lock.
  4. jq the locked narHash for nixpkgs; paste it in a dummy PR description.