all posts

Firecracker vs Cloudflare Workers: which isolation model?

Ajay Kumar··9 min read

Cloudflare Workers and Firecracker microVMs both answer the question "how do I run untrusted code without it taking down my host or my neighbors?" — but they answer it at opposite ends of the stack. A Worker runs your code as a V8 isolate: many tenants share one operating system and even one process, separated by the boundary the JavaScript engine draws around each isolate. A Firecracker microVM gives each workload its own guest kernel behind hardware virtualization (KVM). The joke writes itself: a container is a polite suggestion to the kernel, and an isolate is a polite suggestion to V8. This post is the honest head-to-head — where each boundary actually sits, what each can and can't run, and the rule for choosing. I'm Ajay; I built PandaStack on the microVM side of this line, and I'll try to be fair to the other side.

The short version, so you can leave early if you need to: isolates win on cold start and density and are a beautiful fit for edge request handlers, but the boundary is a language-runtime sandbox and you're confined to the Workers runtime — no arbitrary syscalls, no native binaries, no full Linux. MicroVMs cost a few megabytes and a few milliseconds more, but they run anything Linux runs behind a hardware boundary. If your workload is glue code at the edge, reach for the isolate. If it's arbitrary, model-generated, native-dependency code, you're on the microVM rung. Everything below is the reasoning — and please verify any Cloudflare specifics against their own docs, which move faster than any blog post.

The core difference: where the boundary is drawn

Start with the boundary, because everything else follows from it. A V8 isolate is a lightweight sandbox inside a single V8 runtime — its own heap and its own globals, isolated from other isolates by the engine. Cloudflare's Workers runtime (workerd) packs many tenants' isolates into shared processes on shared machines. There is no per-tenant OS and often no per-tenant process: the thing keeping tenant A's code away from tenant B's memory is the correctness of the V8 sandbox plus the runtime's own hardening. That's what makes cold start effectively instant — there's no kernel to boot and no VM to spin up, just a new isolate in an already-warm engine.

A Firecracker microVM boots its own guest kernel and is confined by hardware virtualization through KVM. The guest reaches the outside world only through a tiny set of emulated virtio devices; the boundary is enforced by the CPU's virtualization extensions, not by a language runtime. Firecracker is the same VMM AWS Lambda and Fargate use, and it treats every guest vCPU thread as hostile from the first instruction. An exploit would have to escape the hypervisor itself — a much smaller, more heavily audited surface than "be a bug in the V8 process everyone shares."

The one-sentence version: an isolate isolates one program's heap behind the JavaScript engine's sandbox, inside a process shared with other tenants; a microVM isolates a whole guest OS behind a hardware boundary, in a VM shared with no one. Don't reason about them on a single "more secure" dial — they defend different things at different layers.

What each can actually run

This is where the choice usually gets made in practice, because it's close to binary. A Worker runs JavaScript, TypeScript, and WebAssembly against the Workers runtime's API surface — a curated set of web-standard APIs (fetch, streams, crypto, KV, R2, D1, Durable Objects, and so on). It is deliberately not a general-purpose Linux box. You don't get arbitrary syscalls, you can't fork/exec a subprocess, you can't shell out, you can't run a native ELF binary like ffmpeg or psql, and you can't pip install a package with a C extension and have it just work. Those aren't bugs — they're the direct consequence of not having a per-tenant kernel to make syscalls against. The Workers platform has grown genuine capabilities over time (Wasm, more Node.js compatibility, container/sandbox products alongside the isolate runtime), so check the current docs rather than trusting stale lore — but the isolate itself remains a language-runtime sandbox, not a machine.

A Firecracker microVM runs anything Linux runs, because there's a real Linux kernel underneath. Arbitrary binaries, any language, subprocesses, compilers, package managers, databases, a real filesystem — all of it, because the boundary is the hypervisor rather than a runtime API allowlist. That capability is the whole reason to pay the microVM's overhead: you stop asking "is this API available in the sandbox runtime?" and start running the actual program.

from pandastack import Sandbox

# Arbitrary Linux inside a Firecracker microVM: install a native-extension
# package and spawn a real subprocess. None of this runs in a V8 isolate.
with Sandbox.create(template="code-interpreter", ttl_seconds=300) as sbx:
    # pip install of a C-extension package at runtime
    print(sbx.exec("pip install --quiet lxml && python3 -c "
                   "'import lxml.etree as e; print(e.__version__)'",
                   timeout_seconds=120).stdout)

    # spawn a native binary as a subprocess
    print(sbx.exec("ffmpeg -version | head -n1", timeout_seconds=30).stdout)

    # it's a whole OS: real shell, real processes, real /proc
    print(sbx.exec("uname -a && ps -e --no-headers | wc -l",
                   timeout_seconds=10).stdout)

For contrast, the equivalent Worker is a request handler, not a machine. It's superb at exactly that — receive a request, do some compute, call bound services, return a response — and it does it with essentially no cold start:

