Skip to content
Blog, 12 November 2026

Nix: The House Built on Rock

All writing in this blog was done 100% by hand, without use of AI even for grammar or spell-check. Graphics were made with assistance of AI, typically Claude.

"Everyone then who hears these words of mine and does them will be like a wise man who built his house on the rock. And the rain fell, and the floods came, and the winds blew and beat on that house, but it did not fall, because it had been founded on the rock. And everyone who hears these words of mine and does not do them will be like a foolish man who built his house on the sand. And the rain fell, and the floods came, and the winds blew and beat against that house, and it fell, and great was the fall of it."

- Gospel of Matthew 7:24-27 (ESV)

Regardless of what you think about the Bible, I think we can all learn something by remembering the parable about the wise man who built his house on the rock vs the foolish man who built on sand. Humans have been building complex systems for thousands of years, and this advice about choosing the best foundation for your project still rings true today. Granted, many of us engineers are now more focused on building agentic systems, which they would probably see as using magic to trick rocks into thinking.

But still, when we as software engineers are thinking about how to use Claude or ChatGPT or Deepseek or whatever else to make the next software product that changes the world, we should heed these words and focus on building on the best, rock solid foundation we possibly can. In my opinion, Nix exists today as an overlooked tool that lets you accomplish this. In this article, we will explore how Nix provides a deterministic, consistent base to reduce the uncertainty of coding agents, why you would want to use Nix over Docker alone, and how you should go about integrating Nix into your existing project to get immediate, tangible benefit.

A Deterministic Alternative

Nix is a framework for building, packaging, and consuming software in a way that eliminates uncertainty around both the process and result of a software compilation. The guarantee that Nix provides is that any two machines using it to take human or llm-written source code and compile that into a program will come out with the same executable, down to the exact byte of machine code.

We accomplish this by treating software compilation as a non-deterministic pure function; which in common language is a process you can repeat over and over where, as long as the inputs are the same, you know the output will be the same, without any observable behavior outside your system, like setting variables or deleting and adding files. In Nix's case, we treat a software compilation as a pure function where the inputs are things like source files, compiler executables, and compilation settings, and the output is the resultant binary.

InputValue/nix/store/…
sourcejq-1.7.1.tar.gzvm0wql28…
compilergcc-12.3.0vylmp73q…
C libraryglibc-2.38-27j6mwswpa…
regex libraryoniguruma-6.9.9np04y67l…
parser generatorbison-3.8.2g0xq5fyb…
build shellbash-5.2-p157dpxg7ki…
configure flags--bindir=… --mandir=…
run test suiteyes
Not inputs: the build sandbox hides these
build machineHydra build farm
build date10 Jan 2024
your $PATH, $HOME, …whatever

Output

/nix/store/sff1hyf3g5iahj7wbspgavajy10ljg31-jq-1.7.1-bin

The output path is a hash of every input above. Change one and see.

Figure 1. The inputs to jq 1.7.1 in nixpkgs (nixos-23.11, 24 Jan 2024). Every path is real, computed by Nix from that revision with each change made as a one-line override.

What's nice about pure functions is that they compose, meaning when compiling one program depending on another program to provide part of its functionality, as long as both compilations follow this pure and functional process you will end up with an resultant system thats completely deterministic.

Now granted, pretty much all build systems and package managers try to do this when they can, to varying degrees of success. But there is no mainstream comparison to Nix when it comes to the extent of the implementation of this philosophy. For example, take the set of common dependencies that run in almost every single computer on earth, like libsqlite, libcrypto, libuv, and libc that implement functionality such as interacting with the filesystem, dealing with encryption, and talking to the database. Today these typically are distributed as shared object binaries, located on the vast majority of machines somewhere in the /usr/lib/ folder or some kind of similar library folder.

When a program, take git for example, wants to use the dependency it loads that file into its run-time and then reuses the instructions it contains. This is great for saving disk size, because you don't need to have two copies of the same thing, but it sacrifices reproducibility to do so. What if I have two seperate computers both using git, where one happens to have a different version of libc installed than the other? Ultimately we find ourselves in the situation where one program can behave in a fundamentally different way than the other not because ofhow the program itself is different, but rather because of the way the program is packaged and consumed.

