This project supports reproducible builds using Nix flakes to ensure bit-for-bit identical binaries.
For TEE (Trusted Execution Environment) attestation, reproducible builds allow:
- Verification: Anyone can rebuild and verify the binary matches the MRTD measurement
- Transparency: Source code provably corresponds to running code
- Trust: No hidden modifications between source and deployed binary
Install Nix with flakes support:
# Install Nix
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
# Or on macOS with existing Nix:
nix --version # should be >= 2.4| Method | Use Case | Platform Support | Build Time | Reproducibility |
|---|---|---|---|---|
| Pure Nix | Linux CI/production | Linux only | ~15-25 min cold, 2-5 min warm | ✅ Fully reproducible |
| Dockerfile.nix | Local dev on macOS | Any (Docker) | ~15-25 min cold, 2-5 min warm | ✅ Fully reproducible |
| Local binary | Development/testing | Native platform | Fastest (incremental) |
Platform: Native (macOS/Linux) Use case: Local development and testing
# Build the application for your platform
nix build .#oauth3
# Run it
./result/bin/oauth3
# Or run directly
nix runNote: This builds for your native platform (macOS on Mac, Linux on Linux). Not suitable for Docker deployment from macOS.
Platform: Linux (x86_64-linux or aarch64-linux) Use case: CI/CD pipelines, Linux servers, production builds
# Build Docker image using Nix (Linux only)
nix build .#dockerImage
# Load into Docker
docker load < result
# Run it
docker run -p 8080:8080 oauth3:nixRequirements:
- Must run on Linux (not macOS)
- Or use remote Linux builder
- Or build in Linux Docker container (see Method 3)
Advantages:
- Fastest on Linux systems
- Native Nix store caching
- Smallest output artifact
Platform: Any (macOS, Linux, Windows with Docker) Use case: Local development on macOS, cross-platform builds
# Build using Nix inside Linux container
docker build -f Dockerfile.nix -t oauth3:nix-docker .
# Run it
docker run -p 8080:8080 oauth3:nix-dockerHow it works:
- Uses
nixos/nix:2.26.3Linux container as builder - Runs
nix buildinside the container - Copies minimal runtime closure to final Debian image
- Same reproducibility guarantees as pure Nix
Build time:
- Cold build (no cache): 15-25 minutes
- Warm build (deps cached): 2-5 minutes
Advantages:
- Works on macOS without cross-compilation
- Produces Linux binaries suitable for Docker
- Same Nix reproducibility guarantees
- Smaller final image (minimal Debian base)
Trade-offs:
- Slower than native Linux builds (Docker overhead)
- Larger build-time resource usage (two-stage build)
Update docker-compose.yml:
services:
app:
build:
context: .
dockerfile: Dockerfile.nix # Use Dockerfile.nix for reproducibility
# ... rest of configThen:
docker compose up --build→ Use Dockerfile.nix
- Works without additional setup
- Fully reproducible
- Can build and test Linux images locally
→ Use Pure Nix (nix build .#dockerImage)
- Fastest build times
- Best caching behavior
- Direct Nix store integration
→ Use Local Binary (nix build .#oauth3)
- Fastest iteration cycle
- Good for development
- Not suitable for production
The Nix build ensures:
- Locked Dependencies:
flake.lockpins all inputs - Fixed Rust Version: rust-toolchain.toml specifies exact version
- Deterministic Builds: CARGO_INCREMENTAL=0, fixed timestamps
- Isolated Environment: No host system contamination
Two developers building from the same commit should get identical binaries:
# Developer A
nix build .#oauth3
sha256sum result/bin/oauth3
# Developer B (same commit)
nix build .#oauth3
sha256sum result/bin/oauth3
# Hashes should match exactlyEnter a development environment with all tools:
nix develop
# Now you have:
# - Rust toolchain
# - diesel-cli
# - PostgreSQL client
# - rust-analyzer# Normal Cargo workflow
cargo update# Update all inputs
nix flake update
# Update specific input
nix flake lock --update-input nixpkgsgit add flake.lock Cargo.lock
git commit -m "Update dependencies"-
Build locally with Nix:
nix build .#oauth3 sha256sum result/bin/oauth3 # Record this hash
-
Generate MRTD: Deploy to TEE and get attestation quote
-
Verify MRTD matches: Extract MRTD from quote, compare to expected hash
-
Anyone can reproduce: Others build from same commit, verify hash matches
| Aspect | Standard Dockerfile | Dockerfile.nix | Pure Nix (Linux) |
|---|---|---|---|
| Reproducibility | ❌ No | ✅ Yes (bit-for-bit) | ✅ Yes (bit-for-bit) |
| Platform | Any (Docker) | Any (Docker) | Linux only |
| Dependency pinning | Cargo.lock only | Cargo.lock + flake.lock | Cargo.lock + flake.lock |
| Build cache | Docker layers | Nix store in container | Nix store (native) |
| Offline builds | ❌ No | ✅ Yes (with cache) | ✅ Yes (with cache) |
| Cold build time | ~2-3 min | ~15-25 min | ~15-25 min |
| Warm build time | ~2-3 min | ~2-5 min | ~2-5 min |
| macOS support | ✅ Yes | ✅ Yes | ❌ No (requires remote builder) |
| TEE verification | ❌ No | ✅ Yes | ✅ Yes |
Enable flakes in /etc/nix/nix.conf:
experimental-features = nix-command flakes
Ensure libpq is available:
nix develop
pkg-config --libs libpqError: required system or feature not available: aarch64-linux
Solution: Use Dockerfile.nix instead:
docker build -f Dockerfile.nix -t oauth3:nix-docker .Or set up a remote Linux builder (advanced).
Image sizes:
- Dockerfile.nix: ~300-400 MB (minimal Debian + runtime deps)
- Pure Nix: ~450 MB (all dependencies in /nix/store)
- Standard Dockerfile: ~100-150 MB (minimal Alpine)
For production, consider:
- Using multi-stage builds to minimize runtime dependencies
- Stripping debug symbols
- Using distroless base images