Fundamentals of Hermetic Builds
Understand what makes a build hermetic, why it matters, and the core principles of reproducibility and determinism in build systems.
Fundamentals of Hermetic Builds
Master hermetic builds with free flashcards and spaced repetition practice. This lesson covers reproducibility principles, dependency isolation, and environment consistencyβessential concepts for reliable software development and deployment pipelines.
Welcome π»
Have you ever heard a developer say "it works on my machine" when software fails in production? Or struggled to rebuild an old project because dependencies changed? Hermetic builds solve these problems by creating completely self-contained, reproducible build environments.
In this lesson, you'll learn what makes a build hermetic, why hermetic builds are critical for modern software development, and how to implement hermetic principles in your own projects. Whether you're working on a small application or managing complex distributed systems, understanding hermetic builds will transform how you think about software reproducibility.
What is a Hermetic Build? π
A hermetic build is a build process that is:
- Completely self-contained - All dependencies are explicitly declared and versioned
- Deterministic - The same inputs always produce the same outputs
- Isolated - No dependence on the host system's state or configuration
- Reproducible - Anyone can rebuild it with identical results, anywhere, anytime
The term "hermetic" comes from Hermes Trismegistus and "hermetically sealed" - meaning airtight and completely isolated from the outside environment. Just as a hermetically sealed container prevents air from entering, a hermetic build prevents external factors from affecting the build process.
π‘ Key Insight: A truly hermetic build should produce byte-for-byte identical outputs when given the same inputs, regardless of when or where it runs.
Core Principles of Hermetic Builds π―
1. Explicit Dependencies π¦
Every dependency must be:
- Explicitly declared in configuration files
- Version-pinned to specific releases (not "latest" or version ranges)
- Content-addressed using checksums or hashes
- Stored reliably in artifact repositories
| β Non-Hermetic | β Hermetic |
|---|---|
npm install express |
npm install express@4.18.2 + lockfile |
| Uses system Python | Downloads Python 3.11.4 from verified source |
Pulls Docker tag latest |
Uses digest @sha256:abc123... |
2. Environment Isolation ποΈ
The build must not depend on:
- System environment variables (except explicitly passed ones)
- Global installations (system packages, tools)
- Network access during build (all deps fetched beforehand)
- Filesystem state outside the build directory
- Current time/date (unless explicitly needed and controlled)
- Random number generators with unpredictable seeds
ββββββββββββββββββββββββββββββββββββββββββββββ β HERMETIC BUILD SANDBOX β β β β ββββββββββββββββββββββββββββββββββββ β β β Build Process β β β β βββββββββββ βββββββββββ β β β β β Source β β Deps β β β β β β Code β β(pinned) β β β β β ββββββ¬βββββ ββββββ¬βββββ β β β β β β β β β β ββββββββ¬βββββββ β β β β β β β β β βββββββββββ β β β β β Build β β β β β β Output β β β β β βββββββββββ β β β ββββββββββββββββββββββββββββββββββββ β β β β β No system access β β β No network during build β β β No global state β ββββββββββββββββββββββββββββββββββββββββββββββ
3. Deterministic Outputs π²
The build process must produce identical results every time:
- No timestamps embedded in artifacts (unless from source control)
- Sorted file lists (filesystem order varies by OS)
- Fixed ordering in archives, manifests, and metadata
- Reproducible randomness (fixed seeds when needed)
- Normalized paths (no absolute paths that vary by machine)
4. Content Addressing π
Dependencies are referenced by their content hash rather than just name and version:
| Reference Method | Example | Hermetic? |
|---|---|---|
| Name only | lodash |
β Version unspecified |
| Name + version | lodash@4.17.21 |
β οΈ Version could be republished |
| Name + version + hash | lodash@4.17.21 + SHA256 |
β Content verified |
π‘ Pro Tip: Modern package managers like npm (package-lock.json), Cargo (Cargo.lock), and Go modules (go.sum) automatically track content hashes.
Why Hermetic Builds Matter π
Reproducibility Benefits
Security Auditing: If you can reproduce a build from 6 months ago, you can verify whether a security vulnerability was present.
Debugging: Reproduce the exact binary that failed in production to debug issues.
Compliance: Prove that released software matches audited source code.
Collaboration: Every team member gets identical build outputs.
The Cost of Non-Hermetic Builds
THE DEPENDENCY DRIFT PROBLEM Day 1: Day 30: Day 90: βββββββ βββββββ βββββββ βBuildβ βBuildβ βBuildβ β β β β β οΈ β β β β ββββ¬βββ ββββ¬βββ ββββ¬βββ β β β β β β lib@1.0.0 lib@1.0.1 lib@2.0.0 (works) (warning) (breaking) Without version pinning, dependencies change underneath you!
Core Concepts in Detail π
Build Caching ποΈ
Hermetic builds enable safe caching because:
- Input hashing: Hash all inputs (source + dependencies)
- Cache lookup: Check if this input hash was built before
- Cache hit: Reuse previous output if hash matches
- Cache miss: Build and store result with input hash
CACHE DECISION FLOW
π Source Code + π¦ Dependencies
|
β
π Hash Inputs
|
β
π Check Cache
|
βββββββ΄ββββββ
β β
β
Hit β Miss
| |
β β
Reuse Build It
Output |
β
Store in Cache
Why hermetic builds make caching safe: Non-hermetic builds might read system state or network resources, so cached outputs could be wrong. Hermetic builds guarantee that identical inputs = identical outputs.
Sandboxing π¦
Build tools enforce isolation through:
Containerization: Docker, Podman isolate filesystem and network
Virtual environments: Python venv, Node.js node_modules, Ruby bundler
Build sandboxes: Bazel sandbox, Nix store paths
| Tool | Isolation Method | Hermetic Level |
|---|---|---|
| Bazel | Sandboxed execution + explicit deps | π’ High |
| Nix | Functional package management | π’ High |
| Docker | Container isolation | π‘ Medium (if used correctly) |
| Make | Dependency tracking only | π΄ Low |
The Dependency Graph π
Hermetic builds require understanding the complete dependency graph:
Your Application
β
β
ββββββββ΄βββββββ
β β
β β
Library A Library B
β β
β β
Library C Library C β Diamond dependency
β β
ββββββββ¬βββββββ
β
(Must resolve to same version)
Transitive dependencies are dependencies of your dependencies. Hermetic builds must pin these too!
Real-World Examples π
Example 1: Non-Hermetic Docker Build β
FROM python:latest
RUN apt-get update && apt-get install -y build-essential
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY app.py .
CMD ["python", "app.py"]
Problems:
python:latestchanges over timeapt-get updatefetches latest package listspip installwithout lockfile installs latest compatible versions- Different results if built today vs. next month
Example 1: Hermetic Docker Build β
FROM python@sha256:a4b5c6d7e8f9...
RUN apt-get update && apt-get install -y \
build-essential=12.9ubuntu3 \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt requirements-lock.txt .
RUN pip install --no-deps -r requirements-lock.txt \
&& pip check
COPY app.py .
CMD ["python", "app.py"]
Improvements:
- Base image pinned to SHA256 digest
- Package versions explicitly specified
- Lockfile with exact versions and hashes
--no-depsensures no surprise transitive deps- Reproducible across time and machines
Example 2: JavaScript Build Evolution π
Stage 1: Non-Hermetic
{
"dependencies": {
"express": "^4.0.0",
"lodash": "*"
}
}
β οΈ Caret (^) and star (*) allow version updates
Stage 2: Better (with lockfile)
npm install # Generates package-lock.json
π‘ package-lock.json pins exact versions and integrity hashes
Stage 3: Fully Hermetic Build Script
#!/bin/bash
set -euo pipefail
## Verify lockfile matches package.json
npm ci # Clean install from lockfile only
## Build with fixed NODE_ENV
NODE_ENV=production npm run build
## Verify output hash
sha256sum dist/bundle.js > dist/bundle.js.sha256
Key difference: npm ci fails if lockfile is out of sync, ensuring consistency.
Example 3: Bazel Build Configuration βοΈ
Bazel is designed for hermetic builds from the ground up:
## BUILD file
load("@rules_python//python:defs.bzl", "py_binary")
py_binary(
name = "app",
srcs = ["app.py"],
deps = [
"@pip_deps//flask", # From locked requirements
"//lib:utils", # Internal dependency
],
python_version = "PY3",
)
Hermetic features:
- Explicit dependency declarations
- Sandboxed execution (process isolation)
- Content-based caching
- Remote execution support
- Reproducible across machines
Example 4: Go Modules πΉ
// go.mod
module github.com/user/project
go 1.21
require (
github.com/gin-gonic/gin v1.9.1
github.com/lib/pq v1.10.9
)
// go.sum (auto-generated)
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
Hermetic properties:
go.moddeclares direct dependenciesgo.sumcontains cryptographic hashes of all deps (including transitive)go buildverifies checksums before building- Module proxy caches ensure availability
Common Mistakes β οΈ
Mistake 1: Using Floating Tags/Versions
β Problem:
image: nginx:latest
β Solution:
image: nginx@sha256:4c0fdaa8b6341bfdeca5f18f7837462c80cff90527ee35ef185571e1c327beac
Mistake 2: Network Access During Build
β Problem:
build:
curl https://example.com/latest-config.json > config.json
gcc -o app main.c
β Solution:
## Fetch dependencies in separate step
fetch:
curl https://example.com/config-v1.2.3.json > config.json
sha256sum -c config.json.sha256
build: config.json
# Build without network access
gcc -o app main.c
Mistake 3: Timestamp Embedding
β Problem:
VERSION = f"1.0.0-{datetime.now().isoformat()}"
Building at different times produces different outputs!
β Solution:
## Use Git commit info (deterministic)
VERSION = f"1.0.0-{os.getenv('GIT_COMMIT_SHA', 'dev')}"
Mistake 4: System Tool Dependencies
β Problem:
## Assumes system has gcc installed
gcc -o app main.c
What version? What architecture?
β Solution:
## Specify exact compiler version
FROM gcc:11.3.0 AS builder
WORKDIR /build
COPY main.c .
RUN gcc -o app main.c
Mistake 5: Mutable Remote Resources
β Problem:
## Pip install from Git branch (mutable)
pip install git+https://github.com/user/repo.git@main
β Solution:
## Pin to specific commit hash
pip install git+https://github.com/user/repo.git@a1b2c3d4e5f6...
Mistake 6: Build Output Varies by Filesystem
β Problem:
## File order depends on filesystem
tar czf archive.tar.gz *
β Solution:
## Sort files explicitly
find . -type f | sort | tar czf archive.tar.gz -T -
Tools and Ecosystem π οΈ
| Tool | Purpose | Hermetic Features |
|---|---|---|
| Bazel | Multi-language build system | Sandboxing, content addressing, remote caching |
| Nix | Package manager | Purely functional, isolated store paths |
| Docker | Containerization | Image layers, digest addressing |
| Pants | Python/JVM build system | Hermetic by default, fine-grained caching |
| Buck2 | Meta's build system | Virtual filesystem, action caching |
Implementing Hermetic Builds: Step-by-Step π
Step 1: Audit Current Build
- List all dependencies (direct and transitive)
- Identify system dependencies
- Find network calls during build
- Check for embedded timestamps/randomness
Step 2: Pin All Dependencies
- Use lockfiles (package-lock.json, Cargo.lock, go.sum)
- Pin base images to digests
- Version system tools
Step 3: Isolate the Build Environment
- Use containers or sandboxes
- Disable network during build phase
- Set fixed environment variables
Step 4: Make Outputs Deterministic
- Remove timestamps (or use from source control)
- Sort file lists
- Normalize paths
- Use fixed random seeds if needed
Step 5: Verify Reproducibility
- Build on different machines
- Build at different times
- Compare output hashes
- Automate verification in CI/CD
Key Takeaways π
β Hermetic builds are self-contained, deterministic, and reproducible
β Pin all dependencies with exact versions and content hashes
β Isolate builds from system state and network resources
β Ensure outputs are deterministic (same inputs β same outputs)
β Use modern tools designed for hermeticity (Bazel, Nix, proper lockfiles)
β Hermetic builds enable safe caching, easier debugging, and better security
β Avoid floating tags, mutable resources, and embedded timestamps
β Test reproducibility by building in different environments
π€ Did You Know?
Google builds over 2 billion builds per week using their hermetic build system (Blaze, the predecessor to Bazel). Every build is reproducible and cached, allowing engineers to reuse build outputs across the entire organization. This saves massive amounts of compute time and ensures consistency across thousands of engineers!
π Further Study
- Bazel Build System Documentation - Comprehensive guide to hermetic builds with Bazel
- Reproducible Builds Project - Tools and practices for verifiable, reproducible software
- Docker Build Best Practices - Creating reproducible container images
π Quick Reference Card: Hermetic Build Checklist
| β Dependencies | All pinned with exact versions + hashes |
| β Base Images | Referenced by SHA256 digest, not tags |
| β Build Isolation | No system state, no network during build |
| β Deterministic Output | No timestamps, sorted files, fixed paths |
| β Lockfiles | Committed to version control |
| β Verification | Output hashes match across rebuilds |
| β Tools | System tools versioned explicitly |
| β Caching | Content-addressed, safe to reuse |