Purity and Explicit Inputs
This page explains a core Nix idea:
Reproducible builds require controlling what information a build is allowed to see.
Nix is “strict” on purpose. That strictness is what enables:
- reproducibility
- correct caching
- predictable upgrades
- fewer “it worked yesterday” failures
We’ll keep this high-level but hands-on, using commands that reveal what Nix is doing.
What “Purity” Means (Practically)
Section titled “What “Purity” Means (Practically)”When we say “pure” in the Nix context, we mean:
- No hidden dependencies
- No ambient state
- No implicit filesystem probing
- No network access during builds (by default)
- The build is a function of declared inputs
In a pure build, if something influences the output, it should be declared as an input.
The Basic Problem: Scripts Can See the Whole World
Section titled “The Basic Problem: Scripts Can See the Whole World”A typical build script can accidentally depend on:
- whatever is on your PATH
- whatever is installed globally
- what time it is
- what DNS resolves to today
- what happens to be on disk
That makes builds:
- hard to reproduce
- hard to cache correctly
- hard to debug
Nix’s solution is: restrict what the build can see.
A Mental Model You Can Reuse
Section titled “A Mental Model You Can Reuse”Build as a function
Section titled “Build as a function”(output) = build(inputs)If you can’t enumerate the inputs, you can’t claim reproducibility.
Evaluation vs Build: Where Purity Applies
Section titled “Evaluation vs Build: Where Purity Applies”Nix has two important phases:
- Evaluation (pure): compute a build graph from expressions
- Realization (sandboxed): run builders with only declared inputs available
We won’t write Nix code yet—this page is about behaviors you can observe.
Demo 1: “If It’s Not an Input, It Doesn’t Exist”
Section titled “Demo 1: “If It’s Not an Input, It Doesn’t Exist””This is the most important lived experience for new Nix users.
Step 1: Get a temporary shell with only one tool
Section titled “Step 1: Get a temporary shell with only one tool”nix shell nixpkgs#bashInteractive -c bashInside that shell, run:
which git || echo "git is not available"which curl || echo "curl is not available"which python || echo "python is not available"Now exit:
exitWhat this demonstrates:
- your environment can be intentionally minimal
- “I assumed X existed” becomes an explicit choice
Step 2 (optional): Add tools explicitly
Section titled “Step 2 (optional): Add tools explicitly”nix shell nixpkgs#bashInteractive nixpkgs#git nixpkgs#curl -c bashNow:
which gitwhich curlNix makes you declare tools because undeclared tools are hidden dependencies.
Demo 2: Dependency Closure is Explicit (and Inspectable)
Section titled “Demo 2: Dependency Closure is Explicit (and Inspectable)”A big reason purity works is that Nix can compute the full closure of a package.
Pick a package and inspect its closure
Section titled “Pick a package and inspect its closure”HELLO="$(nix path-info nixpkgs#hello)"echo "$HELLO"nix path-info -r "$HELLO" | head -n 20Now compare with a larger package:
PY="$(nix path-info nixpkgs#python3)"nix path-info -r "$PY" | wc -lnix path-info -r "$HELLO" | wc -lWhat this demonstrates:
- dependency graphs are explicit
- you can audit and reason about “what’s included”
- inputs are not hidden in global state
Demo 3: “Purity” Enables Correct Caching
Section titled “Demo 3: “Purity” Enables Correct Caching”Traditional build caching is often heuristic (“did files change?”). Nix caching can be correct because the cache key comes from inputs.
Show that the output identity is stable
Section titled “Show that the output identity is stable”nix build --no-link --print-out-paths nixpkgs#hellonix build --no-link --print-out-paths nixpkgs#helloSecond run should be instant.
Show that changing inputs changes outputs
Section titled “Show that changing inputs changes outputs”nix build --no-link --print-out-paths nixpkgs/nixos-23.11#hellonix build --no-link --print-out-paths nixpkgs/nixos-24.05#helloDifferent inputs ⇒ different output paths. That’s an important guarantee.
What Counts as an “Input”?
Section titled “What Counts as an “Input”?”This is where teams often underestimate the problem. Inputs include more than direct dependencies.
Examples of inputs (often forgotten)
Section titled “Examples of inputs (often forgotten)”- Source code (obvious)
- Build tools (make, cmake, maven, gradle)
- Compiler toolchain (gcc/clang/jdk)
- System libraries (openssl, libc, zlib)
- Environment variables that influence the build
- Patches applied during build
- Configuration flags
- The platform/architecture
Nix works because it tries to make these explicit.
Demo 4: Hidden Dependency on PATH (Simple Illustration)
Section titled “Demo 4: Hidden Dependency on PATH (Simple Illustration)”This demo shows why “ambient PATH” is a build hazard.
Outside nix: what tools are on your PATH?
Section titled “Outside nix: what tools are on your PATH?”command -v gcc || truecommand -v make || truecommand -v python || truecommand -v openssl || trueNow create a very minimal Nix environment and re-check:
nix shell nixpkgs#bashInteractive -c sh -lc 'command -v python || echo "no python"; command -v gcc || echo "no gcc"; command -v openssl || echo "no openssl"'Now add them explicitly:
nix shell nixpkgs#bashInteractive nixpkgs#gcc nixpkgs#openssl nixpkgs#python -c sh -lc 'command -v gcc; command -v openssl; command -v python3'Lesson:
- in Nix, ambient availability disappears
- build dependencies become deliberate
Demo 5: Network Access and “Impure Inputs”
Section titled “Demo 5: Network Access and “Impure Inputs””One of the biggest sources of non-reproducibility is downloading things during a build.
The principle
Section titled “The principle”If a build needs the internet, the internet becomes an input.
Nix generally pushes network access out of the build step and into controlled fetchers.
Identify that fetchers are separate steps
Section titled “Identify that fetchers are separate steps”Run:
nix build --no-link nixpkgs#hello -LWatch whether it:
- downloads a substitute (binary cache)
- or performs local builds
Either way, Nix is trying to ensure the artifact is a known result, not “whatever the internet served today.”
Note: Nix can still download sources/binaries, but it does so in a controlled way that can be hashed and cached.
Demo 6: “Impure” Builds as a Contrast
Section titled “Demo 6: “Impure” Builds as a Contrast”Nix has an “impure” mode for evaluation and some commands. This is useful as a contrast.
Evaluate with and without impurity (flake example)
Section titled “Evaluate with and without impurity (flake example)”Try showing that Nix can reference the current directory as an input:
pwdlsThen:
nix flake showNow compare with:
nix flake show --impureDepending on your flake and environment, this might not change output—so treat it as optional. The takeaway is:
- pure evaluation discourages implicit dependency on “whatever directory I’m in”
--impureexists, but it’s a tradeoff
The Big Tradeoff: Strictness vs Convenience
Section titled “The Big Tradeoff: Strictness vs Convenience”This is where you acknowledge the pain point honestly.
What purity costs you
Section titled “What purity costs you”- You must declare tools/dependencies
- Some “quick hacks” stop working
- You occasionally need to fight the build boundary
What purity buys you
Section titled “What purity buys you”- Builds become explainable
- Bugs become repeatable
- Caches become reliable
- Upgrades become mechanical (bump inputs → build/test → promote)
Nix doesn’t remove work. It removes classes of compensatory work.
How This Connects to Real Org Problems
Section titled “How This Connects to Real Org Problems”Purity + explicit inputs is what makes these possible:
- Onboarding: “enter the dev shell” replaces multi-page setup docs
- CI parity: same inputs in CI and local
- Safer upgrades: build/test matrices reveal breakage immediately
- CVE response: update inputs, rebuild everything, promote green
Practical Takeaways
Section titled “Practical Takeaways”-
Reproducible builds require controlling what a build can see
-
“Explicit inputs” is not ceremony—it’s how you make builds predictable
-
Nix’s strictness is what enables:
- correct caching
- reliable reproduction
- safer upgrades