all posts

Firecracker vs the Nix build sandbox: different jobs

Ajay Kumar··8 min read

"Firecracker vs the Nix sandbox" is one of those comparisons that only makes sense once you notice the two things aren't competing for the same job. Firecracker is a Virtual Machine Monitor — it boots a guest with its own kernel inside a hardware-virtualized microVM, and its whole reason to exist is running code you don't trust without letting it touch the host. The Nix build sandbox is the confinement that `nix-build` wraps around a derivation while it's being realized, and its whole reason to exist is making the build pure and reproducible. One is a hardware isolation boundary; the other is a hermeticity mechanism. They answer different questions, and the interesting part is that for some workloads you want both at once.

Scope caveat first: Nix, its sandbox, and the surrounding tooling evolve across versions, and the exact defaults and flags differ between Nix and daemon configurations. Everything below describes the architectural intent of the Nix build sandbox, not a version-pinned guarantee. Verify the current behavior of sandboxing, restricted evaluation, and fixed-output derivations against the Nix manual before you rely on any specific detail.

What the Nix build sandbox actually does

When Nix realizes a derivation with sandboxing on, it doesn't just run your build script in your normal environment. It sets up a restricted one using Linux namespaces — a fresh mount namespace with only the derivation's declared inputs bind-mounted in, a private network namespace with no route to the outside world, and separate PID/user namespaces — plus a chroot-like private root. The build sees `/nix/store` paths it declared as dependencies and essentially nothing else: no `$HOME`, no ambient `/usr/local`, no live network. The goal is to make it impossible for a build to quietly depend on something that wasn't declared, so the same derivation produces the same output anywhere.

That confinement is what makes Nix builds reproducible rather than 'works on my machine.' A build can't reach out and `curl` a URL mid-build, can't read a file that happens to exist on the builder, can't inherit an environment variable that changes the result. Everything the build is allowed to touch is declared up front. Reproducibility falls out of removing the hidden inputs.

# nix.conf -- the sandbox that enforces hermeticity for every build.
sandbox = true            # namespaces + private root; verify current default in the Nix manual

# Because the build sandbox has NO network, anything that needs to fetch must be a
# fixed-output derivation: you declare the hash of the result up front, and Nix
# opens a controlled network hole ONLY because the output is pinned to that hash.
#   fetchurl { url = "https://example.com/foo.tar.gz"; sha256 = "..."; }
# The hash -- not the sandbox -- is what keeps the fetch honest and reproducible.

The network hole for fixed-output derivations is the tell for what the sandbox is really for. Nix lets a fetch reach the network, but only when you've committed to the exact hash of what comes back. If the bytes don't match the declared hash, the build fails. The sandbox is enforcing purity — inputs are either declared-and-local or declared-and-hash-pinned — not defending against a malicious builder. Restricted evaluation (`restrict-eval`) plays the same role at eval time, limiting which paths and URLs pure evaluation can even reference.

The Nix sandbox threat model: purity, not defense

Here's the part that surprises people: the Nix build sandbox shares the host kernel. It's built from namespaces and a chroot-style root, exactly like a container — and like a container, a build process running inside it makes syscalls directly to your host kernel. The Nix manual is explicit that the sandbox exists to improve purity and reproducibility, and is not designed to defend against a genuinely malicious build that's trying to escape or attack the host. Its threat model is 'an honest build that accidentally reaches for an undeclared input,' not 'an attacker who wrote the derivation to break out.'

The Nix sandbox keeps your build honest, not your attacker out. It's a hermeticity fence for the build's own inputs — a shared-kernel confinement whose job is reproducibility. If the derivation itself is hostile, a shared-kernel escape is exactly as reachable as it would be from any container. Verify the specifics against the Nix manual, but don't mistake 'sandbox' here for 'security boundary against untrusted builders.'