Nix solves this by building each program in a isolated environment where you have to exactly specify everything visible to the compiler, so not only the process that produces the binary, but also all of the libraries and environment variables and compiler settings and whatever else goes into making it. If libc isn't specified as a dependency, the build itself will fail at link time as the isolated environment will refuse to use whatever version you have on your laptop, or pass in any environment settings that other program may have set somewhere you forgot about or are not aware of.

This guarantees that, no matter the external software or environment, if I run the build on my laptop and you run it on your laptop, as long as both succeed and have the same input settings, we will both come out with the same, deterministic, byte-identical output.

Compare with
InputBack thenToday
FROM debian:12-slim2bc5c236 (Nov 2023)≠ c67e92c8 (Nov 2026)
apt repositorysnapshot, 13 Dec 2023≠ deb.debian.org
apt: gcc-1212.2.0-14≠ 12.2.0-14+deb12u1
apt: libc6-dev2.36-9+deb12u3≠ 2.36-9+deb12u14
apt: libtool2.4.7-5≠ 2.4.7-7~deb12u1
jq source (git)71c2ab571c2ab5
/jq binary (sha256)164e3872ffd6≠ 2459f8d3b1d4
Image digestsha256:4ee43e1db9f0sha256:031f142bace0

✗ different output

debian:12-slim and apt resolve to newer packages today, so the compiled binary itself is different.

Figure 2. jq 1.7.1, rebuilt both ways on 14 Nov 2026. Docker: the release's own Dockerfile, built twice with --no-cache; the 2023 build pins the base image and apt to 13 Dec 2023 snapshots. Nix: nixpkgs 7824e49, rebuilt from source with nix build --rebuild and checked against the build Hydra made on 10 Jan 2024.

Who cares if software builds are deterministic?

This leads us to Nix's first easy win, the elimination of the "works on my machine" problem. Desktop environments are messy, and you likely have programs and settings from other projects that interfere with your build, whether you know it or not. And especially today, where you expect even small startups and open source projects to have CI processes that automatically run tests and builds in things like GitHub actions, we are using a wider variety of environments to develop software than ever before.

Software built and developed with Nix is able to handle these disparate environments better than anything else. Because you can be confident that software running everywhere is identical down to the last byte, you are way less likely to run into transient issues, and when you do you can solve them quickly in a way that prevents them from showing up again. Even without taking AI into account, its worth it to run your development environment through Nix to ensure you can use whatever machine you have available to you to do your job.

Prioritizing determinism doesn't necessarily mean that you will write less buggy or more accurate code: software that consistently does the wrong thing every time you run it is still wrong. But it does mean that when something goes wrong you can more easily trace the issue back to the faulty component.

Determinism is most obviously valuable in high-assurance domains like aerospace, defense, healthcare, and finance that have high consequences for failure. When a plane crashes and the issue is known, regulators and the industry can make changes and apply lessons learned to future designs to reduce the likelihood of that error ever happening again. It also means that you can start to build tools that try to make bounds and guarantees of your software's behavior before runtime. For industries like flight where there is strong regulator presence this isn't some kind of theoretical discussion about quality, rather its the difference between customers being willing and able to use your products, and going bankrupt because nobody has confidence in you.

Granted, flight systems are an extreme when it comes to quality requirements, and many industries are willing to give up on some element of QA to get in on the speed and time to market advantage that agentic development provides. But it's a mistake to think of having a strong QA process as only being something to prevent disaster. For the last few years I have been firmly embedded in the world of startups, where fail fast and fail often in the search for product market fit takes priority over measuring and reducing failure rate accross millions and billions of events. But fail fast fail often only works when, not only is the cost of failure low, but you can fix and recover from those failures quickly. I'm sure that when investigating failures, either you or your team of agents will appreciate, because you built your system using Nix, you know exactly what was running at time of failure and don't have to guess whether the deployed binary was different than what it should be.

