Skip to content

firefly-engineering/nix-pins

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 

Repository files navigation

nix-pins

Centralized Nix flake input pins for Firefly Engineering repositories.

The problem

When you have multiple Nix flake projects, they inevitably drift apart in their pinned input versions — especially nixpkgs. While this isn't a functional problem (each project still builds), it means your Nix store accumulates duplicate builds of the same packages at slightly different revisions. Dev shells that should be cached end up triggering fresh builds because your pins are just slightly off from one another.

The solution: the "Lockfile Proxy" pattern

nix-pins is a published flake that contains no code — only input declarations and follows wiring. It acts as a centralized "registry" that your downstream projects can optionally reference.

Downstream repositories declare nix-pins as an input and use follows to inherit its pinned versions. This gives you:

  • One place to update — bump a dependency once here, and every downstream repo picks it up on its next nix flake update.
  • Consistent versions — all repos evaluate against the same nixpkgs, toolchains, and utilities, maximizing binary cache hits.
  • Deduplicated evaluation — because downstream inputs follow into nix-pins, Nix doesn't fetch or evaluate the same dependency graph multiple times.
  • Pre-wired follows — transitive dependency relationships (e.g. fenix -> nixpkgs, flake-parts -> nixpkgs-lib) are already configured here, so downstream flakes don't need to repeat that boilerplate.
  • Opt-in, not enforced — if a specific project needs a different version, just change its input back to a direct URL. The synchronization is a convenience, not a constraint.

Available inputs

Nixpkgs channels

Input Tracks Use case
nixpkgs nixpkgs-unstable Default for dev shells, packages, overlays
nixpkgs-lib nixos-unstable (lib only) Lightweight lib access (used by flake-parts)
nixpkgs-stable nixos-25.11 NixOS system configurations, home-manager
darwin-stable nixpkgs-25.11-darwin nix-darwin system configurations
nixpkgs-master master Bleeding-edge packages not yet in unstable

Frameworks

Input Description
flake-parts Modular flake framework (follows nixpkgs-lib)
darwin nix-darwin system management (follows darwin-stable)
home-manager Home directory management (follows nixpkgs-stable)

Rust toolchain

Input Description
fenix Rust toolchain manager (follows nixpkgs)
naersk Rust build tool (follows nixpkgs, fenix)

Utilities

Input Description
flake-utils Per-system helpers (follows systems)
flake-compat Use flakes from non-flake Nix
flake-root Flake source root detection
systems System definitions (nix-systems/default)

Dev shell & formatting

Input Description
devshell Structured dev shell builder (follows nixpkgs)
treefmt-nix Multi-formatter integration (follows nixpkgs)
pre-commit-hooks-nix Git pre-commit hooks (follows nixpkgs, flake-compat)
nix-index-database Offline nix-locate database (follows nixpkgs)

Secrets & infrastructure

Input Description
sops-nix SOPS-based secret management (follows nixpkgs)
nixos-generators NixOS image generation (follows nixpkgs)
fh FlakeHub CLI (follows nixpkgs, fenix)

Usage in downstream repositories

Add nix-pins as an input, then use follows to pull in whichever dependencies you need. The downstream project's flake.lock will still track its own pin of nix-pins — running nix flake update in the downstream repo is what actually syncs it.

Minimal example — just nixpkgs and flake-parts

{
  inputs = {
    nix-pins.url = "github:firefly-engineering/nix-pins";

    nixpkgs.follows = "nix-pins/nixpkgs";
    flake-parts.follows = "nix-pins/flake-parts";
  };

  outputs = { flake-parts, ... }@inputs:
    flake-parts.lib.mkFlake { inherit inputs; } {
      systems = [ "x86_64-linux" "aarch64-darwin" ];
      perSystem = { pkgs, ... }: {
        devShells.default = pkgs.mkShell {
          packages = [ pkgs.hello ];
        };
      };
    };
}

Rust project

