all posts

Kata Containers vs gVisor: the two secure-container runtimes

Ajay Kumar··9 min read

If you've decided a plain container isn't a boundary you'd stake someone else's code on, the two names that come up most for "secure container runtime" are Kata Containers and gVisor. Both slot into the same operational hole — a drop-in OCI runtime, a Kubernetes RuntimeClass, your existing images — and both are genuinely stronger than namespaces-and-cgroups on a shared host kernel. But they get to that stronger boundary by opposite routes. Kata puts a real hardware VM under each container. gVisor re-implements Linux in user space and intercepts the container's syscalls before they ever reach the host kernel — it rewrote the kernel in Go so it can say 'no' to your syscalls more politely. This is the honest two-way comparison, no strawman on either side.

We have separate deep-dives that touch each of these against Firecracker (see /blog/kata-vs-firecracker and /blog/gvisor-vs-firecracker), and a three-way board at /blog/firecracker-vs-kata-vs-gvisor. This post is the focused Kata-vs-gVisor head-to-head — the choice you're actually making when both are already on your shortlist.

Same problem, two answers

The problem both runtimes solve is identical. A container is a Linux process with a private view of the system (namespaces) and a resource budget (cgroups), running on the host's kernel. Every container on the box shares that one kernel, so a kernel bug or a container escape puts the host and every neighbor at risk. The full Linux syscall interface — 300-plus calls, many of them obscure and exploit-favored — is the attack surface the host has to defend against untrusted code. Kata and gVisor are two different ways to shrink that surface, and the difference between them is exactly what you're choosing between.

Kata Containers: a real VM under the container

Kata keeps the container UX and puts a real, lightweight VM underneath it. Kata is an OCI-compatible container runtime — it slots in where runc would, via containerd or CRI-O, and in Kubernetes via a RuntimeClass — so your existing images, registries, and orchestration keep working. The difference is that instead of running the container as a host process, Kata boots a small guest VM per pod/container and runs the workload inside it against a real guest kernel, isolated by CPU hardware virtualization (KVM). A tiny in-guest agent (kata-agent) talks back to the host runtime over vsock to set up the container, mounts, and networking.

The isolation boundary is the one hardware itself enforces: the guest kernel is a genuine, separate Linux kernel, and the host defends the VMM plus the KVM ioctl interface plus a small virtio device surface — not the full syscall ABI. Kata is also VMM-agnostic: it can drive QEMU, Cloud Hypervisor, or Firecracker as the underlying hypervisor, which is why "Kata vs Firecracker" is usually a layering question rather than a rivalry. The cost of the VM approach is per-guest weight — a real kernel and its memory footprint per container — plus the operational surface of running a VMM, a guest kernel, a guest agent, and the Kata shim. That's more moving parts than a single userspace process, in exchange for a boundary the CPU enforces.

gVisor: a kernel rewritten in user space

gVisor (the runtime is runsc, "run sandboxed container") takes the opposite route: no hardware VM at all. Its core component, the Sentry, is a user-space kernel written mostly in Go that re-implements a large fraction of the Linux syscall ABI itself. When the sandboxed workload makes a syscall, gVisor intercepts it and services it inside the Sentry rather than passing it straight to the host kernel. The Sentry maintains its own file-descriptor table, its own virtual-memory bookkeeping, and its own user-space network stack. It still has to touch the host eventually, but only through a small, tightly seccomp-filtered set of host syscalls — so the guest mostly talks to gVisor, and only gVisor talks to the host, through a deliberately narrow door. A separate helper, the Gofer, holds the (narrow) host filesystem access so the Sentry itself keeps minimal host reach.