// A Cloudflare Worker: a request handler running as a V8 isolate.
// No kernel, no subprocess, no native binaries — Workers-runtime APIs only.
export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    // You get fetch, Web Crypto, streams, KV/R2/D1 bindings, Wasm...
    const hash = await crypto.subtle.digest(
      "SHA-256",
      new TextEncoder().encode(url.pathname),
    );
    // ...but no `child_process`, no `exec`, no arbitrary syscalls.
    return new Response(new Uint8Array(hash), { status: 200 });
  },
};

Side by side

  • Isolation — Workers: V8 isolate boundary; many tenants share one process and OS. Firecracker: hardware-virtualized microVM with its own guest kernel; no shared tenant.
  • Cold start — Workers: extremely fast, effectively no boot (new isolate in a warm engine); verify current figures against Cloudflare's docs. Firecracker: ~3s on a true first cold boot, but ~179ms p50 (about 203ms p99) in practice because every create restores a baked snapshot rather than cold-booting.
  • What it can run — Workers: JavaScript, TypeScript, Wasm against a curated web-standard API surface; no arbitrary syscalls, subprocesses, or native binaries. Firecracker: anything Linux runs — any language, native binaries, subprocesses, compilers, package managers.
  • Density — Workers: very high; isolates are cheap enough to pack thousands per host. Firecracker: lower per host, bounded by guest memory/CPU (a few MB overhead per guest for the kernel + device model).
  • Boundary — Workers: the V8 sandbox plus runtime hardening, a language-runtime security boundary. Firecracker: KVM hardware virtualization, a VMM security boundary.
  • Best fit — Workers: edge request handlers, API glue, routing, lightweight transforms at global scale. Firecracker: arbitrary/untrusted/AI-generated code, full toolchains, native deps, code interpreters, per-tenant databases.

Blast radius: a shared process vs one VM

Here's the part that matters for untrusted multi-tenancy, and I'll be precise so this post doesn't earn a correction. Because Workers pack many tenants into a shared V8 process, the historic concern is the isolate escape: a bug in the shared V8 engine that lets one tenant's code read or influence a co-located tenant's memory. This is not hypothetical — V8 is one of the most attacked pieces of software on earth, and Cloudflare invests heavily in hardening the runtime, layering process- and account-level separation, and mitigating side channels precisely because the in-process boundary is the exposed one. The point isn't that Workers are insecure; they run an enormous amount of hostile internet traffic safely. The point is structural: when the boundary is a language runtime shared across tenants, a single engine bug has a wide blast radius by construction.

With Firecracker, the same class of bug is contained differently. Attacker and victim sit in separate VMs with separate kernels and separate address spaces; a guest-level exploit stays inside its own microVM, and to reach a neighbor it would have to escape the hypervisor — a far narrower target. The blast radius of arbitrary code is one disposable VM. That is the trade you're buying with the extra megabytes and milliseconds: not a magic guarantee, but a smaller, hardware-enforced boundary between mutually-untrusting tenants.

Neither model is "immune." Both are exposed to CPU microarchitectural and Spectre-class side channels, and even KVM has had real guest-to-host escape CVEs. The honest framing is that the hardware VM boundary is generally considered stronger for mutually-untrusting tenants because it puts attacker and victim in separate address spaces — stronger, not safe. Pick by threat model, not by a single security number.

Use Workers (isolates) when…

Reach for the isolate model when the shape of the problem matches its strengths:

  • The workload is a request handler or glue: routing, auth, header rewriting, A/B logic, edge caching, calling bound services, streaming responses.
  • The code is yours or your users' JavaScript/TypeScript/Wasm, and it lives happily within the Workers runtime's API surface.
  • You want global, low-latency deployment and near-zero cold start at very high density — thousands of tiny handlers, not a handful of heavy machines.
  • You don't need subprocesses, native binaries, a full POSIX environment, or the ability to install arbitrary dependencies at runtime.

If that's your situation, an isolate isn't a compromise — it's the better tool, and a microVM would be over-paying for a boundary and a capability set you don't need. The whole appeal of the edge is running that glue everywhere with no boot cost, and the isolate is what makes that economical.

Use a microVM (Firecracker) when…

Reach for a microVM the moment the workload needs the OS, the hardware boundary, or both:

  • You must run arbitrary, unmodified code: a real shell, pip install of anything, compilers, databases, multiple processes, package managers, an actual kernel.
  • You're running mutually-untrusting, multi-tenant, or AI-generated code and want a hardware isolation boundary rather than a shared in-process one.
  • You can't predict the dependencies in advance — the defining property of agent-generated code, where the model decides at runtime what to install and execute.
  • You need whole-machine snapshot and fork — freeze an environment mid-task and branch it — which is a VM-level capability an isolate doesn't offer.

