Container vs microVM vs gVisor: Choosing Agent Isolation
Every team building an AI agent that runs code eventually has to answer one question: what is the boundary between the code the model wrote and everything else you own. It gets answered badly, usually because it gets answered by whichever isolation technology the team already had running.
I picked one and lived with it. I'm Ajay Kumar, an infrastructure engineer with 14 years in this, and I built PandaStack solo over about six months — an Apache-2.0 Firecracker microVM cloud where every sandbox create is a snapshot restore. Around 400 Go files, 300+ organizations signed up. So I have a bias, and I'll declare it: I chose KVM microVMs. This post is my attempt to say honestly when that's the wrong choice, because for a lot of workloads it is.
The five models, and what the boundary actually is
Containers / namespaces. One shared Linux kernel. Isolation is a set of kernel features — namespaces, cgroups, seccomp, capabilities — filtering how your process sees that kernel. The guest talks directly to the same kernel your other tenants do.
V8 isolates. No process, no filesystem, no kernel boundary at all. A JavaScript heap and context inside a running host process. The boundary is V8's own sandbox plus whatever the embedder exposes.
gVisor. A userspace kernel. The Sentry implements a large subset of Linux in Go and services guest syscalls itself. The host kernel is still shared, but the guest reaches it through a deliberately narrow, seccomp-restricted set of calls rather than the full surface.
Kata Containers. An OCI-compatible runtime that puts each pod or container inside a lightweight VM. You keep your container tooling and gain a hypervisor boundary underneath it.
Firecracker / KVM microVMs. A guest kernel of your own, its own page tables, on KVM. The VMM is minimal by design — no BIOS, no PCI, a handful of virtio devices. The tenant's kernel is not your kernel.
Those are genuinely different boundaries, not points on a line. The axes below decide between them.
Axis 1: syscall compatibility
This eliminates options faster than anything else, so run it first.
If your workload is "the model writes a small pure-JS transform and returns a value," isolates are extraordinary and everything else is waste. If your workload is pip install pandas && python script.py, isolates are not a candidate at all — no fork, no exec, no ELF loader, no filesystem. That is not a criticism of isolates. It is the design.
gVisor sits in the interesting middle. Most things work. The failure mode is specific and annoying: something in the long tail — an unimplemented syscall, a /proc or /sys file a tool reads, an ioctl a runtime wants, a niche filesystem or networking feature — breaks a workload that ran fine on your laptop, and you find out from a user rather than from a test. Whether that matters depends entirely on how bounded your workload set is. If you control the images, it's fine. If your product promise is "run whatever the agent wrote," you are signing up to debug other people's package installs forever.
Containers, Kata and microVMs are all "real Linux" for compatibility purposes. With a microVM you also choose the kernel, which matters more than I expected. I run a 5.10 guest kernel, and it has been the binding constraint on two features I wanted to ship — one of them needed a netfilter module that kernel build does not carry. Owning the kernel means owning kernel decisions.
Axis 2: blast radius when the guest is hostile
Assume the guest is root, because in most agent sandboxes it is. Now ask what one bug buys the attacker.
| Model | Escape requires | Reaches | Cross-tenant exposure |
|---|---|---|---|
| Containers | One Linux kernel LPE | The host | Every tenant on the host |
| V8 isolates | A V8 sandbox bug | The host process | Every tenant in that process |
| gVisor | A Sentry bug and a host kernel bug reachable through its narrowed syscall set | The host | Every tenant on the host |
| Kata | A guest kernel bug and a VMM/KVM escape | The host | Every tenant on the host |
| microVMs | A guest kernel bug and a VMM/KVM escape | The host | Every tenant on the host |
The honest reading: nothing here gives you a hardware boundary. Every model short of full physical separation ends at "host compromise" if enough things break. What changes is how many independent bugs you need and how much surface is presented.
Containers ask for one bug in a very large, very actively researched surface. gVisor is a real improvement over that — not marketing — because the attacker's reachable host surface shrinks dramatically. microVMs ask for a chain: break the guest kernel, then break a VMM that is deliberately small.
My reason for choosing microVMs was not a probability estimate. It was that I could not enumerate my workloads. I don't know what code my users will run, so I wanted the boundary whose safety argument doesn't depend on knowing. That reasoning, and what the boundary does and does not cover, is written up in the isolation model docs.
Axis 3: boot latency, and the units problem
The June 2026 analyst report comparing 19 agent-sandbox platforms lists creation times in its matrix spanning roughly 0.79ms to 2.7s. Those are other people's reported numbers, not mine, and I want to flag something about reading them: 0.79ms and 179ms are not the same unit of work. One instantiates a JavaScript context inside a process that is already running. The other produces a Linux system with its own kernel, its own page tables, a network interface and a listening service. Comparing them as though they measure the same thing is the single most common error in this category.
Here is what I measure on my own fleet, clock stopped when the guest actually answers a TCP probe, not when the API returns:
| Operation | p50 | p99 |
|---|---|---|
| Create (snapshot restore) | 179ms | 203ms |
| Snapshot load alone | ~80ms | — |
| Cold boot (first spawn of a template only) | ~3s | — |
| Same-host copy-on-write fork | 400ms | 750ms |
| Cross-host fork | 1.2s | 3.5s |
| Wake from hibernate | ~1.2s | — |
The 179ms is not Firecracker booting a kernel in 179ms. It's a restore: PUT /snapshot/load, then PATCH /vm {"state":"Resumed"}. The kernel booted once, months ago, at bake time. I broke that path down stage by stage in the anatomy of a 179ms boot.
The practical point: with checkpoint/restore, microVM start latency stops being a differentiator against containers for most agent workloads. It becomes a hundreds-of-milliseconds problem instead of a seconds problem, usually below the latency of the LLM call that triggered it. The same report predicts checkpoint/restore becomes table stakes by 2027, and I think that's right — it's what makes the heavier isolation models viable at all.
Axis 4: memory per instance — the real cost of my choice
This is where microVMs lose, and it deserves more honesty than it usually gets.
Firecracker's documented design target is under 5 MiB of VMM overhead per microVM. That number is true and it is also misleading, because it isn't your cost. Your cost is the guest RAM, and a guest kernel plus userland has a floor a container process does not.
My baked template sizes are the actual number: 2 GiB for the code-interpreter and agent templates, 4 GiB for the base apps template and the browser template, 1 GiB for Postgres. A container running the same Python script might hold 80 MiB resident. That's the trade, stated plainly.
Two things soften it, neither of which eliminates it. First, RAM is committed, not touched: I demand-page guest memory from object storage over userfaultfd — 4 MiB range GETs, a header recording which chunks are non-zero so absent ones are zero-filled without a fetch, a prefetch trace replayed in the background, and a per-host shared chunk cache so the first restore pays network latency and every later one is local-disk fast. The snapshot-restore internals cover that path. Second, hibernate and wake let idle instances stop costing anything — about 50s to hibernate a 4 GiB app VM, about 1.2s to bring it back.
But one constraint I can't engineer away shaped the whole platform: Firecracker cannot change vCPU or RAM at snapshot restore. Guest size is frozen at bake time, so a customer's app gets the template's memory and my per-instance cost is set months before the instance exists. Density is what you sell away when you buy a kernel boundary.
The theoretical ceiling on a host is 16,384 sandboxes, from the /30 subnet space in my networking pool. I'll never reach it. The real ceiling is host RAM, roughly two orders of magnitude lower.
Axis 5: operational complexity
Containers have an ecosystem. Firecracker has a VMM.
I had to build the rest: a pre-allocated pool of network namespaces with veth pairs and tap devices, because creating one cold costs about 100ms and I wanted that off the create path — I keep a warm depth of 4 per template and fall back to an on-demand ~500ms build rather than returning a 503. A snapshot store. A memory-streaming layer. A scheduler with leases and heartbeats. Copy-on-write rootfs on XFS reflinks. That is what the 400 Go files are, and most of them are not the interesting part of the product. The networking internals describe that pool.
There are second-order costs too. Nested virtualization means my agent nodes cannot live-migrate, so every host maintenance event is my problem rather than the cloud's. Re-baking a template invalidates existing snapshots. Hugepage-backed snapshots can only be restored through the userfaultfd path — a coupling I did not anticipate when I enabled the flag.
Kata deserves credit here specifically because it attacks this axis. If you already run Kubernetes, it gets you a hypervisor boundary without rebuilding the surrounding platform. I didn't take it because I wanted control over the restore path — snapshot, fork, hibernate — and that meant driving the VMM directly. If I had needed OCI compatibility more than I needed fork, I'd have chosen differently.
The part these guides skip: isolation is not an abuse control
I want to be blunt about this, because I got it wrong.
A free-tier account 27 minutes old mined cryptocurrency on my fleet. My isolation boundary held perfectly the entire time. It changed nothing — the abuse was entirely inside what the sandbox is supposed to permit. Isolation answers "can this escape," and it answers it well. It does not answer "should this be running," "who pays," or "is this the same person who signed up eleven times yesterday."
The controls that actually worked were egress policy (blocking well-known Stratum ports at the top of the chain in each namespace, which is a denylist and was evaded twice), quota admission that doesn't have off-by-one bugs, and attribution — being able to map a running VM back to an organization fast enough to act. Building those took comparable effort to the isolation itself, and none of it appeared in any comparison matrix I read beforehand. The full incident and the four layers I built after it are in isolation is not an abuse control.
Budget for it. Whichever model you pick, you are not done when the boundary works.
Where the category actually landed
That same report covers 19 platforms, and its isolation column spans Firecracker, gVisor, Kata, Docker, V8 isolates and plain namespaces. Everything in this paragraph is industry reporting rather than my own measurement. As reported: every hyperscaler entered in 2026 — Vercel Sandbox reached GA, alongside Cloudflare's Sandbox SDK, AWS AgentCore, Google Agent Sandbox and NVIDIA OpenShell. Modal is reported to have raised $355M at a $4.65B valuation, and Daytona a $24M Series A. E2B publishes a figure of over a billion sandboxes started, with pause/resume that preserves memory state. Two long-tail projects are described as dormant.
Read that as a signal about which axes the market decided were load-bearing. Persistence has converged — everyone has it. Checkpoint/restore is heading the same way. Nobody converged on isolation, which tells you it's genuinely workload-dependent rather than a solved question with one right answer.
The decision table
| Your workload | Model | Why | What it costs you |
|---|---|---|---|
| Short pure-JS/TS transforms, per-request, no fs or subprocesses | V8 isolates | Sub-millisecond start, near-zero per-instance memory | No processes, no pip install, a V8 bug is cross-tenant in-process |
| Your own first-party code, trusted images, CI-style steps | Containers | Best density and tooling; you control the images | One kernel bug from host compromise |
| Unreviewed but non-hostile code, arbitrary binaries, density matters | gVisor | Much narrower host surface, container-shaped ops | Long-tail syscall gaps you debug from user reports |
| You already run Kubernetes and OCI images are non-negotiable | Kata | Hypervisor boundary without rebuilding the platform | More moving parts; less control of the restore path |
| Hostile-by-assumption multi-tenant, arbitrary binaries, needs fork/pause/persistence | Firecracker / KVM | Own kernel per tenant; snapshot, fork and hibernate are yours | 1–4 GiB per instance; you build networking, scheduling, storage |
| GPU workloads, kernel modules, multi-hour jobs | Dedicated VM or bare metal per tenant | Nothing else gives clean device passthrough and long-lived state | Density and start latency both gone |
What I'd choose again
Firecracker, for what I'm building, without much hesitation — because I could not enumerate my workloads, and the fork and hibernate primitives turned out to be product features rather than infrastructure details.
But if I were building an agent that only ran short JS transforms, the isolates answer is obviously right and I'd be embarrassed to run a hypervisor for it. If I were running unreviewed-but-cooperative Python at high density, I think gVisor is the better engineering trade than I gave it credit for when I started, and I say that with six months of sunk cost pointing the other way.
Pick on syscall compatibility first — it eliminates fastest. Then blast radius, honestly, including the part where none of these end at a hardware boundary. Then measure boot latency in units that match. Then go build the abuse controls, because the boundary is the easy half.
Ajay Kumar is an infrastructure engineer with 14 years of experience and the creator of PandaStack, Riff and PandaFlow. He is available for infrastructure consulting — microVM platforms, Firecracker, multi-tenant isolation — at linkedin.com/in/ajay-kumar-devops.
I'm Ajay Kumar — I build and operate PandaStack, an open-source Firecracker microVM cloud for AI agents. Everything above comes from running it in production.
Need this kind of infrastructure work? See what I do or email hello@ajayk.sh.
Keep reading
It's 2 AM. Do You Know What Your AI Agent Is Doing?
I run the microVMs that other people's AI agents execute in. At 2 AM almost all of them are idle — and the things burning money are the robots watching them.
11 minSep 2, 2026What Scale to Zero Cost Me to Build: 50s Sleeps, 14s Wakes
Hibernate took 50 seconds. Wake took 14, not the 1.3 I had reported. The measured price of building scale to zero on a Firecracker fleet, and every fix.
9 minSep 2, 2026Sandbox Creation Time Benchmarks Measure Different Things
Every AI sandbox matrix has a creation time column spanning 0.79ms to 2.7s. That spread is definitional, not performance. Here's how to make it comparable.
10 min