The overall point here is that if it makes sense to sacrifice some amount of quality for velocity, you should at least take the time to build a solid foundation to prevent your codebase becoming an unmaintainable quagmire. You can recover from a few faults in non-load bearing beams on the upper stories of a mansion, but cracks in the foundation could lead to catastrophic collapse. Fixing those cracks now will give you the confidence and ability to build even higer.

Doesn't Docker already do this for me?

Docker solves a reusability problem by letting you run the same software across disparate architecture, which certainly helps with reproducibility but by itself doesn't constitute a solution. Many seem to think that Docker offers reproducibility, but this is best settled by a paper written earlier this year by researchers from Telecom Paris.1 They actually tested thousands of publicly available Docker images to emperically determine whether building the same image resulted in a bit-identical result. Long story short:

"Our results show that Docker does not guarantee reproducibility under any tested definition, nor is there a “silver bullet” set of rules for writing Dockerfiles yielding reproducible images."

- Malka, Zacchiroli and Zimmermann, Docker Does Not Guarantee Reproducibility, 2026

This was suprising in that it made Docker look even worse than I previously thought. Out of the containers tested, only 72.6% were even successfully rebuildable without running into an unrecoverable error. The biggest culprit of non-reproducability is the frequent usage of mutable tags like latest, which do not constitute a pin to a specific dependency, rather allowing the maintainer to push arbitrary updates as they see fit. Further compounding this is the availability of the internet in Docker builds, which introduces arbitrary behavior dependent on servers accessed at build time.

Beyond just the raw success of the rebuild, only 37.3% had the same general set of packages included as part of the resultant image, 4.6% having not only the same packages but also the exact same versions of those packages. Only 0.3% of builds offered the strong guarantee that Nix does, which is the exact same output files for the exact same input.

Hover or tap a bar for its count and definition.

Figure 3. Docker images rebuilt from GitHub Actions workflows that last built them in 2023, and how many still matched the original.Source: Malka, Zacchiroli and Zimmermann, arXiv:2601.12811, §4.

Some advise that you can use Docker with strong discipline and rules to fix this, however the same researchers tested settings that are commonly advised as providing reproducibility like pinning dependencies, not using latest, and avoiding temporary files. Ultimately they found that they could help but had a weak effect.

Now, that isn't to say that Docker isn't useful or a solid tool, just that it isn't made for build reproducability. Docker is best at deploying the same software across many different environments and having it still more or less work. The distribution architecture that combines the best of both worlds is one that builds a Docker image using Nix, so you get the benefits of consistent, reproducible software thats able to be deployed to many different platforms. Just don't expect a Nix-less Docker container you build and test on your machine to be the same, or even to have the same set of dependencies as the one your colleague is building on their machine, and especially not the one building on CI.

Integrating Nix

Like I said earlier, I consider Nix to be more of a framework and suite of tools rather than one specific thing. What binds them all together is the Nix language, which I think of as a cross between Haskell and JavaScript.

Like JavaScript, it's dynamically typed and is built around JSON-like data structures, just with a different syntax. However, like Haskell, to support pure, non-deterministic builds, the Nix language takes a functional approach with pure methods and lazy evaluation. However, don't let the comparison to Haskell fool you, it's extremely simple and easy to learn, with only a few core concepts and very little syntax sugar, and in my opinion it would be a great teaching language for a CS 101 course.

After installing Nix on your laptop, the first easy win is to create a devshell. Devshells enable you to make the tooling used to build and write your software portable, so that if someone else wants to get started on development they don't have to seperately download helpers like pnpm, foundry, pulumi, and the aws cli. Instead they just run one command, nix develop, which installs all packages and gives them an environment where everything is consistent with what you have already been using to develop.

This is especially relevant for agents, which if you are following good security practice should be running in a headless VM, likely to have a totally different environment and packaging ssytem than your, often MacOS, development laptop. This means less tokens and time used by your agent trying to setup the tooling it needs for development, and it can rather focus its full efforts on actually writing code and not installing peripheral software.

