Overview
Per the Sovereign Architecture SOP (effective 2026-03-13), all production container images MUST be built with Nix using pkgs.dockerTools.buildLayeredImage. Zero Dockerfiles in production.
Core Principles
- Reproducible – All builds pinned to
nixos-25.05viaflake.nix - Cross-compiled – Development on Apple Silicon (
aarch64-darwin), images targetx86_64-linux - Zero Docker daemon – Nix builds the image, Skopeo pushes it, Pulumi deploys it
- EU sovereign – Images pushed to Scaleway Container Registry (
rg.fr-par.scw.cloud/sanmarcsoft/)
Build Command
| |
OrbStack on the Mac Mini M4 provides the x86_64-linux builder transparently.
Flake Structure
Every service that produces an OCI image has a flake.nix at its root:
| |
Fixed-Output Derivations (FOD)
Some builds need network access (e.g., bun install, hugo mod get). These use Fixed-Output Derivations with a known hash.
First Build (Computing the Hash)
- Set the hash to
pkgs.lib.fakeHash:
| |
- Build (this will FAIL and print the correct hash):
| |
- Replace
pkgs.lib.fakeHashwith the printed hash:
| |
- Rebuild – should succeed.
When to Recompute the Hash
The FOD hash must be recomputed whenever:
- Dependencies change (bun.lock, package.json, go.sum)
- Build-time environment variables change (e.g.,
VITE_CLERK_PUBLISHABLE_KEY) - Source files included in the FOD change
Procedure:
- Set hash back to
pkgs.lib.fakeHash - Build, capture new hash from error output
- Update the hash in
flake.nix - Commit the updated hash
Push to Registry
Using the push-image Script
Some flakes include a push-image convenience script:
| |
Manual Push with Skopeo
| |
Service-Specific Patterns
Vite App (verifieddit-www)
Uses FOD for bun install + vite build. Clerk publishable key baked in at build time:
| |
Python Service (badge-signer)
Uses python312.withPackages for Python deps and fetches c2patool binary:
| |
Bun Service (stripe-backend)
Pre-builds with bun run build then copies dist/ into the Nix derivation:
| |
Important: dist/ must exist before nix build. Run bun install && bun run build first.
Hugo Docs
Hugo module downloads require FOD for Go module fetching:
| |
Nix Shell for Development
Each flake provides dev shells:
| |
Common Nix Image Contents
| Package | Purpose |
|---|---|
pkgs.bun | Bun runtime (for JS/TS services) |
pkgs.nodejs_20 | Node.js runtime (when needed) |
pkgs.cacert | SSL CA certificates |
pkgs.coreutils | Basic Unix tools |
pkgs.bash / pkgs.bashInteractive | Shell for entrypoint scripts |
pkgs.nginx | Web server (for static sites) |
pkgs.gettext | envsubst for config templating |
pkgs.gnugrep | grep (not in coreutils on Nix) |
pkgs.nettools | netstat for port detection |
Troubleshooting
fakerooterror on macOS: Thefakerootutility does not work on macOS. Build withnix build .#packages.x86_64-linux.oci-imageto use the Linux builder (OrbStack provides this transparently).- FOD hash mismatch: Dependencies changed. Recompute by setting
outputHash = pkgs.lib.fakeHash;, building, and capturing the new hash. /usr/bin/env: No such file or directory: Nix sandbox does not have/usr/bin/env. Use absolute paths to binaries (e.g.,${pkgs.bun}/bin/bun) or create wrapper scripts.cleanSourceexcludes needed files:pkgs.lib.cleanSourceexcludes.git,node_modules, etc. If you need untracked files (likedist/), usebuiltins.pathinstead.- Cross-compile fails: Ensure OrbStack is running on the Mac Mini M4. Nix delegates
x86_64-linuxbuilds to OrbStack’s Linux VM.