The win: you shrink the host-kernel attack surface dramatically without booting a second operating system. No guest kernel to manage, no virtio device model, startup much closer to a container than a VM, and higher density because there's no per-guest kernel memory. The cost lands in two workload-dependent places, which we'll get to: compatibility (the Sentry doesn't implement all of the kernel) and per-syscall interception overhead.

gVisor can run on a KVM "platform," and this trips up reviewers constantly: even in KVM mode, gVisor is NOT a hardware VM like Kata. It keeps a userspace-kernel process model with no virtualized guest kernel — it borrows CPU virtualization extensions to make syscall trapping and address-space switching fast, not to boot a guest. "gVisor is basically Kata" is the fastest way to get a security review handed back to you. Kata boots a real guest kernel; gVisor answers your syscalls in Go.

Attack surface: two different things to trust

Both runtimes shrink what untrusted code can reach, but they relocate the boundary to different places, and that changes what you're trusting. With Kata, the untrusted workload runs against a real guest kernel; a full compromise of that guest kernel still leaves the attacker inside a hardware-virtualized VM, and to reach the host they have to defeat the VMM's device emulation or the KVM interface. That's a small, heavily audited surface — but the VMM and KVM have had real guest-to-host escape CVEs, so it isn't magic. With gVisor, the untrusted workload never reaches a real kernel at all; it hits the Sentry, and the host kernel only ever hears from the Sentry through a tight seccomp allowlist. The catch is that the Sentry itself — a large, sophisticated Go codebase re-implementing kernel behavior — now becomes part of your trusted computing base. It's software, so it can have bugs; gVisor moves the boundary from the host kernel to the Sentry rather than eliminating it.

Neither is unbreakable. Kata leans on KVM and VMM device emulation, which have had guest-to-host escape CVEs. gVisor's Sentry is software with its own bug surface. And microarchitectural side channels cross every boundary in principle, because caches and branch predictors are shared at the hardware level. Both are meaningfully stronger than a shared kernel — neither replaces defense-in-depth, network egress controls, resource limits, and short-lived environments.

Compatibility: where gVisor's model shows its seams

This is the sharpest practical difference. Kata runs a real guest Linux kernel, so its compatibility is essentially that of a normal Linux box — if it runs on Linux, it runs in the Kata VM, including native dependencies, unusual syscalls, and exotic filesystem or networking semantics. gVisor re-implements the kernel and, crucially, doesn't implement all of it. Some syscalls are unimplemented; some implemented ones differ in edge-case behavior from the real kernel. Ordinary code overwhelmingly runs fine, but the long tail — programs using unusual syscalls, exotic filesystem semantics, or kernel features the Sentry hasn't reproduced — can misbehave or fail outright.

When you can characterize the workload, that long tail is a non-issue: validate it once against gVisor's compatibility surface and move on. When the workload is arbitrary — an AI agent that can decide to run literally anything — the long tail is exactly where surprises live, and a tool silently failing because a syscall wasn't implemented is a bad failure mode. Check gVisor's own compatibility documentation for specifics rather than assuming, because this is precisely the kind of thing that evolves release to release.

Performance: two different taxes

The models pay for isolation in different currencies, so "which is faster" depends entirely on the workload shape. gVisor's tax is per-syscall: every syscall is trapped and serviced in user space instead of going straight to the kernel, plus filesystem operations hop through the Gofer. CPU-bound code that rarely calls into the kernel barely notices; syscall-heavy and I/O-heavy workloads — lots of small reads and writes, filesystem churn, heavy networking — feel it on every call. Kata's tax is per-guest: a real kernel to boot and a real memory footprint per VM, so its overhead shows up as boot latency and density rather than a per-syscall penalty — once the VM is up, syscalls hit the guest kernel directly with no interception. The overhead on both sides is real and workload-dependent; measure it against your own workload rather than trusting a single headline number, and verify current numbers against each project's docs.

Boot and density

gVisor starts closer to a container because there's no guest kernel to boot, and it packs denser because there's no per-guest kernel memory — this is a genuine advantage when you're running many short-lived, well-behaved sandboxes. Kata boots a real guest kernel per container, which historically made it the heavier of the two on cold start, though the gap depends heavily on the chosen VMM (Kata-on-Firecracker is the fast, dense end; Kata-on-QEMU is heavier) and on template/snapshot techniques. If raw startup and packing density are your dominant concern and your workloads are predictable, gVisor's model has a structural edge; if compatibility and a hardware boundary matter more, Kata's per-guest cost is the price of that boundary.

Side by side

  • Isolation model — Kata: hardware virtualization (KVM) around a real guest kernel, wrapping an OCI container. gVisor: software syscall interception by a user-space kernel (the Sentry), no hardware VM boundary.
  • Attack surface — Kata: the host defends the VMM + KVM ioctl interface + a minimal virtio device surface; the guest kernel is inside the VM. gVisor: the host sees only the Sentry's small seccomp-filtered syscall set, but the Sentry itself joins your TCB.
  • Syscall compatibility — Kata: very high; it's a real Linux kernel, so if it runs on Linux it runs in the VM. gVisor: high for ordinary code, but some syscalls are unimplemented or differ, so exotic or syscall-heavy workloads can break (verify against gVisor's docs).
  • Overhead profile — Kata: per-guest cost (real kernel + memory footprint), but syscalls hit the guest kernel directly with no interception. gVisor: near-container for CPU-bound work; per-syscall interception tax plus Gofer filesystem proxying on syscall- and I/O-heavy workloads.
  • Density — Kata: VM-class per-guest memory (a real guest kernel each). gVisor: higher; no per-guest kernel, so it packs more sandboxes per host.
  • Kubernetes — Kata: native via CRI + a RuntimeClass (annotate a pod, it runs in a VM). gVisor: also a RuntimeClass (runsc handler), so both opt individual pods into stronger isolation with one field.
  • Ops complexity — Kata: more moving parts (shim + VMM + guest kernel + kata-agent); VMM-agnostic across QEMU/Cloud Hypervisor/Firecracker. gVisor: lighter to run — a drop-in OCI runtime with no guest kernel or VMM, just the Sentry + Gofer per sandbox.
  • Best fit — Kata: you want VM-grade isolation and full Linux compatibility inside your existing Kubernetes/OCI world, especially for arbitrary or GPU/device-heavy workloads. gVisor: you want the lightest, densest stronger-than-container boundary for well-characterized workloads you can validate.

