Antithesis CTO Dave Scherer didn’t intend to become a Nix guru, and he definitely didn’t anticipate that Nix would one day power his company’s software build pipelines. Scherer just got tired of installing and configuring DOS emulation environments on his computers … only for OS updates to break them.
Basically, Scherer learned Nix so he could play old-school games. He certainly didn’t plan to use Nix to reproducibly build and deploy a deterministic simulation-testing platform that gives developers what feel like superpowers. Scherer just wanted to play Doom!
The need for Nix
Antithesis’s flagship software-testing platform runs software in a deterministic hypervisor. It eliminates variability stemming from different kinds of external factors, ensuring bugs can be replicated reliably. In distributed teams, timing, networking, and hardware quirks introduce non-determinism that makes bugs hard to reproduce. Deterministic builds eliminate those sources of variability so issues are easier to track down.
At first, the company relied on dev containers to support local team development and standardize its software build and deployment workflows. Containers gave Antithesis reproducibility, but at the cost of convenience, speed, and friction-free workflows. Developers would mount their project’s source code (usually saved in a repo on their local systems) in a dev container at runtime. They’d then execute their build commands from within the dev container. But developers’ workflows slammed into a wall whenever they needed a tool, library, secret, or resource not available in the container.
This pattern didn’t scale well beyond local development, either, because it involved multiple developers and CI/CD pipelines simultaneously pulling images from Docker Hub. Plus, all of those image pulls led to rate-limiting. The pattern was far from ideal, says John Murray, head of developer experience and internal testing with Antithesis.
“This ‘worked,’ but it was just not a great workflow. It was very janky. Plus, we had one million and one different containers, because as you're developing, you're changing things all the time,” Murray explains.
“Basically, everyone wanted us to find something better. At least we knew just where to look!”
Determining to use Nix
Some orgs come to Nix after a stretch of wandering in the desert, searching for some solution, any solution, to intractable build problems. Some identify Nix early on, reluctantly conclude that it’s too complex, but come back to it after experimenting with other tools. Turns out Nix isn’t nearly as complex as these alternatives. Antithesis was lucky. It already had a Nix champion in its own CTO.
Scherer’s experience convinced him Antithesis could use Nix throughout its SDLC—not just for building software, but for running its testing platform. “Basically how our Antithesis testing platform works is we have a deterministic hypervisor that's FreeBSD-based and we run NixOS [in a VM] on top of that,” Murray explains. “All of our infrastructure and builds for about two or three years now have been Nix.”
But why Nix? Is there something different, something distinctive about Nix itself?
The tl;dr explanation is that Nix lets Antithesis achieve immutability, reproducibility, atomicity, and determinism throughout its SDLC and in the software environments that run on its testing platform.
Determinism is the key. Antithesis’ platform expects to test software in reproducible runtime environments where all dependencies, variables, and other settings can be strictly controlled. These runtimes execute in the “Determinator,” a hypervisor that provides a deterministic hardware and network environment by simulating a standard implementation of the x86-64 instruction set, floating-point unit, and complex instructions, along with factors like network latency, packet loss, and bandwidth constraints. By replaying a sequence of events that always produces the same outcome, Antithesis can reproduce even elusive bugs. This lets Antithesis simulate and replicate the behavior of a distributed system under controlled conditions.
This is where Nix comes into the mix. For its deterministic testing environment to work as intended, Antithesis needs a way to eliminate variability in the build process as a source of nondeterminism.
“We use Nix because we want to make sure our builds are reproducible. That way we get the same exact software when we run it in the hypervisor,” Murray explains. “It’s one thing to remember what code and variables you give a system, it’s another to make sure you run exactly the same software in the system.”
Demystifying Nix
This might sound like magic, but it’s grounded in the principles of functional programming, where output is always a function of explicitly defined inputs. The genius of Nix lies in applying these principles to package management and build systems, treating builds as isolated, reproducible processes.
It’s worth briefly unpacking how this works.
The basic building block of Nix is the derivation, a declarative definition that explicitly specifies all the inputs and build instructions required to reproduce a software artifact. In other words, the Nix derivation serves as both a high-level abstraction and a fully deterministic build plan, complete with source code, dependencies, environment variables, and build scripts.
When you run nix build
against this plan, the derivation is always deterministic: identical inputs yield identical builds.
Dependencies are declared and tracked explicitly, not left to luck.
“Always” is not a throwaway word, either, because a Nix derivation guarantees reproducibility over time. By strictly defining which versions of software are used in a runtime environment, Nix gives Antithesis a way to reproduce that environment exactly at any point in the future. “We can store the environment runtimes we build in Nix’s binary cache, which is convenient,” Murrays says.
Taking back local dev
At this point Antithesis has been building and running on Nix for about three years. As Murray indicated, it now uses Nix throughout its SLDC, from local development to CI and QA testing, staging, and production.
For the most part, he says, everything just worked out. The smoothness of the switchover stems in no small part from the fact that Scherer, Murray, and other Antithesis-ians deeply grok Nix. It also helps that Nix itself isn’t unpredictable or fussy: once you get a build working, it stays working.
Murray uses the example of a Rust build environment Antithesis had previously implemented as a dev container. Aside from
the frustrations involved in using a container-based pattern for local development, Antithesis’ developers also struggled
with error-prone Rust cargo
builds inside their dev containers. With Nix, he says, “you can create a reproducible Rust
build in just 10 lines of Nix code. Your cargo
builds don’t just work ‘half’ the time, or ‘most’ of the time. They
work all of the time. One-hundred percent.”
Antithesis developed a custom CLI tool, Command Sets, to make it easier to run bash scripts without requiring an interactive Nix shell. Command Sets evaluates Nix expressions that define attribute sets, so that when a bash script runs, Command Sets resolves and pulls required dependencies on the fly. This lets developers easily create and run dependency-aware tasks that run the same way anywhere, on any platform.
Transforming deployment
Antithesis uses Nix throughout its deployment process, too. Previously, the NixOS AMIs running in its hypervisor pulled
code from repositories and built binaries locally using nix build
. While this ensured reproducible builds for individual
services, it didn’t address configuration conflicts or ensure compatibility between services— for example, when
Antithesis’ hypervisor and fuzzer service had been built from different branches
in the same repo.
Now, Antithesis defines each deployment in a single JSON file, specifying all required services and binaries alongside
their Nix store paths. And instead of building artifacts locally, its NixOS AMIs fetch pre-built binaries directly from
Antithesis’ binary cache, via nix-store –realize
. This not only guarantees that each AMI runs exactly the same version
of the specified binaries, it also lets Antithesis build and maintain lightweight AMIs, as required dependencies are
fetched at runtime using the JSON deployment file.
“Instead of building stuff locally on the machine, which takes forever and eats up resources, we just pull pre-built binaries at runtime, straight from the binary cache. It’s so much faster, and we don’t have to bake everything into the AMI anymore,” Murray explains. “Plus, our AMIs are super minimal—basically just enough to bootstrap—so it’s much cleaner, and everything works the same every time.”
Go big or go small
If there’s one thing Nix is known for, it’s for its steep learning curve.
There is some truth to this, Murray agrees. After all, Nix comes out of functional programming, so people who aren’t comfortable with that paradigm may struggle to adapt to Nix’s concepts, methods, and language. “It's a hard paradigm to wrap your head around, and [Nix] error messages aren’t an enormous amount of help,” he concedes. “So it did seem like anytime someone had an issue, what happened was they would reach out to me, and I basically would have to pair with them to solve the issue.”
But if you’re struggling with issues like reproducibility and immutability in your SDLC—or if you require determinism in your build artifacts—Nix is a “beautiful” solution, Murray asserts.
“You get a lot of things for free even if you don’t fully buy in. You can just use Nix for your reproducible developer environments. And that’s a great 75% solution,” he says. “Plus, once you’ve built [a package], you can cache it in the binary cache, so everyone can reuse it without rebuilding everything from scratch.”
And for a company the size of Antithesis, with fewer than 100 employees, custom NixOS modules provide a brilliant solution for managing software change while maintaining traceability, reducing risk, and enforcing compliance. “Our ‘IT department’ is me and two other people who know NixOS. But what's really cool is that whenever we have security patches or need to enforce a configuration change—you know, if we need to make sure everyone's automatically screen locks for SOC2 Compliance (which Antithesis just achieved)—we can do that using NixOS, with our custom NixOS module,” he observes.