This is exactly the AI-agent and general code-execution case. An agent's value is that it runs commands you didn't write and couldn't fully predict: it shells out, installs native-dependency packages, spawns subprocesses, edits files, hits the network. Every one of those is something a Worker either can't do or would need a different Cloudflare product for. The honest reason to pick the microVM here isn't that it's "more secure" in the abstract — it's that it's the only rung that can run the workload at all, and it happens to come with a hardware boundary.

Where PandaStack lands

PandaStack is built on the microVM rung because that's where arbitrary, untrusted, model-generated code belongs. Every sandbox is a Firecracker microVM with its own guest kernel (5.10, Ubuntu 24.04 guest), isolated by KVM hardware virtualization — not a shared-process isolate and not a shared-kernel container. That means pip install of anything, a real shell, subprocesses, compilers, and the full long tail of native-dependency Python all just work, because there's a real Linux kernel underneath rather than a runtime API allowlist you had to anticipate.

The classic objection to the VM rung is cold start, and that's the part the engineering answers. There's no warm pool of idle VMs; every create restores a baked snapshot on demand at 179ms p50 (about 203ms p99), the underlying snapshot restore is roughly 49ms, and only a true first cold boot pays the ~3s kernel-boot tax. A same-host fork — copy-on-write guest memory plus an XFS-reflink rootfs — lands in 400–750ms, so you can branch a running environment cheaply. That's still heavier than spinning up a V8 isolate, and it always will be, because you're getting an entire OS instead of a request handler. But it's light enough that the hardware boundary costs you almost nothing in practice. None of this makes the microVM the right answer for edge glue — if your workload fits a Worker, ship a Worker. It makes the microVM the right answer for the workload it's actually for: arbitrary code you didn't write and can't fully predict.

These aren't always either/or. A common shape is a Worker at the edge for routing and auth that hands the heavy, untrusted, or long-running work off to a microVM backend. Use the isolate for what isolates are great at, and the microVM for what only a real OS behind a hardware boundary can do.

Frequently asked questions

What's the difference between a V8 isolate and a Firecracker microVM?

A V8 isolate is a lightweight sandbox inside a single JavaScript engine — its own heap and globals — with many tenants sharing one process and operating system, isolated by the V8 sandbox plus runtime hardening. A Firecracker microVM gives each workload its own guest kernel behind hardware virtualization (KVM), sharing nothing with other tenants. The isolate's boundary is a language runtime; the microVM's boundary is the hypervisor. That's why isolates start faster and pack denser, while microVMs can run arbitrary Linux code behind a stronger, hardware-enforced boundary.

Can Cloudflare Workers run arbitrary code, native binaries, or subprocesses?

Not in the isolate runtime. A Worker runs JavaScript, TypeScript, and WebAssembly against the Workers platform's curated web-standard APIs. It has no per-tenant kernel, so there are no arbitrary syscalls, no fork/exec of subprocesses, no native ELF binaries like ffmpeg or psql, and an arbitrary pip install of a C-extension package won't run. For unconstrained native code you need a real OS, which is the microVM case. Cloudflare's platform is evolving, so verify current capabilities against their docs — but the isolate itself is a language-runtime sandbox, not a machine.

Which is more secure for running untrusted multi-tenant code?

Neither is universally 'more secure' — they draw the boundary at different layers. Workers pack many tenants into a shared V8 process, so a bug in the shared engine (an isolate escape) has a wide blast radius by construction, which is why Cloudflare hardens the runtime heavily. Firecracker puts each tenant in its own VM with a separate kernel and address space, so a guest exploit is contained to one microVM and would have to escape the hypervisor to reach a neighbor. Both are exposed to microarchitectural side channels, so frame the hardware boundary as stronger for mutually-untrusting tenants, never as immune.

How fast is a Firecracker microVM compared to a Worker's cold start?

A Worker isolate starts effectively instantly because there's no kernel to boot and no VM to spin up — just a new isolate in an already-warm engine (check Cloudflare's docs for current figures). Firecracker is heavier: a true first cold boot is around 3 seconds, but production platforms restore a baked snapshot instead of cold-booting. PandaStack's create is 179ms p50 (about 203ms p99) via snapshot-restore, with a same-host fork in 400–750ms. It's slower than an isolate and always will be, because you're getting a whole OS behind a hardware boundary rather than a request handler.

When should I choose Cloudflare Workers over a Firecracker-based sandbox?

Choose Workers when the workload is an edge request handler or glue — routing, auth, header rewriting, caching, calling bound services — written in JavaScript, TypeScript, or Wasm and living within the Workers runtime's API surface, where you want near-zero cold start at global scale and high density. Choose a Firecracker microVM when you must run arbitrary or AI-generated code, native binaries, subprocesses, full toolchains, or per-tenant databases, or when you need whole-machine snapshot and fork. A common architecture uses both: a Worker at the edge that hands heavy, untrusted work to a microVM backend.

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.