Centralized Nix flake input pins for Firefly Engineering repositories.
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.
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.
| 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 |
| 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) |
| Input | Description |
|---|---|
fenix |
Rust toolchain manager (follows nixpkgs) |
naersk |
Rust build tool (follows nixpkgs, fenix) |
| 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) |
| 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) |
| Input | Description |
|---|---|
sops-nix |
SOPS-based secret management (follows nixpkgs) |
nixos-generators |
NixOS image generation (follows nixpkgs) |
fh |
FlakeHub CLI (follows nixpkgs, fenix) |
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.
{
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 ];
};
};
};
}{
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 = ./.; };
};
};
}{
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
];
};
};
}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";
};
# ...
}A pins update can break downstream consumers. To avoid updating blindly, use --override-input to test the new pins against downstream repos before pushing.
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-pinsThe --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.
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_SHATo 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-configTo bump all inputs to their latest versions:
nix flake updateTo bump a specific input:
nix flake update nixpkgs
nix flake update fenixAfter updating, run the pre-flight validation against downstream repos before merging.
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