How it fits together
This page is the map: the detect → emit → image pipeline, the pure-Nix / CI split, and how the whole thing composes builders that already existed in this repo instead of re-implementing them.
The pipeline
The public API (lib/api.nix, ADR-0039) is a thin composition. Nothing
in it is new machinery — it wires together stages that already exist and are
already tested:
src ──▶ detect ──▶ emit ──▶ mkHardenedImage ──▶ image + SBOM
(IR) (derivation) (buildGitlabImage
+ lib/sbom)
- detect (
lib/ecosystem/detect.nix, ADR-0032) — file-presence detection. It looks for a small fixed set of marker files and returns a normalized IR ({ ecosystem; toolchainVersion; primaryMarker; buildConfigFile; }). The markers arego.mod→ go,Cargo.toml→ rust,pyproject.toml→ python,Gemfile.lock→ ruby. It is not a general project-type solver; it exists so the dispatch is uniform. If nothing matches, it throws at eval time naming the markers it searched and the escape hatches — no silent empty image. - emit (
lib/ecosystem/{go,rust,python,ruby}.nix) — per-ecosystem emitters that assemble the arguments for the matching nixpkgs builder (buildGoModule,rustPlatform.buildRustPackage,buildPythonApplication+buildPoetryDeps,bundlerEnv+enrichGemset). They emit build args; they don’t re-implement dependency fetching. - mkHardenedImage — wraps the built derivation into a hardened OCI image via
buildGitlabImageand attaches its CycloneDX SBOM vialib/sbom.
mkSecureArtifact is the umbrella that runs all three stages, each overridable:
force the ecosystem with ecosystem, skip detection-and-build entirely with
overrideDerivation, or forward image args through image.
mkHardenedImage is the derivation-first core that mkSecureArtifact funnels
into once it has a derivation.
Composition, not reimplementation
The API deliberately reuses existing, tested builders:
| Concern | Delegated to |
|---|---|
| OCI image | lib/gitlab-container-builder.nix → pkgs.buildGitlabImage |
| SBOM | lib/sbom (bombon-backed, ADR-0036) |
| Ecosystem detection | lib/ecosystem/detect.nix (ADR-0032) |
| Ecosystem build args | lib/ecosystem/{go,rust,python,ruby}.nix |
buildGitlabImage is the same builder GitLab’s own service images use — no
shell in production images, non-root by default, minimal layers, standard OCI
labels. Building the image under the FIPS package set is what flips its FIPS
marker so FIPS labels appear (see FIPS).
The pure-Nix / CI split
Everything above is pure Nix: deterministic, IFD-free (a consumer’s
nix flake check --no-build evaluates cleanly), and free of secrets. It
produces the image and the SBOM and nothing more.
The rest — signing, attestation, the offline SLSA L3 build, the scan gate —
cannot be a pure function: it needs this project’s signing key, its Sigstore
OIDC identity, network, and offline runners. That half lives in the reusable CI
components (ci/components/hardened-artifact-slsa-l3.yml, and
hardened-artifact-slsa-l1.yml for the unsigned slsa-l1 tier). The slsa-l3
component builds the pure-Nix output on the offline SLSA L3 tier and then
signs/attests/scans it on the networked shared runner. The security boundary
between those two runner roles is authoritative (ADR-0035):
offline SLSA L3 VM nix-attic-shared (networked)
────────────────── ─────────────────────────────
build .#target from source ──▶ substitute closure (--max-jobs 0)
no secrets, no network └─ sbom · scan+gate · sign · attest
no signing key key + Sigstore identity live here
A cache miss on the shared runner fails rather than rebuilding off-VM, so the signed artifact is provably the offline VM’s output. Signing never touches the offline VM by construction.
IFD-free discipline
The whole surface is import-from-derivation-free. Lockfiles and gemsets must be
vendored relative paths (./Cargo.lock, ./poetry.lock, ./gemset.nix),
never read out of a fetched src. That’s what keeps nix flake check --no-build
evaluable for consumers, and it’s enforced by the repo’s no-ifd-lockfile lint.
Where to read more
- The public API and its two-halves rationale: ADR-0039.
- The auto-ecosystem derivation library (detect + emitters): ADR-0032.
- The offline SLSA build VMs and shared Attic runner: ADR-0035.
- The ecosystem-aware SBOM: ADR-0036.
- Function signatures: the API reference.