If you are responsible for security in your organization, you're probably deeply worried that one of your developers will run npm update or some other tool that implicity updates a dependency that happens to be malware. Enterprises like Amazon mitigate this by having an internal repository of dependencies teams are required to use that is continually reviewed and approved by an internal dedicated security team, but this is probably way too much work for startups and smaller companies.

Instead, you can mitigate the greatest risk of supply chain attacks by, of course, simply using Nix. Because of determinism, Nix won't update dependencies unless you explicitly tell it to, which automatically solves the greatest vector that led to Shai Hulud and the recent wave of major attacks.

npm update on every build
Nix, from flake.lock
npm: days on the malicious release
0
Nix: days on the malicious release
0

⚠ malicious 4.2.2safe releasenot yet built

Figure 4. A made-up month for one dependency. Drag through the days, and press nix flake update to move the pin.

If you want to go the extra mile and have an Amazon-style whitelisted repository, Nix is probably the easiest way to do that. This way you get to use the base repository of Nixpkgs, which is already extremely comprehensive and larger than Debian's and offers an easier integration with language-level dependency management tools like pnpm and cargo, in a way that tools like apt and dnf don't.

Maybe even try NixOS

If you are super impressed with the Nix ecosystem, you may want to start running at least your development machine on NixOS, which essentially takes the Nix package management system and applies it to all system configuration, making it possible to configure things like your filesystem, device settings, and firewall with the Nix language. I have been daily driving this operating system for roughly 6 months now, and the thing I like the most about it is that it makes me feel comfortable spending time "ricing" my system to make it custom to exactly the way I want it.

Previously I was discouraged from spending time configuring my software because, even if I had the same dotfiles, if I switched laptops or operating system all that time would be wasted. Now, even if I go back to Arch or Debian I know that I can carry over the bulk of my configuration along with me. I tested this last month, when after my laptop died it took me less than an hour to get an identical system running on a new laptop.

If you are using agents heavily I would especially encourage you to try out Nixos, given that the burden of learning the complex system is lessened with AI guidance, and Nixos is particularly equipped to recover from failure. Because of determinism, if you or your agent makes a change that bricks everything its trivially easy to revert to a previous configuration that you know worked, rather than resetting everything or trying to hunt down what file or setting was changed that broke your laptop.

Boot menu

    Figure 5. Every rebuild of a NixOS configuration is a new generation. Let an agent make changes, then roll back past the ones that break the boot.

    Putting QA back into agentic development

    Its no secret that agentic software engineering is dramatically rising, with stackoverflow reporting 37% of surveyed developers using AI Agents at work daily this year in May 2026, up more than double from 14% in 2025.2 But trust in agents is still shaky, as from the same survey only about 5% report trusting ai for "many tasks and decisions". Though there has been a marked increase, developers still feel hesitant about the quality of work that agents produce, and clearly there is a lot of work to be done to make the output of AI agents reliable and high-quality.

    • Daily
    • Weekly
    • Monthly or less
    • Not using agents
    2025
    2026

    Hover or tap a segment for its share.

    Figure 6. Developers using AI agents at work, by how often.Source: Stack Overflow, Agents on a leash: 2025 Developer Survey and April 2026 pulse survey (1,100 respondents). Rows may not sum to 100% from rounding.

    Nix is particularly useful for those who want to build quality software while retaining the advantage of feature velocity provided by the agentic paradigm. If you want to go beyond making things that gets met with slop allegations then you need to make sure that agents have access to the best possible base so every small mistake they do make are less able to compounding into something that destroys the entire system.

    I don't think that Nix was just quite invented when the Bible was written, but if it was they may very well have used it instead of the house on rock as an analogy for creating systems that stay solid in the face of pressure and abuse.

    References

    1. Julien Malka, Stefano Zacchiroli and Théo Zimmermann, “Docker Does Not Guarantee Reproducibility,” arXiv:2601.12811, January 2026. ↩

    2. Erin Yepis, “Agents on a leash: Agentic AI remains mostly single-agent and monitored at work,” Stack Overflow Blog, May 27, 2026. ↩

    All posts