Reproducible and Hermetic Builds
A reproducible build produces identical declared artifacts when someone repeats the documented build from the same source and defined environment. A hermetic build controls what the build can read and use. These properties support each other, but one successful artifact comparison does not establish both. Start by naming the artifact, declaring inputs, and repeating the build in independently prepared environments.
The practical question is where unexplained variation can enter: compiler selection, dependency resolution, environment variables, timestamps, filesystem order, network responses, or packaging metadata. A lockfile addresses part of that list. It does not put the rest of the machine in a jar.
TL;DR
- A reproducible build produces identical declared artifacts when the documented build is repeated from the same source and environment.
- A hermetic build controls what the build can read and use; the two properties support each other but are different.
- Name the artifact, declare every input, and rebuild in independently prepared environments to compare.
- Variation hides in compilers, dependency resolution, environment variables, timestamps, file order, network responses, and packaging metadata.
- A lockfile covers dependencies only, not compilers, plugins, native libraries, or build configuration.
Quick Example
This Python example creates a fictional two-file ZIP archive. It sorts members and fixes timestamps and permission metadata. Changing dictionary insertion order leaves the bytes and hash unchanged, while changing file content changes the archive. It uses stored members rather than compression to keep that variable outside this demonstration.
This checks deterministic packaging for these trusted in-memory inputs in one runtime. It does not compile a project, isolate a process, deny network access, validate provenance, or establish cross-platform reproducibility. Two successful calls can share the same hidden dependency. That is why the next experiment needs an independently prepared environment rather than another press of the same button.
Core Concepts
Define the artifact and the comparison
The Reproducible Builds project defines reproducibility through identical specified artifacts from the same source, build environment, and instructions. Scope matters: an unsigned executable, a debug-symbol archive, and a release bundle can have different comparison boundaries. Declare which output is compared and what inputs are required before reporting success. Reproducible Builds definition
Compare actual bytes or a suitable cryptographic digest of each declared artifact. Record the source revision, build instructions, toolchain identity and configuration alongside the comparison. A matching filename, successful test suite, or matching package version is weaker evidence than artifact identity. Behavioral tests still matter, but they answer another question.
If release signing or notarization introduces a changing envelope, identify that stage explicitly. Do not quietly strip whatever differs until the comparison passes. Explain the boundary and retain the original artifacts so an independent reviewer can understand what was excluded and why. A reproducibility claim becomes less useful when its exceptions live only in someone's terminal history.
Hermeticity makes inputs accountable
Bazel describes hermeticity in terms of isolation from host changes and explicit source and tool identity. The aim is to avoid undeclared dependencies influencing the result. Pinning a toolchain helps, but a build that also discovers a host compiler, reads a user configuration file, or fetches an unpinned response still has inputs to account for. Bazel hermeticity overview
Use the build system's actual isolation mechanisms and verify their behavior. A container image can describe many dependencies, yet the build can still read mounted host files or access the network. A sandbox is useful only within its implemented boundary. Avoid calling a build hermetic merely because its command runs inside a container.
These are proposed investigations, not results from the toy ZIP check. Choose the experiments that address actual inputs in your build, and keep failures as evidence rather than normalizing them away.
Separate identity, caching, and trust
A build cache reuses outputs based on the system's input model. If that model omits an input, a cache hit can conceal the problem rather than prove the output correct. When investigating drift, compare clean execution and cache behavior deliberately. Record which result came from which path.
Likewise, reproducible bytes do not prove that source code is safe or that a dependency is authorized. A malicious program can reproduce perfectly. Provenance, source review, dependency policy, signing, testing, and reproducibility contribute different evidence. Keep their claims distinct so a successful hash comparison does not become a universal security badge.
Best Practices
Write a short build contract: source identity, dependency identities, toolchain, meaningful environment, instructions, expected artifacts, and comparison method. Put the contract near the project so someone outside the original workstation can attempt it. Document required secrets or signing boundaries without embedding secret values in logs or packages.
Investigate differences in a controlled sequence. Compare archive inventories and metadata, then narrow changing content to the responsible build step. Change one suspected input at a time when practical. Rebuilding everything with several flags changed may produce matching output, but it gives weak evidence about the cause of the original drift.
Keep a small regression case for each resolved source of variation. A timestamp fix should survive a later build at a different time; a path fix should survive a different checkout location. Match the experiment to the claim. The example's insertion-order test supports sorted packaging only, so it belongs beside that limited claim.
Escalate to stronger isolation when unknown inputs repeatedly defeat diagnosis or when independent rebuilds are a release requirement. Conversely, a small internal script may begin with documented inputs and repeatable packaging before adopting a complex build platform. The appropriate investment depends on release obligations and the consequences of unexplained variation, not a fashionable tool name.
Common Mistakes
Treating a lockfile as the whole environment
Comparing two builds on one machine
Calling any container hermetic
Normalizing outputs with undocumented steps
FAQ
Can a build be reproducible without being hermetic?
Yes. Repeated builds can happen to receive the same undeclared inputs and produce identical artifacts. That observation is useful within its tested conditions, but stronger control and variation tests are needed to explain why the result should survive a different environment.
Does hermeticity guarantee identical output?
No. An isolated process can still contain nondeterministic behavior or consume changing inputs that the system explicitly permits. Hermeticity concerns the input boundary; reproducibility concerns the declared output comparison. Check both with evidence appropriate to each claim.
What should a team do first?
Choose one important artifact, record the build contract, and try an independent rebuild. Use the first difference to identify a concrete missing input or nondeterministic step. That gives the next improvement a measurable purpose without pretending the whole release pipeline has already been proven.