Kubernetes: both are a RuntimeClass

Operationally this is where they feel most alike. Both are CRI-compatible, so in Kubernetes you register each as a RuntimeClass and opt individual pods into stronger isolation with a single field — trusted system pods keep running as ordinary containers, untrusted or multi-tenant pods run under Kata (in a VM) or gVisor (under the Sentry). Same cluster, same manifests, mixed isolation. The only thing that changes between them is the handler and what happens underneath:

# Two RuntimeClasses, same cluster. Pick per-pod which boundary you want.
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: kata
handler: kata            # boots a lightweight hardware VM per pod
---
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: gvisor
handler: runsc           # runs the pod under gVisor's user-space kernel
---
apiVersion: v1
kind: Pod
metadata:
  name: untrusted-job
spec:
  runtimeClassName: kata   # swap to `gvisor` to run under the Sentry instead
  containers:
    - name: task
      image: ghcr.io/acme/agent-tool:latest

On the node, installing the runtimes is a similar shape too — you register a runtime handler with containerd/CRI-O and then reference it by name. A quick way to prove which boundary a pod actually got:

# Kata: a real guest kernel means the pod sees a *different* kernel than the
# host node — the guest kernel version, not the host's.
kubectl run kata-probe --rm -it --restart=Never \
  --overrides='{"spec":{"runtimeClassName":"kata"}}' \
  --image=alpine -- uname -r

# gVisor: the reported kernel identity is the Sentry answering uname(), not
# the host kernel and not a booted guest kernel — a user-space kernel is
# fielding the syscall.
kubectl run gvisor-probe --rm -it --restart=Never \
  --overrides='{"spec":{"runtimeClassName":"gvisor"}}' \
  --image=alpine -- uname -a

