Building with Nix¶
nix is the single entry point for every Sipi build artifact (dev binary,
static binary, Docker image, debug symbols). It is reproducible, hermetic,
and shares its dependency cache with CI via Cachix.
Nix primer (for newcomers)¶
A few concepts that make the rest of this page click:
Derivation. Each package in the flake — dev, default, release,
sanitized, fuzz, docker, static-amd64, etc. — is a separate
derivation: a build recipe with fully declared inputs (source, compiler,
flags, deps). Nix hashes the recipe, builds once, caches the result in
/nix/store, and serves future requests instantly.
Outputs. A single derivation can produce multiple outputs. For
example .#dev emits:
- default output — the sipi binary + runtime files
^debug— split debug symbols (fromseparateDebugInfo = true)^coverage—coverage.xmlfrom gcovr
Select an output with ^: nix build .#dev^coverage. Without ^, you
get the default output.
List what the flake offers:
Cross-platform dispatch. Each derivation declares its target
system (e.g., aarch64-linux). nix build reads it and either builds
locally (if the host matches) or hands off to a configured external
builder. No --system flag needed — see
Building Linux binaries from macOS.
Common syntax shapes:
| Form | Meaning |
|---|---|
nix build .#dev |
Current-system dev derivation, default output |
nix build .#packages.aarch64-linux.dev |
Explicit target system |
nix build .#dev^coverage |
A specific output of a derivation |
nix flake show |
List everything the flake exposes |
One-time setup¶
1. Install Determinate Nix.
On macOS, add the native-linux-builder feature so you can build any
Linux variant from macOS without a VM. As of April 2026 this is
not yet generally available — request access via Determinate
Systems support, per the
native-linux-builder setup notes.
Once enabled, verify:
You should see entries for aarch64-linux and x86_64-linux. Without
native-linux-builder, Linux-targeted builds on macOS need a remote
Linux builder configured by hand.
2. Enable the shared Cachix cache. This is what turns a 15-minute
cold build into a 2-minute warm one — CI pushes every ext/ dep and
every sipi-* output to dasch-swiss.cachix.org.
This writes ~/.config/nix/nix.conf entries for the substituter and
trusted public key. After the first successful nix build, subsequent
builds on the same flake closure are near-instant.
3. (Optional) GH_TOKEN for cold-cache Kakadu fetches. The Kakadu
FOD only runs when Cachix doesn't already have its output. In that
rare case it needs a GitHub token to fetch dsp-ci-assets. Put this
in .envrc once:
On the hot path you never notice this — Cachix serves the Kakadu
output and GH_TOKEN is never read.
Build artifacts (via just — what CI invokes)¶
Every build recipe wraps nix build .#<variant>. No imperative cmake
invocations in recipes — CI invokes only just <recipe>.
just nix-build # .#dev: Debug + coverage; unit tests run in the sandbox
just nix-build-default # .#default: RelWithDebInfo + tests
just nix-build-release # .#release: stripped, no tests
just nix-build-sanitized # .#sanitized: Debug + ASan + UBSan
just nix-build-fuzz # .#fuzz: libFuzzer binary only
just nix-build-static-amd64 # .#static-amd64: Zig-in-Nix musl
just nix-build-static-arm64 # .#static-arm64: Zig-in-Nix musl
just nix-build-release-archive-amd64 # .#release-archive-amd64: tarball + sha256 + debug
just nix-build-release-archive-arm64 # .#release-archive-arm64: tarball + sha256 + debug
just nix-coverage # .#dev^coverage: writes result-coverage/coverage.xml
just nix-docker-build # streams .#docker-stream into the local Docker daemon
Debug symbols for any variant are available on the .debug output:
Dev shell inner loop (non-recipe — local iteration only)¶
The justfile does NOT expose an imperative build recipe. The fast edit/rebuild cycle is intentionally documented here as a dev-shell pattern instead of a recipe — recipes are contracts that CI runs the same command, and CI always goes through a Nix derivation.
nix develop # default = clang + libc++
nix develop .#fuzz # libstdc++ for libFuzzer ABI
nix develop .#gcc # gcc14Stdenv
# Inside the shell (non-reproducible, will NOT match CI byte-for-byte):
cmake -B build -S . -DCMAKE_BUILD_TYPE=Debug -DCODE_COVERAGE=ON
cmake --build build --parallel
./build/sipi --config config/sipi.localdev-config.lua
# subsequent edits:
cmake --build build # incremental
To run the reproducible CI build (same commands as CI), use
just nix-build instead. For tests against the built sipi, use:
just rust-test-e2e # resolves $SIPI_BIN, default ./result/bin/sipi
just hurl-test # Hurl HTTP contract tests
just nix-run # sipi with the dev config
just nix-valgrind # sipi under Valgrind
Building Linux binaries from macOS¶
Requires native-linux-builder enabled on your Determinate Nix install
(see One-time setup — not yet GA as of April 2026,
request via support). Once it's on, just name the Linux package
explicitly:
Swap aarch64-linux for x86_64-linux to reproduce the amd64 CI
variant. Works for every package: dev, default, release,
sanitized, fuzz, static-amd64, static-arm64.
native-linux-builder dispatches the build automatically; you don't
pass a --system flag. Warm Cachix → seconds. Cold Cachix → ~15 min
(and the Kakadu FOD will need GH_TOKEN in your env — see step 3 of
One-time setup).
Static Linux binaries¶
Static musl binaries are produced by the Nix flake via a Zig-based
toolchain (cmake/zig-toolchain.cmake). No Zig install is needed on
the host — Zig ships inside the Nix dev shell and is invoked by
mkStaticBuild in flake.nix.
just nix-build-static-amd64 # x86_64-linux-musl
just nix-build-static-arm64 # aarch64-linux-musl
just nix-build-release-archive-amd64
just nix-build-release-archive-arm64
Validation: