Day 30 — Flake checks
Day 30 — Flake checks
Stage III · ~4h
Goal: Add flake checks so nix flake check exercises eval/format/build assumptions locally—the same shape CI will use later.
Why this day exists
A flake that only switches on your laptop is a private ritual. Checks turn “I hope this still evaluates” into a command:
nix flake checkToday is not full nixosTest mastery (Stage VI). Today is lightweight gates: format, build a shell, evaluate a host, maybe build a package.
Theory 1 — What checks are
Flake schema: checks.<system>.<name> → derivations. nix flake check builds them (with some special cases).
| Check type | Purpose |
|---|---|
| Format / lint | Style consistency |
nixosSystem eval/build |
Config still evaluates |
mkShell build |
DevShell inputs resolve |
| Small package build | Packaging sanity |
nixosTest |
VM integration (later) |
Failed check → non-zero exit → CI red.
Theory 2 — Hand-rolled checks skeleton
# inside outputs, with forAllSystems / pkgsFor from Day 29
checks = forAllSystems (system:
let
pkgs = pkgsFor system;
in {
# 1) formatter dry-run style check
format = pkgs.runCommand "check-format" {
buildInputs = [ pkgs.nixpkgs-fmt ];
} ''
set -euo pipefail
# copy sources; fail if nixpkgs-fmt wants changes
mkdir -p $out
# For a real check, point at your nix files via path builtins carefully.
# Simpler starter: succeed if formatter exists
command -v nixpkgs-fmt > $out/fmt-path
'';
# 2) devShell builds
devshell = self.devShells.${system}.default;
# 3) host evaluation via build of system toplevel (heavy but strong)
# lab-host = self.nixosConfigurations.lab.config.system.build.toplevel;
});Building the full toplevel is an excellent check but can be slow and architecture-bound. For a laptop-only x86_64-linux lab, pinning checks to that system is fine.
Lighter host check — eval only patterns
Some people use pkgs.writeText of a JSON dump of options they care about, or nixos-rebuild build in CI scripts without putting toplevel in checks. Either works if documented.
Theory 3 — flake-parts checks
Under perSystem:
perSystem = { self', pkgs, ... }: {
checks = {
devshell = self'.devShells.default;
# treefmt / pre-commit style modules exist in the ecosystem—optional
};
};Community modules (treefmt-nix, git-hooks.nix / pre-commit-hooks) are optional accelerators—do not require them today if time is short.
Theory 4 — Format check that actually fails
A useful format check:
format = pkgs.runCommand "check-nix-fmt" {
nativeBuildInputs = [ pkgs.nixpkgs-fmt ];
src = ./.; # careful: large contexts; often better to pass explicit file list
} ''
set -euo pipefail
cp -r $src tree
chmod -R u+w tree
cd tree
# list your nix files
find . -name '*.nix' -print0 | xargs -0 nixpkgs-fmt --check
mkdir -p $out
touch $out/ok
'';Notes:
--checkmust exist for your formatter CLI (nixpkgs-fmt supports check mode;nix fmtworkflows vary)
- Copying entire flake including
.gitcan be heavy—trim withlib.cleanSource
src = lib.cleanSource ./.;Theory 5 — What belongs in check vs push-time CI only
Local nix flake check |
CI later (Stage VI) |
|---|---|
| Format | Same + matrix systems |
| Shell / small builds | Cache push |
| Optional host toplevel | Full VM tests |
Keep local checks fast enough that you run them. A 40-minute check nobody runs is theater.
Theory 6 — nix flake check UX
nix flake check
nix flake check -L # logs
nix build .#checks.x86_64-linux.format -LIndividual check attrs help debug without running the full set.
Worked example — Practical minimal set for Stage III
checks = forAllSystems (system:
let pkgs = pkgsFor system; in {
shell = self.devShells.${system}.default;
# only on the host architecture you actually use:
}) // {
# top-level non-per-system alternative patterns exist; prefer per-system consistency
};Plus on x86_64-linux only:
# conceptual
lab-toplevel = self.nixosConfigurations.lab.config.system.build.toplevel;If aarch64 eval of an x86-only host is nonsense, do not genAttrs that check onto all systems blindly—gate by system.
checks = forAllSystems (system:
let pkgs = pkgsFor system; in {
shell = self.devShells.${system}.default;
} // nixpkgs.lib.optionalAttrs (system == "x86_64-linux") {
lab-toplevel = self.nixosConfigurations.lab.config.system.build.toplevel;
});Lab 1 — Baseline without checks
cd /path/to/your-flake
nix flake show
nix flake check || trueNote current behavior (may pass with zero checks or warn).
Lab 2 — Add devShell as a check
Wire checks.<system>.shell = self.devShells.${system}.default (or flake-parts equivalent).
nix build .#checks.x86_64-linux.shell -LLab 3 — Add a format check that can fail
- Implement format check on
*.nix
- Run
nix flake check
- Break formatting in a temp edit
- Confirm check fails
- Restore format
# example break
# add ugly spacing in a nix file, then:
nix flake checkLab 4 — Optional host toplevel check
If disk/time allow:
nix build .#checks.x86_64-linux.lab-toplevel -LRecord wall-clock time. If > your patience budget, document “CI-only toplevel” and keep shell+format local.
Lab 5 — Developer loop script (optional)
# tools/check.sh
#!/usr/bin/env bash
set -euo pipefail
nix flake check -LNot required if you prefer raw commands.
Lab 6 — Journal: CI shape
Write the future CI job in five lines:
checkout
install nix
nix flake check
# later: deploy job separate
Common gotchas
| Symptom / mistake | What to do |
|---|---|
| Checks empty / not discovered | Must be under checks output correctly |
| IFD surprises slow checks | Avoid unnecessary import-from-derivation |
| Building host on wrong system | optionalAttrs gate |
| Format check always succeeds | You didn’t pass real sources |
| Check context includes secrets | Don’t; keep secrets out of flake source |
nix flake check downloads the world |
First run cold; then cached—note substituters |
Checkpoint
- At least two meaningful checks (e.g. shell + format)
nix flake checkfails when format intentionally broken
- Know how to build one check attribute
- Host toplevel check added or explicitly deferred with reason
- Future CI sketch written
- Checks are fast enough to run before commit
Commit
git add .
git commit -m "day30: flake checks for format and shell"Write three personal gotchas before continuing.
Tomorrow
Day 31 — Overlays light: pin or patch one package for host and shell without forking all of nixpkgs.