CI with Nix¶
Overview¶
Continuous integration with Nix means install Nix on the runner, then evaluate and build the same flake (or expression) developers use locally. Pin inputs with flake.lock so CI and laptops share the same dependency graph. Prefer substituting from binary caches over rebuilding on every clean runner; push newly built paths back to a project cache when write access is available.
nix.dev documents one GitHub Actions path: install Nix, optionally wire a project cache (often Cachix), then run builds. Action choices below (Cachix, Determinate Systems, and others) are examples of common patterns, not endorsements. Flake-oriented jobs typically use experimental nix flake check / nix build (nix-command and flakes). Nixpkgs-scale job graphs belong on Hydra. Cache hosting options are covered under binary cache hosting.
Boundaries¶
- This page: runner install → cache → job shape → common CI failure modes for a single project or config flake.
- Not here: private-input auth deep dive (private flakes and CI); which flake outputs to expose (checks and hydraJobs); mono-repo folder conventions (config repo layout); which Nix distribution to install on a laptop (installers and Nix variants); Hydra jobset ops (Hydra).
Details¶
Typical pipeline¶
- Checkout the repo (including
flake.lock). - Install Nix on the runner. Common GitHub Actions patterns: cachix/install-nix-action (nix.dev recipe; enables
nix-commandandflakesby default) or Determinate Systems’ installer actions (e.g.nix-installer-action/determinate-nix-action—check each action’s README for which Nix distribution it installs and how to pin versions). Compare distributions under installers and Nix variants; pin the action (and thus the Nix) your team verified. - Configure substituters / push credentials for a binary cache when used.
- Run builds. Classic expressions:
nix-build(and optionallynix-shell) as in the nix.dev recipe. Flakes:nix flake check(evaluates flake outputs and buildschecks) and/ornix buildfor packages you care about. Wirechecksand optionalhydraJobsas in checks and hydraJobs.
nix flake check is experimental (nix-command / flakes; see the Nix manual). Useful flags: --no-build (evaluate only), --all-systems (check every system key).
Job shapes¶
Pick a gate that matches repo size. Config mono-repos often need more than one nix flake check—see config repo layout and private flakes and CI.
| Shape | When | Typical command / wiring |
|---|---|---|
| Single flake check | Small app / library flake; cheap checks |
One job: nix flake check (optional nix build). Expose gates via checks. |
| Host matrix | Config flake with many nixosConfigurations / hosts |
Parallel jobs per host (or shard); build .#nixosConfigurations.<host>.config.system.build.toplevel (or equivalent). Tree layout: config repo layout. |
| Path filters | Large mono-repo; docs-only or single-host PRs | Forge-native path filters skip unrelated host jobs; still keep a cheap lint/checks job when filters would skip everything. Auth for private inputs: private flakes and CI. |
| Context | Usual CI |
|---|---|
| Single project / flake | GitHub Actions (nix.dev recipe), GitLab CI, Forgejo Actions, etc. |
| Massive job graph (e.g. nixpkgs) | Hydra |
Caching¶
Without a project cache, every clean runner rebuilds from scratch. A binary cache lets CI and developers substitute shared store paths:
- Cachix — hosted cache paired with
cachix-actionin the nix.dev recipe (auth via CI secrets:CACHIX_AUTH_TOKENand/orCACHIX_SIGNING_KEY; never commit tokens). See Cachix docs. - Attic / self-hosted — same idea: substituter URL + signing or auth; see binary cache hosting.
Hydra also acts as a large-scale cache producer for nixpkgs-style jobsets; most application repos do not need Hydra.
Secrets and private inputs¶
Do not commit forge tokens, signing keys, or Cachix write credentials. Store them in the CI secret store and inject at runtime. For private flake inputs, set Nix access-tokens from CI secrets (e.g. via extra_nix_config on install-nix-action) so evaluation can fetch locked private sources. Full private-input failure table: private flakes and CI. If evaluation fails mysteriously in CI, compare purity and fetch errors with debugging evaluation.
Failure modes¶
| Failure | What goes wrong |
|---|---|
| No project cache | Clean runners rebuild from source → long wall time and flaky timeouts. Wire a substituter (and push on success) as under Caching / binary cache hosting. |
Missing nix-command / flakes |
nix flake check / nix build unknown or disabled. Enable experimental features in the install action / nix.conf, or use an installer that sets them—confirm with nix config show after install (installers and Nix variants). |
| Private input 401 / 404 | Locked private github: / gitlab: fetch without CI access-tokens (or wrong host/scope). Inject forge secrets as in Secrets and private inputs; details in private flakes and CI. |
| IFD inside a check | A checks derivation (or eval path) imports from derivation → realise-during-eval, slow gates, or failure when allow-import-from-derivation = false. Keep checks cheap; see lazy trees and eval perf and checks and hydraJobs. |
Examples¶
Minimal GitHub Actions sketch aligned with the nix.dev CI recipe, adapted for a flake gate. Illustrative—not run in this vault; requires a runner with network and your own secrets. Action major tags below match current install-nix-action / cachix-action majors as of 2026-07 (nix.dev’s pasted pins may lag—pin majors or full tags yourself). Same job shape works with other install actions; swap the install step only. Placeholders only; keep secrets out of git:
# .github/workflows/ci.yml
name: CI
on:
pull_request:
push:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v31
# Optional: project binary cache (omit if you only use cache.nixos.org)
# - uses: cachix/cachix-action@v17
# with:
# name: YOUR_CACHE_NAME
# authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
# # or signingKey: ${{ secrets.CACHIX_SIGNING_KEY }}
- run: nix flake check
- run: nix build
Classic (non-flake) jobs from the same recipe use nix-build / nix-shell instead of nix flake check / nix build, and may set nix_path (e.g. nixpkgs=channel:nixos-unstable) on install-nix-action.
For private GitHub flake inputs, pass a token into Nix config from a secret (do not hardcode):
- uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GH_TOKEN_FOR_NIX }}
References¶
- nix.dev — Continuous integration with GitHub Actions — install Nix, Cachix secrets/actions, basic workflow
- Nix manual —
nix flake check— experimental flake evaluation andchecksbuilds - Nix manual —
access-tokens— forge tokens for private fetches - Nix manual — Import from derivation — realise-during-eval cost in checks
- cachix/install-nix-action — install Nix on GitHub Actions (cited by nix.dev; example pattern)
- Cachix documentation — hosted binary cache push/pull (example pattern)
- DeterminateSystems/nix-installer-action — alternate GitHub Actions install pattern (example, not endorsement)
See also¶
- checks and hydraJobs — flake CI outputs and
nix flake check - config repo layout — mono-repo hosts/modules and CI matrix fit
- Private flakes and CI — private-input failure modes and org CI matrices
- Installers and Nix variants — which Nix distribution the runner action installs
- Hydra — large-scale Nix CI
- Binary caches (cheatsheet) — consume / host / sign chooser
- Binary cache hosting — Cachix, Attic, and self-hosted caches
- Access tokens — private flake inputs in CI
- Import from derivation — IFD cost in eval/check gates
- Lazy trees and eval perf — IFD policy and eval latency in CI
- Debugging evaluation — evaluating flakes when CI fails early
- Flake CI with GitHub Actions (worked example)
- Experimental: flakes / nix-command — features
nix flake checkneeds