skip to content

What makes a build hermetic, and why is a hermetic build not automatically deterministic?

level: juniorimportance: must knowfreq 68%

answer

  1. two properties, routinely confused
  2. one is about what goes in
  3. the other is about the bytes coming out
  4. nothing enters once the build starts
  5. clocks and paths are not inputs

basics

~20 s

A hermetic build declares and pins every input before it starts and fetches nothing while it runs. It can still emit different bytes on each run, because embedded timestamps, absolute paths and file ordering vary independently of the inputs.

solid answer

~50 s

Hermeticity is a property of a build's **inputs**: every source file, dependency, tool and base image is declared up front and pinned to exact content, and the build performs no network fetches once it starts. Determinism is a property of the **output**: the same declared inputs always produce the same bytes. They are independent. A hermetic build can still be non-deterministic because it bakes in things that are not inputs at all: the wall-clock time, the absolute path of the workspace, the order the filesystem happened to return files in, the locale, the hostname. Conversely a build that downloads dependencies mid-run might happen to produce identical bytes today and different bytes tomorrow when the upstream changes underneath it. In practice you want both, and you get hermeticity first, because you cannot meaningfully compare two builds until you know they consumed the same inputs.

go deeper

for a junior

Be ready to define both words in one breath: hermetic is about inputs being declared and pinned with no fetching mid-run, deterministic is about the same inputs yielding the same bytes. Give one example of each failing.

for a middle

Expect to enumerate what actually counts as an input, including the toolchain and the base image, and to explain why a version range or a mutable tag does not pin anything.

for a senior

Show how you enforce it rather than intend it: a fetch phase separate from a sealed build phase, hashes verified on the way in, and a hard failure when a step wants something undeclared.

for a principal

Own the argument for sequencing. Hermeticity comes before reproducibility because it makes a byte-level difference diagnosable, and be ready to say which builds in an estate are worth taking all the way and which only need pinned inputs.

## Two different properties that get merged into one word Engineers routinely use "hermetic", "deterministic" and "reproducible" interchangeably. They are not the same claim, and an interviewer asking this question is usually checking whether you can separate them. **Hermeticity is about inputs.** A build is hermetic when the complete set of things it consumes is declared before it starts and pinned to exact content, and when nothing else can enter while it runs. "Declared" means written down somewhere the build system reads: a manifest, a lockfile, a build graph. "Pinned" means identified by content, not by a moving name, so that the same declaration cannot resolve to different bytes tomorrow. "Nothing else enters" means the build step does not open a network connection, does not read an unmanaged directory on the machine, and does not depend on whatever tools happen to be installed on the runner. **Determinism is about output.** A build is deterministic when the same declared inputs produce byte-identical output. When that holds not just twice on one machine but across different machines, directories and dates, people usually call it reproducible. ## Why the two do not imply each other All four combinations exist. - Hermetic and deterministic: the goal. Output is a pure function of declared inputs. - Hermetic but not deterministic: the common case, and the reason this question gets asked. The build consumed exactly the declared inputs, and still stamped the current time into an archive header, embedded the absolute path `/home/runner/work/build-9182` into debug information, and wrote archive members in whatever order the filesystem returned them. None of those are inputs; they are ambient facts about the run. - Deterministic but not hermetic: fragile luck. A build that fetches a dependency by a mutable tag can produce identical bytes on Monday and Tuesday and different bytes on Wednesday when someone republishes that tag. Nothing in your build changed; the world did. - Neither: the default state of most pipelines before anyone works on it. ## What counts as an input The list is longer than most people's first answer. Source, obviously. Third-party dependencies, pinned by digest or by a lockfile that records a hash per component rather than a version range. The compiler and every other tool the build shells out to. The base image or the container the step runs in, referenced by digest rather than by a tag such as `latest`, because a tag is a name that can be repointed while a digest names the content itself. Configuration and environment variables the build reads. Code generators and their templates. The two inputs people forget are the toolchain and the operating-system libraries underneath it. If the runner image is upgraded and your compiler minor version changes, your build consumed a different input even though nothing in your repository moved. ## What "no network mid-build" actually means It does not mean the build never touches the network. It means the fetching is a separate, earlier phase with its own verification, and the phase that transforms inputs into an artifact runs sealed. A typical shape is: resolve and fetch every declared dependency, verify each one's hash against the declaration, place them into a local input tree, then execute the build in a sandbox with no outbound access. If a build step needs something that was not fetched, the correct outcome is a hard failure, not a lookup. Enforcement matters more than intent. A build that is *supposed* to be offline but runs in a sandbox that permits egress is one careless script away from not being hermetic, and nobody will notice until an upstream host changes what it serves. ## Half-measures that look like hermeticity - A version range in a manifest is not a pinned input. `^2.4.0` resolves at build time and can resolve differently next week. - A lockfile with exact versions but no hashes is better but still names rather than pins: an exact version can be republished in some ecosystems. - Caching is not hermeticity. A cache changes where an input came from, not whether it was declared. - Disabling the network without declaring inputs just makes the build fail; declaring inputs without sealing the network just makes the failures silent. ## Why you want it The practical payoff is that the build becomes a function you can re-run. Failures stop being "it worked yesterday"; a change in output implies a change in something you wrote down. An upstream host cannot alter what you shipped without altering a declaration first. And a build cache becomes trustworthy, because a cache key computed over declared inputs actually describes the output.

  • Is an exact version in a manifest a pinned input?
    Not quite. An exact version is still a name that an index resolves for you, and in some ecosystems that name can be republished with different content. Pinning means content-addressing: a lockfile entry that records a hash per component, or an image referenced by digest rather than tag. If a declaration can resolve to different bytes tomorrow, the input is named, not pinned.
  • Which inputs do teams most often forget to declare?
    The toolchain and the environment around it: the compiler, the interpreter, the shell utilities a build script calls, the base image, and the OS libraries linked in. Teams pin their third-party dependencies carefully and then let the runner image float, so a platform upgrade silently changes an input. Pin the image by digest and treat the toolchain as a dependency with a version.
  • Can a build be deterministic without being hermetic?
    Yes, but only by luck. If every upstream it fetches happens to serve the same bytes, output is stable. The moment something upstream is republished or a mirror diverges, output changes with no change on your side, and you have no declaration to diff. That is why hermeticity comes first: it makes a byte difference mean something.

Hermeticity is checking every ingredient onto the counter and locking the kitchen door. Determinism is the cake coming out identical every time - which the locked door alone does not give you if the recipe writes today's date on top.

saying these in an interview costs you the question

  • Says hermetic means the output bytes are identical
  • Treats a version range or floating tag as a pinned input
  • Forgets the compiler and base image are inputs
  • Claims turning off the network alone makes a build hermetic
  • Confuses a warm build cache with a declared input set

context