This isn't a knock on Nix — it's doing precisely what it set out to do, extremely well. The confusion is purely linguistic: 'sandbox' in the Nix world means 'a hermetic environment for a reproducible build,' and 'sandbox' in the untrusted-code world means 'an isolation boundary strong enough to run an attacker's code.' Same word, different guarantee. If you're evaluating Nix's sandbox as an answer to 'can I safely build a repo I don't trust,' you're asking it a question it never promised to answer.

Firecracker's threat model: untrusted code isolation

Firecracker is built for the exact question the Nix sandbox doesn't answer. It boots a microVM with its own guest kernel, its own memory, and a tiny set of emulated virtio devices, confined by KVM hardware virtualization. Code inside the guest cannot make a syscall to your host kernel, because it doesn't share your host kernel — it has its own. The only path from guest to host is the narrow device model and the KVM boundary, a vastly smaller and more heavily audited surface than the full Linux syscall table a container (or a Nix build sandbox) shares with its host. This is the model AWS Lambda and Fargate use to run untrusted code from many tenants on shared fleets.

But Firecracker says nothing about reproducibility. A microVM will faithfully run whatever you put in it, including a build that reaches out to the network, reads whatever's on its disk, and produces a different result every time. Firecracker gives you a hard wall around the build; it does nothing to make the build inside that wall pure. That's not a gap — it's just a different axis. Isolation strength and build reproducibility are orthogonal properties, and each tool nails exactly one of them.

Side by side

  • What it isolates — Nix sandbox: the build's inputs (declared deps only, no network, no ambient environment). Firecracker: the entire guest, via a separate kernel under hardware virtualization.
  • Threat model — Nix sandbox: an honest build accidentally depending on undeclared inputs; purity, not defense. Firecracker: genuinely untrusted or malicious code trying to escape or attack the host.
  • Kernel — Nix sandbox: shares the host kernel (namespaces + chroot-like root). Firecracker: its own guest kernel; escape means beating the hypervisor.
  • Reproducibility — Nix sandbox: the whole point; same derivation, same output anywhere. Firecracker: says nothing about it; runs whatever you give it, reproducible or not.
  • Untrusted-code safety — Nix sandbox: not designed as a boundary against a hostile builder (verify against the Nix manual). Firecracker: designed exactly for untrusted, multi-tenant code.
  • Composability — they stack cleanly: run Nix inside a Firecracker microVM to get hermetic, reproducible builds AND a hardware isolation boundary at the same time.

Read the list and the two stop looking like rivals. Nix wins the reproducibility axis outright; Firecracker wins the untrusted-isolation axis outright; and the last row is the punchline — because they isolate different things, they compose instead of compete.

The pattern: run Nix inside a Firecracker microVM

The moment you want reproducible builds of code you don't trust — a CI system building arbitrary contributor PRs, a hosted service realizing user-submitted derivations, a build farm for untrusted repos — you need both properties at once. You want Nix's hermeticity so the build is pure and cacheable, and you want a real isolation boundary because the derivation itself might be hostile. The clean answer is to nest them: run `nix-build` inside a Firecracker microVM. Nix's sandbox keeps the build honest; the microVM keeps a malicious build off the host. Each tool does the one job it's good at.

With PandaStack you don't drive Firecracker's socket by hand — you ask for a sandbox and get a hardware-isolated microVM back, then run whatever build you like inside it:

from pandastack import Sandbox

# The microVM is the isolation boundary (its own guest kernel under KVM).
# Nix, running INSIDE it, is the hermeticity/reproducibility boundary.
# Untrusted derivation + hardware isolation + pure build = both properties at once.
with Sandbox.create(template="base", ttl_seconds=600) as sbx:
    # A build that turns out to be hostile escapes into a disposable guest
    # kernel, not your host. A build that's merely impure is still caught
    # by Nix's own sandbox inside the VM.
    out = sbx.exec(
        "nix-build ./untrusted-repo --option sandbox true",
        timeout_seconds=600,
    )
    print(out.stdout)
