Firecracker microVMs, or how to run untrusted agents without betting the host
Everyone is running untrusted AI agents now. I benchmarked Firecracker to find out whether you really can get VM-grade isolation at container speed. Average boot: 216.7ms.
Originally a shorter post on LinkedIn, expanded here with the full methodology.
Companion repositoryEveryone is running untrusted AI agents now. Code they generate, commands they execute, tools they call. So how do you orchestrate thousands of them locally without one rogue agent owning your whole machine?
It comes down to a question that predates the current moment by a decade: how do you get container-like speed and VM-like isolation at the same time?
The tradeoff everyone accepts
That has always been a tradeoff you were told to pick a side of.
Virtual machines are secure. Each one gets its own kernel, its own virtual hardware, a boundary enforced below the operating system. They also take ten to sixty seconds to boot, which rules them out for anything you want to create and destroy per task.
Containers boot in tens of milliseconds. They also share the host kernel. One kernel escape and you are on the host, and possibly into whatever else is running next door.
For a platform running arbitrary code, or arbitrary agents, shared-kernel isolation is not something you want to bet a business on.
The question Lambda kept raising
This is the same thing that always fascinated me about AWS Lambda. Functions spin up in milliseconds, run, and get torn down. Thousands of times a second. Across untrusted tenants, on shared hardware.
The obvious guess is Docker. It is also the wrong guess, for exactly the reason above. AWS is running your code next to a stranger's code, and a shared kernel means a single privilege escalation bug is a multi-tenant incident.
The real answer is Firecracker, the open source virtual machine monitor AWS built on KVM and released in 2018.
Each workload runs in its own lightweight microVM with its own guest kernel, hardware-isolated through KVM. That is a real security boundary, not a namespace and a set of cgroups. But it is stripped down so aggressively that it boots almost like a container.
It is not a Lambda-only trick either:
- Lambda isolates each tenant in its own microVM, then reuses warm ones across invocations
- Fargate runs containers on the same foundation
- Kata Containers gives you an OCI-compatible runtime backed by microVMs
- Platforms sandboxing AI-generated code increasingly use one disposable microVM per execution, destroyed when it finishes
That last one is why this matters right now.
I did not want to trust the marketing
Claimed boot times in a README are not the same as boot times on your hardware. So I measured it.
Setup. Firecracker v1.7.0, a minimal vmlinux kernel, 128 MiB of memory, 1
vCPU, KVM on bare metal. Ten iterations, measuring from process start to the
guest reporting system-ready.
# one iteration, timed from launch to the guest's ready marker
start=$(date +%s%N)
firecracker --api-sock "$SOCK" --config-file vm-config.json &
wait_for_guest_ready "$SOCK"
end=$(date +%s%N)
echo "$(( (end - start) / 1000000 ))ms"Results.
| Metric | Value |
|---|---|
| Average boot | 216.7ms |
| Median boot | 214ms |
| Range | 213ms to 225ms |
| Standard deviation | 4.1ms |
| Memory overhead | roughly 5 to 10 MB per microVM |
Two things stand out. The first is the absolute number: a real VM, with its own kernel, in about a fifth of a second. The second is the standard deviation. Under 2% variation means cold starts are predictable, which matters more than raw speed when you are deciding whether to keep a warm pool at all.
At 5 to 10 MB of overhead each, a single host packs thousands of them.
Small sample, single host, so treat this as directional rather than a datacenter SLA. Full methodology is in the repo.
Where that lands
| Isolation approach | Startup | Boundary |
|---|---|---|
| Traditional VM | 10 to 60s | Hardware, separate kernel |
| Firecracker microVM | ~217ms | Hardware, separate kernel |
| Warm Docker container | tens of ms | Shared kernel, namespaces |
| Bare process | under 10ms | Essentially none |
Firecracker sits in the gap that was supposed to be empty: VM-grade security at close to container speed.
The trick is what they threw away
The interesting part is not what Firecracker added. It is what it refuses to emulate.
No BIOS. No PCI bus. No USB, no video, no legacy device emulation, no boot loader. What is left is a minimal device model written in Rust: virtio block, virtio net, a serial console, a one-button keyboard controller for reset.
The guest kernel comes up with almost nothing to discover, because there is almost nothing there.
That is the elegant part. What makes it fast is exactly what makes it safe. Less to initialize is also less to attack. The attack surface shrank as a side effect of the thing that shrank the boot time.
It is worth noticing how rare that is. Most performance work trades away safety. This traded away compatibility instead, and compatibility was the thing nobody needed for a function that runs for 200ms and dies.
So when do you actually reach for it
Use it when you are executing code you did not write and cannot review, and the blast radius of a kernel escape is unacceptable. Agent-generated code, untrusted user submissions, CI for forked pull requests, multi-tenant execution of any kind.
Do not reach for it when you control everything running on the box. You will pay the operational cost of managing kernels, root filesystems, and a device model, and buy a boundary you did not need. A container is fine, and honestly a process is often fine.
The reason it belongs in your head right now is that the AI tooling shift moved a lot of workloads from the first category into the second without anyone deciding to. Agents that write and execute code are untrusted execution, even when you trust the person who started them.
If you are orchestrating fleets of agents and have not thought about blast radius, this is the primitive worth knowing.