{
  inputs = {
    nix-pins.url = "github:firefly-engineering/nix-pins";

    nixpkgs.follows = "nix-pins/nixpkgs";
    flake-parts.follows = "nix-pins/flake-parts";
    fenix.follows = "nix-pins/fenix";
    naersk.follows = "nix-pins/naersk";
    treefmt-nix.follows = "nix-pins/treefmt-nix";
  };

  outputs = { flake-parts, ... }@inputs:
    flake-parts.lib.mkFlake { inherit inputs; } {
      systems = [ "x86_64-linux" "aarch64-darwin" ];
      imports = [ inputs.treefmt-nix.flakeModule ];
      perSystem = { pkgs, system, ... }: let
        toolchain = inputs.fenix.packages.${system}.stable.defaultToolchain;
        naersk' = inputs.naersk.lib.${system}.override {
          cargo = toolchain;
          rustc = toolchain;
        };
      in {
        packages.default = naersk'.buildPackage { src = ./.; };
      };
    };
}

NixOS / nix-darwin system configuration

{
  inputs = {
    nix-pins.url = "github:firefly-engineering/nix-pins";

    nixpkgs.follows = "nix-pins/nixpkgs-stable";
    darwin.follows = "nix-pins/darwin";
    home-manager.follows = "nix-pins/home-manager";
    sops-nix.follows = "nix-pins/sops-nix";
  };

  outputs = { darwin, home-manager, sops-nix, ... }@inputs: {
    darwinConfigurations.my-mac = darwin.lib.darwinSystem {
      system = "aarch64-darwin";
      modules = [
        home-manager.darwinModules.home-manager
        sops-nix.darwinModules.sops
        ./configuration.nix
      ];
    };
  };
}

Opting out for a single input

If a project needs a different version of a specific input, just override the follows with a direct URL:

{
  inputs = {
    nix-pins.url = "github:firefly-engineering/nix-pins";

    # Follow most things from nix-pins...
    flake-parts.follows = "nix-pins/flake-parts";
    fenix.follows = "nix-pins/fenix";

    # ...but pin nixpkgs independently for this project
    nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
  };

  # ...
}

Pre-flight validation

A pins update can break downstream consumers. To avoid updating blindly, use --override-input to test the new pins against downstream repos before pushing.

Quick manual check

From inside a downstream repo:

# Point nix-pins at your local checkout (works before you even commit):
nix develop --override-input nix-pins path:/path/to/nix-pins --command true

# Or test against a branch/PR:
nix develop --override-input nix-pins github:firefly-engineering/nix-pins/my-update-branch --command true

# Run the full check suite:
nix flake check --override-input nix-pins path:/path/to/nix-pins

The --command true trick forces Nix to fully evaluate and build the dev shell, then exits immediately. The downstream flake.lock is not modified — you're testing a "what if" scenario.

Override a single transitive input

You can also test a specific nixpkgs bump without touching nix-pins at all:

nix build --override-input nix-pins/nixpkgs github:nixos/nixpkgs/COMMIT_SHA

Batch pre-flight script

To validate multiple downstream repos at once after updating pins locally:

#!/usr/bin/env bash
set -euo pipefail

PINS_DIR="${1:?Usage: $0 /path/to/nix-pins [project ...]}"
shift
PROJECTS=("$@")

echo "Validating pins at: $PINS_DIR"

for project in "${PROJECTS[@]}"; do
    if [ ! -d "$project" ]; then
        echo "SKIP $project: not found"
        continue
    fi

    echo "--- $(basename "$project") ---"

    if nix develop "$project" \
        --override-input nix-pins "path:$PINS_DIR" \
        --command true 2>&1; then
        echo "PASS"
    else
        echo "FAIL: breakage detected in $(basename "$project")"
        exit 1
    fi
done

echo "All projects validated."

Usage:

./preflight.sh ~/src/nix-pins ~/src/project-a ~/src/project-b ~/src/my-system-config

Updating pins

To bump all inputs to their latest versions:

nix flake update

To bump a specific input:

nix flake update nixpkgs
nix flake update fenix

After updating, run the pre-flight validation against downstream repos before merging.

Syncing downstream repos

Once a pins update is merged, update downstream repos to pick up the new versions:

# Update just the nix-pins input (leaves other direct inputs alone):
nix flake update nix-pins

# Or update everything:
nix flake update

About

Centralized Nix flake inputs for improved cache management

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages