Motivation
Why Nix?
Section titled “Why Nix?”Broken Window Fallacy
Section titled “Broken Window Fallacy”
Broken Window Examples
Section titled “Broken Window Examples”Nix Warts
Section titled “Nix Warts”Language
Section titled “Language”- Non-mainstream syntax
- Laziness surprises
- Poor error messages
- Reading is easier than writing
- Debugging can feel indirect
Purity & Friction
Section titled “Purity & Friction”- “Why can’t I just curl this thing?”
- “Why is this forbidden?”
- “Why do I need to declare this explicitly?”
Learning Curve
Section titled “Learning Curve”- Steep initial slope
- Feels slower before it feels faster
- Easy to feel blocked early on
Documentation and tooling
Section titled “Documentation and tooling”- documentation -> Could be better
- ide support -> Could be better
Why these warts exist
Section titled “Why these warts exist”| Frustration | What It Enforces |
|---|---|
| No implicit deps | Explicit dependency graphs |
| No global state | Reproducibility |
| No network | Hermetic builds |
| Immutable store | Rollbacks & safety |
| Weird language | Laziness + composability |
Nix makes you pay the cost upfront
Section titled “Nix makes you pay the cost upfront”Nix charges you early for mistakes you normally pay for later.
| Missing dependency | caught at build time, not prod |
| Undeclared tool | caught immediately, not on a coworker’s laptop |
| Drift | impossible, not “unlikely” |
| extensibility | add/change functionality without touching old code |
The Honest Promise
Section titled “The Honest Promise”Nix doesn’t save time immediately. It stops time from leaking later.
Comparing to other solutions
Section titled “Comparing to other solutions”Surely something else exists that is comparable. Right?
Docker
Section titled “Docker”How Docker Builds Really Work
Section titled “How Docker Builds Really Work”- Docker Builds Are Filesystem-Based, Not Dependency-Based
- Docker images are built as a stack of filesystem layers
- Each instruction in a Dockerfile (RUN, COPY, ADD, etc.) creates a new layer
Layers record:
- file additions
- file deletions
- file modifications
Docker does not understand:
- why a file exists
- which files are dependencies vs outputs
- which tools were actually used
# DockerfileFROM ubuntu:22.04RUN apt-get update && apt-get install -y curl wget vimRUN echo "Hello from Docker!" > /hello.txtCMD ["cat", "/hello.txt"]What Docker Does Well
Section titled “What Docker Does Well”- Produces a runnable, isolated filesystem snapshot
- Ensures runtime parity between machines
- Freezes results, not process
What Docker Cannot Guarantee
Section titled “What Docker Cannot Guarantee”- Deterministic builds over time
- Reproducible images from the same Dockerfile
- Explicit dependency graphs
- Hermetic builds
Implications
Section titled “Implications”- Docker tracks what changed on disk, not what the build depends on.
- Docker isolates well, but doesn’t integrate well
- half tools on local machine, half the tools inside docker images
Others
Section titled “Others”The industry keeps independently rediscovering the same problems — and inventing partial solutions inside narrower scopes.
Nix didn’t invent these concerns. It’s the first tool that tries to solve them generally, instead of per-language or per-runtime.
- asdf / mise – great for tools, not system deps, no purity
- devcontainers – good UX, still Docker-centric, heavy
- bazel / pants – excellent builds, weak local dev ergonomics
- ansible / chef / salt – fleet config, poor dev loop
- homebrew + scripts – works… until it doesn’t
Case Studies
Section titled “Case Studies”What Yarn Was Trying to Fix
Section titled “What Yarn Was Trying to Fix”- Non-deterministic installs (package.json alone was not enough)
- “Works on my machine” caused by solver drift
- Hidden transitive dependency changes
- Slow, stateful installs
How Yarn Addressed It
Section titled “How Yarn Addressed It”- Lockfiles (yarn.lock) → explicit dependency graphs
- Deterministic resolution → same tree everywhere
- Content-addressed cache → reuse instead of rebuild
- Offline installs → fewer hidden dependencies
Why This Matters
Section titled “Why This Matters”Yarn is an admission that imperative package installs are not reliable
The industry accepted:
Section titled “The industry accepted:”- reproducibility matters
- dependency graphs must be explicit
Where Yarn Falls Short (By Design)
Section titled “Where Yarn Falls Short (By Design)”- Only governs JavaScript dependencies
- Assumes the system environment already works
Cannot model:
Section titled “Cannot model:”- compilers
- native libraries
- OS differences
- build tools
SBT (Simple Build Tool)
Section titled “SBT (Simple Build Tool)”sbt wasn’t meant to be “just Scala’s Maven.”
Its original ambitions:
Section titled “Its original ambitions:”- Incremental builds
- Precise dependency tracking
- Declarative build definitions
- Programmatic builds (builds as code)
- Cross-project composition
- Sound familiar again?
sbt introduced:
Section titled “sbt introduced:”- Fine-grained incremental compilation
- Task graphs
- Dependency-aware caching
- Build definitions as Scala code
These ideas later influenced:
Section titled “These ideas later influenced:”- Bazel
- Pants
- Buck
- Gradle
Simple build tool -> Scala build tool
Section titled “Simple build tool -> Scala build tool”Design implications
Section titled “Design implications”- Host Environment Leakage
- Non-Hermetic Builds
- Scope Explosion
User experience
Section titled “User experience”- Hard to reason about
- Hard to standardize
- Hard to cache globally
Haskell
Section titled “Haskell”Haskell learned to treat dependency updates like a pipeline, not a fire drill
Historical Versioning Problems
Section titled “Historical Versioning Problems”- Lots of small libraries
- Deep dependency trees
- Frequent upstream changes
- Builds breaking due to solver drift / native deps
What emerged
Section titled “What emerged”- Pin a complete package universe (compiler + libraries + native deps).
- Make updates mechanical (not artisanal).
- Build everything automatically to see what breaks.
- Treat breakage as data (a matrix), not a surprise.
- Promote only the green set into the “blessed” environment.
Landing on nix
Section titled “Landing on nix”Considering the problem
Section titled “Considering the problem”Given the constraints of:
- reproducibility
- composability
- local developer ergonomics
- production integration
- long-term maintenance
Considering the available solutions
Section titled “Considering the available solutions”- Docker
- bazel
- pants
Conclusion.
Section titled “Conclusion.”Nix is the only tool I’ve found that makes the full set of tradeoffs explicit and survivable.