# VM (and anything the build touched) gone here.

Because every PandaStack sandbox is its own Firecracker microVM created via snapshot-restore (~49ms for the restore step, 179ms p50 end-to-end), wrapping each untrusted build in a fresh hardware-isolated machine costs you almost nothing in latency — and you can fan many of them out in parallel, one microVM per build, each disposable.

Which to reach for

If your builds are your own code and you care about reproducibility, caching, and 'works everywhere,' Nix's sandbox is the right and sufficient tool — a microVM around trusted builds would be pure overhead. If you're running code you don't trust and reproducibility isn't the concern, Firecracker is the boundary you want, and Nix would be answering a question you're not asking. And if you're building untrusted code and want the result to be reproducible — the CI-and-build-farm case — reach for both: Nix inside a microVM. The honest one-liner for 'Firecracker vs the Nix sandbox' is that it's rarely a versus. Nix gives you reproducibility, Firecracker gives you isolation, and the interesting workloads want the two stacked. Verify the current specifics of Nix's sandbox against its manual before you lean on any single detail.

Frequently asked questions

Is the Nix sandbox a security boundary?

No — not in the sense of defending against untrusted or malicious code. The Nix build sandbox exists for reproducibility and hermeticity: it uses Linux namespaces and a chroot-like root to cut a build off from the network and from any input it didn't declare, so the same derivation produces the same output anywhere. But it shares the host kernel, exactly like a container, and the Nix manual is explicit that it's about purity, not defending against a builder that's actively trying to escape or attack the host. Its threat model is 'an honest build accidentally reaching for an undeclared input,' not 'an attacker who wrote the derivation to break out.' For running code you don't trust, you need a real isolation boundary like a Firecracker microVM. Verify the current details against the Nix manual.

Are Firecracker and the Nix sandbox competitors?

Not really — they solve different problems and are often complementary. The Nix sandbox is a hermeticity mechanism that makes builds pure and reproducible; Firecracker is a hardware isolation boundary that runs untrusted code without letting it touch the host. Nix says nothing about isolation strength against a hostile builder, and Firecracker says nothing about build reproducibility. Because they isolate different things — Nix isolates the build's inputs, Firecracker isolates the whole guest — they compose cleanly rather than compete.

How do I run untrusted reproducible builds safely?

Use both. Run Nix inside a Firecracker microVM: Nix's sandbox keeps the build hermetic and reproducible (declared inputs only, no ambient network or environment), and the microVM's separate guest kernel under KVM keeps a genuinely malicious derivation off your host. This is the pattern for CI that builds arbitrary contributor PRs, hosted services that realize user-submitted derivations, and build farms for untrusted repos. With PandaStack, each build runs in its own disposable microVM created in about 179ms (p50) via snapshot-restore, so the isolation costs almost no latency and you can fan builds out in parallel.

What are fixed-output derivations and why does the Nix sandbox need them?

The Nix build sandbox has no network access, which is deliberate — network access would let a build depend on something undeclared and break reproducibility. A fixed-output derivation is the controlled exception: you declare the exact hash of the result up front, and Nix opens a network hole only because the output is pinned to that hash. If the fetched bytes don't match, the build fails. So the honesty comes from the hash, not the sandbox — the sandbox enforces that everything a build touches is either declared-and-local or declared-and-hash-pinned. Verify the current behavior against the Nix manual.

Does Firecracker make my builds reproducible?

No. Firecracker is an isolation boundary, not a reproducibility mechanism. A microVM will faithfully run whatever you put in it, including a build that fetches from the network, reads whatever is on its disk, and produces a different result every time. It gives you a hard wall around the build but does nothing to make the build pure. Reproducibility is a separate axis — that's what tools like Nix's sandbox provide. If you want both, run Nix inside the microVM.

Run code in a microVM in one API call.

49ms p50 cold start. Fork, snapshot, and scale to zero.

Start free
Written by Ajay Kumar, Founder, PandaStack.