# Outside Kubernetes, gVisor is just an OCI runtime you can point Docker at:
docker run --rm --runtime=runsc alpine \
  sh -c 'echo hello from inside the Sentry'

# Verify handler names, install steps, and platform flags against the Kata
# and gVisor docs — both runtimes evolve quickly.

How to choose

Pick gVisor when you want stronger-than-container isolation at the lightest operational weight and highest density, your workloads are well-behaved (no exotic syscalls, not pathologically I/O-bound), and you're comfortable validating compatibility against its implemented surface. It's the right amount of boundary at low cost precisely when you can characterize what runs — Google Cloud Run and GKE Sandbox lean on exactly this profile.

Pick Kata when you want a hardware-enforced boundary and full Linux compatibility without leaving Kubernetes/OCI — especially for arbitrary code you can't fully predict, or for workloads that need a richer device model (GPU passthrough, hot-plug) that gVisor's model doesn't cover. You keep your images, registries, and orchestration; Kata swaps the runtime underneath and gives you a real guest kernel behind a hardware boundary. And because Kata is VMM-agnostic, you still choose its engine — QEMU for the fullest device model, or Firecracker/Cloud Hypervisor for the leaner, faster end.

The dividing line, stated plainly: gVisor trades compatibility and a per-syscall tax for lightness and density; Kata trades per-guest weight and operational surface for a hardware boundary and "it's just Linux" compatibility. When you can predict the workload, gVisor's lightness often wins. When you can't — arbitrary, untrusted, model-generated code — the hardware boundary and full compatibility tip toward Kata.

Where a purpose-built microVM platform fits

Both of these live inside the assumption that you're running a Kubernetes cluster and choosing a RuntimeClass for it. If your actual need is "give me a hardware-isolated environment as an API call" — the AI-agent and multi-tenant code-execution case — there's a third shape that isn't a RuntimeClass at all: a platform built directly on a minimal VMM like Firecracker. It lands on the same side of the compatibility argument as Kata (a real guest kernel, so if it runs on Linux it runs here, and no per-syscall interception tax like gVisor's), but it skips the cluster and the container-runtime plumbing entirely.

PandaStack is an Apache-2.0 open-source platform that takes that path. Each sandbox is its own Firecracker microVM with its own guest kernel under KVM, created via snapshot-restore rather than a cold boot — a p50 of 179ms per create (about 203ms p99, with roughly a 49ms restore step). Only the very first cold boot of a template takes around 3s; a same-host copy-on-write fork lands in roughly 400–750ms and a cross-host fork in 1.2–3.5s. One call gives you a hardware-isolated environment with no cluster, no RuntimeClass, and no runtime handler to register:

from pandastack import Sandbox

# One Firecracker microVM per call — its own real guest kernel under KVM,
# created via snapshot-restore (~49ms restore, ~179ms p50 end to end).
# Real Linux inside like Kata ("if it runs on Linux, it runs here"), with
# no per-syscall interception tax and no cluster to stand up.
with Sandbox.create(
    template="code-interpreter",   # python + node scientific stack
    ttl_seconds=300,               # reaped automatically if abandoned
    metadata={"task": "agent-tool-call"},
) as sb:
    out = sb.exec("uname -r && python3 -c 'print(2 + 2)'")
    print(out.stdout, out.exit_code)
# Context manager destroys the microVM here — nothing survives to the next task.

The SDK reads PANDASTACK_API_KEY from the environment; the same flow exists in the TypeScript SDK and CLI. Because the core is Apache-2.0, you can self-host the whole stack on your own Linux KVM hosts — the same Firecracker substrate Kata can drive as a VMM, without assembling either the cluster layer or the platform layer yourself.

The honest bottom line

Kata Containers and gVisor are both real answers to the same problem, and the choice is a genuine trade rather than a ranking. Kata puts a real hardware VM under each container: full Linux compatibility, a boundary the CPU enforces, at the cost of per-guest weight and more operational moving parts. gVisor re-implements Linux in user space and intercepts your syscalls: near-container weight and high density, at the cost of a compatibility long tail, a per-syscall interception tax, and a large new TCB in the shape of the Sentry. Both drop into Kubernetes as a RuntimeClass, so the decision is about the boundary, not the plumbing. For well-characterized workloads where lightness and density dominate, gVisor fits. For arbitrary code where compatibility can't quietly fail, the hardware boundary tips toward Kata — or toward a purpose-built microVM platform like PandaStack if what you want is a VM-isolated environment as an API call rather than a runtime inside a cluster. Match the boundary to your threat and your workload, and verify the workload-specific claims about both against their own documentation, which is the honest thing to do for runtimes that evolve as fast as these.

Frequently asked questions

What's the core difference between Kata Containers and gVisor?

They reach a stronger-than-container boundary by opposite routes. Kata boots a real, lightweight hardware VM (under KVM) beneath each container and runs the workload against a genuine guest kernel — a hardware-enforced boundary with full Linux compatibility. gVisor (runsc) boots no VM at all; its Sentry is a user-space kernel written in Go that re-implements a large fraction of the Linux syscall ABI and intercepts the container's syscalls before they reach the host kernel — a software boundary that's lighter and denser but carries a compatibility long tail. Both drop into Kubernetes as a RuntimeClass, so the choice is about the boundary, not the plumbing.

Which has better syscall compatibility for arbitrary code?

Kata, clearly. It runs a real guest Linux kernel, so its compatibility is essentially that of a normal Linux box — if it runs on Linux, it runs in the Kata VM, including native dependencies and unusual syscalls. gVisor re-implements the kernel in the Sentry and doesn't implement all of it, so some syscalls are unimplemented or differ in edge-case behavior, and syscall-heavy or exotic workloads can break. Ordinary code overwhelmingly runs fine on gVisor; the risk is the long tail, which matters most when the workload is arbitrary and can't be validated in advance. Check gVisor's compatibility docs for specifics.

Is gVisor lighter than Kata?

Yes, in operational weight and density. gVisor is a drop-in OCI runtime with no guest kernel and no VMM — just a Sentry (the user-space kernel) and a Gofer (the host-filesystem proxy) per sandbox — so it starts closer to a container and packs denser because there's no per-guest kernel memory. Kata has more moving parts (a shim, a VMM, a guest kernel, and the kata-agent) and VM-class per-guest memory. The trade for gVisor's lightness is a per-syscall interception tax on syscall- and I/O-heavy workloads, plus the compatibility long tail and the Sentry joining your trusted computing base.

Is gVisor a virtual machine when it uses the KVM platform?

No — and this is a consequential mistake in security reviews. On the KVM platform gVisor borrows the CPU's virtualization extensions to make syscall trapping and address-space switching fast, but there is no guest kernel being booted. The isolation model is still a user-space kernel (the Sentry) intercepting syscalls, not a hardware-virtualized VM. Kata, by contrast, does boot a real guest kernel under KVM. So 'gVisor is basically Kata' is wrong: they're structurally different — a software syscall interceptor versus a real hardware VM.

Which should I use for an AI agent that runs untrusted code?

For arbitrary, model-generated code the hardware boundary and full Linux compatibility usually win, which points to Kata over gVisor — you can't have a tool silently fail because a syscall isn't implemented, and a real guest kernel behind a hardware boundary avoids that. If you're already in Kubernetes and want a drop-in runtime, Kata (optionally on Firecracker as its VMM) fits. If what you actually want is a VM-isolated environment as an API call rather than a runtime inside a cluster, a Firecracker-based platform like PandaStack is the leaner path — it creates a fresh microVM per task in about 49ms via snapshot-restore. Either way, pair it with network egress controls, resource limits, and short-lived environments.

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.