CI pipelines for flakes
CI pipelines for flakes
Goal: Run your flake checks/builds on GitHub Actions (or GitLab) with a modern Nix installer, optional Cachix/Attic push, and a PR-shaped workflow that fails on regressions.
If tests only run on your laptop, they are hobbies. 2026 default shape: forge CI + flakes + binary cache. Baseline nixpkgs: 26.05.
Why this day exists
Stage V packages and Stage VI modules become team infrastructure only when a clean runner reproduces them. CI also forces purity: “works on my machine” impurities die in public logs. Capstone the capstone harden chapter reuses this muscle.
Theory 1 — Minimal trustworthy pipeline
checkout → install Nix → (auth cache) → nix flake check / build → (push cache)
| Job / step | Purpose |
|---|---|
checkout |
Source + lockfile |
install Nix |
Known installer; flakes enabled |
cache auth |
Pull/push substitutes |
check |
nix flake check or selected checks |
build-pkg |
Stage V package |
build-hosts |
Optional toplevel builds |
Trust boundaries
| Surface | Rule |
|---|---|
| PR from fork | Often no secret cache write tokens |
main |
May push cache |
| Logs | Never print age keys, Cachix tokens, SSH keys |
trusted-users |
Do not weaken developer laptops for CI |
Theory 2 — GitHub Actions workflow (2026-shaped)
# .github/workflows/nix.yml
name: nix
on:
push:
branches: [ main ]
pull_request:
workflow_dispatch:
permissions:
contents: read
# id-token: write # if using OIDC for caches later
concurrency:
group: nix-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
# pin to a reviewed tag in real repos
- name: Magic Nix Cache (optional)
uses: DeterminateSystems/magic-nix-cache-action@main
# or Cachix below
# - uses: cachix/cachix-action@v15
# with:
# name: myorg
# authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- name: Flake metadata
run: nix flake metadata
- name: Selected checks
run: |
nix build -L \
.#checks.x86_64-linux.cooltool-runs \
.#checks.x86_64-linux.ssh-hardening
- name: Build package
run: nix build .#cooltool -L
- name: Build edge toplevel (optional, heavy)
run: nix build .#nixosConfigurations.edge.config.system.build.toplevel -LPin action versions to tags or SHAs you have reviewed. Versions above are illustrative—confirm current upstream tags when you wire the repo. Freeze pins harder by the multi-node bar chapter for Capstone.
Theory 3 — Cachix (and friends) in CI
- uses: cachix/install-nix-action@v31
with:
nix_path: nixpkgs=channel:nixos-26.05
- uses: cachix/cachix-action@v15
with:
name: myorg
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
# signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' # if used
- run: nix build .#cooltool -LHouse rules
| Rule | Why |
|---|---|
| Push from CI (common) | Shared binaries; less laptop upload chaos |
| Separate PR vs main push rights | Abuse / token theft mitigation |
| Public keys on consumers | Lab + admin substituters + trusted-public-keys |
| Never commit private signing keys | Rotate immediately if leaked |
Alternatives
| Tool | Notes |
|---|---|
| Cachix | Hosted; simple |
| Attic | Self-hosted multi-tenant cache |
| Harmonia | Serve local store as binary cache |
| Magic Nix Cache | UX-oriented GitHub cache integration |
Any one done well beats none.
Theory 4 — What not to run on free runners
| Heavy | Strategy |
|---|---|
| Full nixosTest matrix | Linux job; fewer nodes; path filters |
| Multiple toplevels | Build one primary host per PR |
| Nested KVM | May work on GH-hosted; else self-hosted runner |
| Secret full decrypt | Dummy configs for CI (the more tests chapter) |
Unbounded flake check |
Select attrs until budget known |
on:
pull_request:
paths:
- 'flake.nix'
- 'flake.lock'
- 'modules/**'
- 'hosts/**'
- 'pkgs/**'
- 'tests/**'
- '.github/workflows/nix.yml'Timeouts and disks
jobs:
check:
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
# …Out-of-disk: slim checks, GC mid-job only if needed, or larger runners.
Theory 5 — GitLab CI sketch
# .gitlab-ci.yml (sketch)
image: debian:bookworm
variables:
NIX_CONFIG: "experimental-features = nix-command flakes"
before_script:
- apt-get update && apt-get install -y curl xz-utils
# install Nix via official/Determinate method — prefer current docs
- nix flake metadata
check:
script:
- nix flake check -L
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCHPrefer maintained install methods over ancient nixos/nix container tags when flakes misbehave.
Theory 6 — CI vs deploy
| Pattern | the CI pipelines chapter stance |
|---|---|
| CI verifies build/check | Default |
| CI pushes cache | Preferred |
| CI deploys to lab | Optional later; protect with environments |
| CI holds prod SSH keys | High risk; avoid early |
Human or gated CD deploy after green checks is enough for Stage VI. Capstone the capstone harden chapter revisits.
Worked example — selected checks job
- name: Fast checks
run: |
set -euo pipefail
nix build -L \
.#checks.x86_64-linux.cooltool-runs \
.#checks.x86_64-linux.ssh-hardening
- name: Optional full check
if: github.ref == 'refs/heads/main'
run: nix flake check -LSplit fast PR signal from heavier main-branch verification when needed.
Worked example — intentional red PR
# on a branch: break a check assertion or package
git checkout -b ci/red-test
# … introduce fail …
git commit -am "ci: temporary red"
git push -u origin ci/red-test
# open PR → confirm CI red
# fix → confirm greenIf you cannot get a red PR, the workflow is not protecting you.
Lab — multi-step
Suggested notes: ~/lab/90daysofx/02-nixos/day66
Step 1 — Add workflow file
Commit .github/workflows/nix.yml (or GitLab equivalent) to your lab flake repo. Use path filters if monorepo.
Step 2 — First green CI
Push branch; fix until green on a clean runner. Paste run URL into notes.
mkdir -p ~/lab/90daysofx/02-nixos/day66
echo "CI_URL=…" > ~/lab/90daysofx/02-nixos/day66/ci-url.txtStep 3 — Wire cache
Cachix or Magic Nix Cache or Attic. Confirm second run is faster or shows substitutes.
Step 4 — PR simulation
Open PR that breaks a check; confirm CI red; fix; green.
Step 5 — Secrets hygiene
Ensure tokens only in forge secrets; scan workflow for accidental echo of secrets.
Step 6 — Consumer config
Add substituter public keys to lab/admin Nix config; rebuild or set user nix.conf.
Step 7 — Document runner needs
KVM? memory? timeout? Write docs/CI.md with:
| Topic | Content |
|---|---|
| Triggers | PR/main |
| Commands | exact nix build lines |
| Cache | name + public key |
| Limits | known failures without KVM |
| Non-goals | deploy-from-CI policy |
Step 8 — Status badge (optional)
README badge for the workflow—only if green is honest.
Step 9 — Deploy from CI? (decide)
Write the choice: verify-only vs gated deploy. Default for this day: verify-only.
Common gotchas
| Symptom / mistake | What to do |
|---|---|
| Flakes disabled | Installer action enables; set NIX_CONFIG |
| Disk full on runner | Slim checks; larger runner; GC carefully |
| Unpinned actions | Pin majors/SHAs per org policy |
| Cache not used | Public key / auth; path existence; wrong cache name |
| VM tests fail without KVM | Self-hosted or reduce tests |
| Monorepo wrong cwd | defaults.run.working-directory / filters |
| Fork PRs leaking write tokens | Restrict secret access |
| IFD / eval forever | Fix expressions; select attrs |
Checkpoint
- Workflow exists and runs on PR/push
- At least one Nix build/check green in CI
- Intentional red PR observed
- Cache strategy configured or explicitly deferred with reason
docs/CI.mdnotes runner limits and commands
- Tokens not in git
- Three personal gotchas
Commit
git add .
git commit -m "day66: GitHub Actions Nix CI pipeline"Write three personal gotchas before continuing.