gVisor and Kata Containers: Sandboxed Runtimes in Kubernetes
A plain-English guide to running untrusted workloads safely, by putting an extra wall between your containers and the machine they run on.
Imagine you own a big apartment building. Every tenant has their own locked door, their own furniture and their own mail slot. But behind the walls, all the tenants share the same water pipes, the same electrical wiring and the same boiler in the basement. Most days, nobody notices. Then one tenant finds a way to tamper with the boiler, and suddenly every apartment in the building has a problem.
Containers in Kubernetes work a lot like that building. Each container feels private, but all of them on one machine share a single core piece of software called the operating system kernel. If someone breaks through that shared layer, they can reach everyone else. For years, teams accepted this risk because the workloads they ran were their own code, written by their own engineers.
That world has changed. Today, platforms run customer-submitted code, continuous integration jobs from strangers, plugins from third parties and even code written by AI models. For these cases, “a locked door” is not enough. You need a second wall. Two open-source projects, gVisor and Kata Containers, build that second wall in two very different ways. In this guide you will learn what they are, how they work inside, how Kubernetes lets you choose them, and when each one is the right tool.
1Why Normal Containers Are Not Enough
Before we meet the fixes, we need to understand the problem. Containers are wonderful, but they have one famous weak spot: they share a kernel.
What is a kernel, and why does everyone share it?
The kernel is the most trusted program on any computer. It sits between your applications and the hardware. When an application wants to read a file, send data over the network or start another process, it cannot touch the hardware directly. It asks the kernel to do the job on its behalf. Each of these requests is called a system call, or syscall for short.
A Linux container is not a tiny computer. It is just a normal group of Linux processes that has been given a restricted view of the system. Two Linux features create this restricted view. Namespaces control what a process can see, such as its own process list, its own network interfaces and its own file tree. Control groups, usually called cgroups, control how much a process can use, such as how much memory or CPU time it gets.
Notice what is missing from that list: a separate kernel. Every container on a node talks to the very same Linux kernel running on that node. That is why containers start in a fraction of a second and use so little memory. It is also why they are less isolated than virtual machines.
Think of an apartment building. Each apartment has its own door and lock, which are the namespaces. The building also limits how much water each tenant can use, which is the cgroups. But the pipes, wires and boiler are shared by all. The kernel is the boiler room. A tenant who finds a way into the boiler room is no longer a tenant. They are the building manager.
The attack surface problem
An attack surface is the total set of places where an attacker can try to break something. For a standard container, the attack surface includes every system call that the Linux kernel offers. Linux has several hundred of them, and behind them sits tens of millions of lines of kernel code written over three decades. Code that big always contains bugs, and some of those bugs are security holes.
When a security hole in the kernel is found, a malicious process inside a container can sometimes use it to escape. Escaping means gaining control over the host machine and, from there, over every other container on it. This is not a theory. It has happened many times in the real world.
Real escapes that made headlines
Here are a few well-known examples that show why teams worry about the shared kernel and the shared runtime.
- Dirty COW (2016). A flaw in how the Linux kernel handled memory let an ordinary user write to files they should only be able to read. It affected almost every Linux system of its time, including container hosts.
- runc CVE-2019-5736. A malicious container could overwrite the host’s copy of runc, the program that starts containers, and so run code as the host’s administrator.
- Dirty Pipe (2022). Another kernel flaw, this time in how pipes handled data, allowed overwriting read-only files.
- Leaky Vessels (2024). A set of runc flaws, including CVE-2024-21626, that could let a container reach the host file system through a leaked file handle.
Notice the pattern. Some of these bugs were in the kernel itself, and some were in the runtime that sets up containers. In both cases, the wall between the container and the host was only as strong as the shared code underneath it.
Containers were designed for packaging and resource sharing first, and isolation second. They are a good boundary between your own well-behaved services. They are a weaker boundary between mutually distrusting parties.
Who actually needs a stronger wall?
Not everyone does. If you run your own microservices, written by your own team, on your own cluster, regular containers plus good hygiene are usually fine. A stronger wall becomes valuable when the code you run is not fully trusted. Common cases include:
- Multi-tenant platforms, where many customers share one cluster and you promise each one that others cannot reach them.
- Serverless and function platforms, where strangers upload arbitrary code and you run it for them.
- Continuous integration systems, where a build job may run a script pulled from a pull request opened by anyone.
- Online coding playgrounds and notebooks, where users type code and press run.
- AI agents and code interpreters, where a model writes code that your platform executes without a human reading it first.
- Regulated workloads, where auditors want a strong, provable boundary around sensitive data.
For all of these, the answer is the same idea: put one more layer between the workload and the host kernel. That layer is what we call a sandbox.
2What a Sandboxed Runtime Really Is
A sandboxed runtime is a replacement for the part of the system that actually starts your container. It keeps everything else about containers the same, but changes how the container touches the host.
The sandbox idea in one sentence
A sandbox is a protective box around a program, built so that even if the program is hostile, it can only play inside the box. A children’s sandbox keeps sand from spreading across the garden. A software sandbox keeps a misbehaving program from spreading across the machine.
A sandboxed container runtime applies this idea to containers. You still build images the same way. You still write the same Kubernetes objects. Your application does not need to be rewritten. What changes is the engine underneath, which now adds an extra boundary between the container and the host kernel.
Two strategies: intercept or virtualize
There are two main ways to build that extra boundary, and these two ways are exactly what gVisor and Kata Containers represent.
Intercept and re-implement
Put a guard in front of the host kernel. The guard receives every request from the application and handles most of them by itself, using its own code. Only a very small number of safe requests ever reach the real kernel. This is gVisor.
Virtualize and isolate
Give the workload its own private kernel inside a very small virtual machine. Whatever happens inside stays inside, because the virtual machine is walled off by hardware features. This is Kata Containers.
Shrink what the host exposes
Both approaches aim to make the host kernel reachable through a much narrower and better-guarded door than a normal container ever gets.
Same Kubernetes experience
Both plug into Kubernetes through the standard runtime interface, so a pod still looks like a pod to the cluster, your tools and your teammates.
Picture a bank. Strategy A is the teller behind bulletproof glass. You pass your request through a small slot, the teller handles it, and you never touch the vault. Strategy B is giving every customer their own small private office in a separate building, with their own staff, so nothing they do can ever reach another customer’s office. The glass is cheap and fast but the teller must understand every request. The private office is sturdier but costs more space and takes longer to open.
What a sandbox does not do
It is important to stay realistic. A sandboxed runtime does not replace good security practice. It will not fix a weak password, an over-permissive cloud role or a leaked secret. It does not stop an application from being hacked at the application level, for example through a SQL injection. What it does is limit the damage after that, by making it much harder for the compromised program to break out to the host and to its neighbors. Security experts call this defense in depth: many layers, so that one failure is not fatal.
flowchart TB
subgraph RUNC["Regular container - runc"]
A1["Application"] --> K1["Host Linux Kernel"]
end
subgraph GV["gVisor sandbox"]
A2["Application"] --> S2["User-space kernel - Sentry"]
S2 -->|"few filtered calls"| K2["Host Linux Kernel"]
end
subgraph KT["Kata Containers sandbox"]
A3["Application"] --> G3["Guest Linux Kernel"]
G3 --> H3["Hypervisor - VMM"]
H3 --> K3["Host Linux Kernel"]
end
Read the figure from top to bottom in each box. In the regular container, the application speaks straight to the host kernel. In gVisor, there is a guard program in the middle. In Kata, there is a whole second kernel and a hypervisor in the middle. The more layers there are, the harder an attack becomes, but each layer also adds some cost. We will weigh those costs later.
3How Kubernetes Starts a Container
To understand how a sandboxed runtime plugs in, you first need to see the chain of programs that turns a pod request into a running process.
The chain, link by link
When you ask Kubernetes to run a pod, no single program does all the work. Several programs hand the job along like workers on an assembly line. Each one has a clear role.
Kubelet
The kubelet is the agent running on each node. It watches the cluster for pods assigned to its node and decides what must be started or stopped.
CRI
The Container Runtime Interface is a standard set of calls the kubelet uses to talk to any compatible runtime. It lets Kubernetes stay neutral about which engine runs underneath.
High-level runtime
Programs like containerd or CRI-O receive CRI calls. They pull images, manage storage and network setup, and keep track of container state.
Shim
A small helper process that stays alive next to each pod. It lets the high-level runtime restart without killing running containers, and it reports back on exits.
Low-level OCI runtime
This is the program that really creates the isolated process. The default is runc. It follows the Open Container Initiative specification to set up namespaces and cgroups, then starts your application.
Where the sandbox plugs in
The clever part is that only the last two links need to change. Kubernetes, the kubelet and even containerd stay exactly as they are. To get a sandbox, you swap runc for a different low-level runtime that follows the same rules but isolates the workload differently.
- For gVisor, the replacement is a runtime called
runsc. - For Kata Containers, the replacement is a shim and runtime pair, usually referenced as
kata, that starts a lightweight virtual machine.
Because the swap happens deep in the stack, your container images, your registry, your deployment tools and your monitoring dashboards keep working. This compatibility is the single biggest reason these projects are practical.
flowchart LR
API["Kubernetes API Server"] --> KL["Kubelet on the node"]
KL -->|"CRI calls"| CD["containerd or CRI-O"]
CD --> SH["Runtime shim"]
SH --> R1["runc - default"]
SH --> R2["runsc - gVisor"]
SH --> R3["Kata runtime - lightweight VM"]
R1 --> W1["Regular container"]
R2 --> W2["gVisor sandbox"]
R3 --> W3["Kata micro VM"]
Newer containerd versions use a “shim v2” design where one shim process serves a whole pod. This fits sandboxed runtimes very well, because a pod maps neatly to one sandbox or one virtual machine.
A confusing word: “sandbox” used two ways
Here is a common trap for beginners. In Kubernetes, every pod already has something called a pod sandbox. This is simply the shared environment, namespaces and a tiny placeholder process often called the pause container, that all containers in the pod live in. It is a bookkeeping idea, not a security feature.
When people say sandboxed runtime or sandboxed container in a security conversation, they mean something stronger: an extra isolation layer such as gVisor or Kata. The two uses of the word are unrelated. Every pod has a pod sandbox, but only pods using a runtime like gVisor or Kata are security-sandboxed.
4Meet gVisor: A Kernel Written for Safety
gVisor takes the “guard at the glass window” approach. It places a brand-new kernel, written from scratch, between your application and the real one.
What is gVisor?
gVisor is an open-source project started at Google. Its core idea is surprisingly bold: write a second Linux-compatible kernel that runs as an ordinary program, not as part of the operating system. Google calls this an application kernel. Your container believes it is talking to Linux. In truth, it is talking to this program, which then decides what is safe to pass along to the real kernel underneath.
gVisor is written in Go, a memory-safe language. This matters a great deal. Many of the worst kernel bugs in history come from memory mistakes in the C language, such as reading past the end of a buffer. A memory-safe language makes whole families of those mistakes much harder to make.
Think of a hotel concierge. Guests never walk into the hotel’s back offices to book a taxi or order a repair. They ask the concierge, who handles almost everything with the hotel’s own staff and phones the outside world only when truly needed. If a guest behaves strangely, the concierge simply refuses. The concierge is gVisor’s kernel, and the outside world is the real Linux kernel.
The main parts
runsc
The command-line runtime that follows the OCI rules. This is the program containerd launches in place of runc. It sets up the sandbox and starts the other parts.
Sentry
The heart of gVisor. It is the user-space kernel that implements system calls, memory management, process handling, signals, threads and more, all in its own code.
Gofer
A helper process that opens files on the host on behalf of the sandbox. The Sentry is not allowed to open host files directly, so this helper acts as a tightly controlled file clerk.
Netstack
gVisor’s own network stack, also written in Go. It handles protocols such as TCP and UDP inside the sandbox rather than leaning on the host’s network code.
Platform
The mechanism used to catch system calls from the application. Common choices are the default systrap approach and a KVM-based approach for certain environments.
Seccomp filter
The Sentry itself is boxed in by a strict filter that allows only a small list of host system calls. Even if the Sentry were tricked, its reach would stay narrow.
flowchart TB
APP["Your application in the container"] -->|"system calls"| SENTRY["Sentry - user-space kernel"]
SENTRY --> NET["Netstack - own network stack"]
SENTRY -->|"file requests"| GOFER["Gofer - file access helper"]
SENTRY -->|"small filtered set of calls"| HOSTK["Host Linux Kernel"]
GOFER --> HOSTK
NET -->|"packets"| HOSTK
SEC["Seccomp filter"] -.->|"restricts"| SENTRY
Two layers of protection
gVisor gives you two walls, not one. First, an attacker must find a bug in the Sentry to do anything interesting. Second, even after finding one, the Sentry is itself restricted to a short list of host calls. To escape, an attacker would need to chain two separate weaknesses: one in the Go code of gVisor and one in the small slice of the host kernel that the Sentry is still allowed to reach. That is a far taller order than exploiting a single kernel bug.
gVisor does not claim to be bug-free. Its promise is narrower and more honest: it reduces the amount of host kernel code that untrusted programs can reach, and it uses a safer language for the code that does the guarding.
5Inside gVisor: The Life of a System Call
The best way to understand gVisor is to follow one small request, such as reading a file, from the moment the application asks to the moment it gets an answer.
Step by step
The application makes a system call
Your program wants to read a file, so it issues a read system call, exactly as it would on any Linux machine. It has no idea anything is different.
The platform intercepts it
Instead of the call landing in the host kernel, the chosen platform catches it and hands control to the Sentry. This is the moment the guard steps in.
The Sentry decodes and checks
The Sentry looks at which call was made, validates every argument, checks permissions using its own bookkeeping, and decides how to serve it.
The Sentry serves it, or asks for help
Many calls, such as memory allocation or inspecting process state, are answered completely inside the Sentry. File operations are forwarded to the Gofer, which opens and reads the file using narrow, pre-approved access.
The answer returns to the application
The Sentry places the result where the application expects it and resumes the program. From the program’s point of view, it simply called Linux and got an answer.
How the interception works
The platform is the piece that physically catches the call. Think of it as the doorbell that rings the concierge. Different platforms trade speed for portability.
- Systrap is the modern default. It uses a combination of host features to trap system calls efficiently and works on ordinary machines, including cloud virtual machines, without special hardware access.
- KVM platform uses the processor’s virtualization features to run the application in a special mode, which can make interception faster on bare-metal hosts where this is available.
Older versions also offered a ptrace-based platform that was easy to run but slower. Recent versions have moved on from it, so treat any advice that relies on it as dated.
Files and the network
File access deserves special attention because it is where many container escapes begin. In gVisor, the Sentry keeps its own in-memory picture of the file tree and talks to the Gofer over a dedicated protocol when it needs real files from the host. The Gofer is a separate process with a very limited set of permissions. Even if the application tricks the Sentry, the Gofer will only open what it has been told is allowed.
For networking, gVisor can use its own netstack. Packets are processed by gVisor’s Go code, and only raw packets cross to the host. This keeps the host’s network code, which has seen its share of vulnerabilities, further from the workload. There is also a mode that passes network calls through to the host stack for speed. That mode is faster but gives up part of the isolation benefit, so teams choose it deliberately.
The honest cost: compatibility
Because gVisor re-implements Linux rather than using Linux, it must catch up with every feature. It supports most commonly used system calls, which is enough for the vast majority of web services, databases and language runtimes. However, unusual calls, exotic file systems, and some specialized tools may be missing or behave slightly differently. A program that works on regular Linux may fail in gVisor because of one rarely used call. This is why testing your workload in the sandbox before rolling it out widely is non-negotiable.
Workloads that make huge numbers of system calls, or that move lots of small file or network operations, can slow down noticeably. Each call now takes a detour through the Sentry. Heavy computation that rarely calls the kernel is barely affected.
6Meet Kata Containers: A Tiny VM for Every Pod
Kata Containers takes the opposite road. It does not guard the kernel. It gives each pod its own private kernel inside a very small, very fast virtual machine.
What is Kata Containers?
Kata Containers is an open-source project that runs containers inside lightweight virtual machines while still behaving like a container runtime. It was formed in 2017 by merging two earlier efforts: Intel’s Clear Containers and Hyper’s runV. It is now hosted by the OpenInfra Foundation and has contributors from many large companies, with strong early involvement from firms such as Ant Group and Intel.
A virtual machine, or VM, is a pretend computer running inside a real one. The pretend computer has its own kernel. A hypervisor is the program that creates and manages such pretend computers, and it uses special processor features to keep each one strictly walled off. Ordinary VMs are heavy because they carry a full operating system and boot slowly. Kata trims the VM down to the bare minimum so that it boots in a fraction of a second and uses little memory.
Back to the apartment building. A regular container is a rented apartment. A Kata pod is a small detached cottage with its own plumbing, wiring and boiler. If something goes wrong in one cottage, the neighbors never feel it, because nothing is shared except the plot of land underneath. The cost is that you need a bit more land and building a cottage takes longer than unlocking an apartment.
The main parts
Kata shim and runtime
The program that plugs into containerd or CRI-O. It receives the pod request and orchestrates the creation of the virtual machine.
Hypervisor or VMM
The virtual machine monitor that actually creates the VM. Kata can work with several, including QEMU, Cloud Hypervisor, Firecracker and a built-in option called Dragonball.
Guest kernel
A small, tuned Linux kernel that runs inside the VM. It is stripped of features that a pod does not need, which keeps boot time and memory low.
Guest root file system
A minimal image inside the VM, containing just enough to start the helper agent and then the containers.
Kata agent
A small program inside the VM that takes commands from outside and creates the actual containers within the guest, using ordinary namespaces and cgroups.
Virtio devices
Efficient virtual hardware, such as virtual network cards and shared file systems, that let the VM exchange data with the host in a controlled way.
flowchart TB
CRI["containerd or CRI-O"] --> SHIM["Kata shim v2"]
SHIM --> VMM["Hypervisor - QEMU, Cloud Hypervisor, Firecracker"]
subgraph VM["Lightweight VM - one per pod"]
GK["Guest Linux Kernel"]
AG["Kata agent"]
C1["Container A"]
C2["Container B"]
AG --> C1
AG --> C2
GK --- AG
end
VMM --> VM
SHIM |"vsock channel"| AG
VMM --> HK["Host Linux Kernel and KVM"]
One VM per pod, not per container
A key design choice is that Kata creates a VM for the whole pod, not for each container. This matches how Kubernetes thinks. All containers in a pod already share a network and can share storage, so they all live together in the same VM. The boundary that Kata protects is between pods, which is exactly where trust boundaries usually sit.
7Inside Kata: How a Pod Becomes a Micro VM
Now let us follow a pod from request to running container and see where the isolation comes from.
The lifecycle
Containerd calls the Kata shim
The high-level runtime sees that this pod asks for the Kata runtime and starts the Kata shim instead of the default one.
The hypervisor creates the VM
The shim asks the chosen hypervisor to build a small virtual machine with a defined amount of memory and CPU, using the hardware virtualization features of the processor.
The guest kernel boots
The tiny kernel starts inside the VM. Because it is stripped down and tuned, this step is fast, commonly well under a second on modern hardware.
The agent comes alive
The Kata agent starts and opens a private communication channel to the shim. The channel is usually vsock, a special host-to-guest link that does not use the normal network.
The image is shared into the guest
The container’s file system is made available inside the VM, typically using a shared file system called virtio-fs or a block device that holds the image layers.
The agent starts the containers
Inside the guest, the agent creates namespaces and cgroups and launches your application, exactly like a normal container, except that every system call now lands in the guest kernel rather than the host’s.
Why this is strong
When your application makes a system call, the guest kernel answers it. The host kernel never sees that call. For a program to harm the host, it would first need to find a bug in the guest kernel, which only gets it control of the guest. Then it would need a second bug in the hypervisor or in the narrow set of virtual devices, to cross from the guest to the host. Hardware virtualization is one of the most heavily tested isolation mechanisms in computing, and it is what public clouds already rely on to separate customers from each other.
Networking and storage
Networking is a tricky part, because Kubernetes expects each pod to have a network identity prepared by a network plugin outside the VM. Kata bridges this gap by connecting the virtual network card inside the VM to the pod’s network setup on the host, so that traffic flows as the cluster expects. From outside, the pod still looks and behaves like any other pod, with its own IP address.
For storage, volumes mounted into a pod must cross the VM boundary. Kata handles this with shared file system mechanisms or by handing the VM a virtual disk. This crossing is a place where performance can differ from regular containers, so file-heavy workloads should be measured.
Resources and overhead
A VM carries a fixed cost: the guest kernel, the agent and the hypervisor itself use memory and CPU even if your application is idle. Kata can resize the VM as containers are added, adding CPU and memory while it runs. Still, this fixed cost is real, and Kubernetes has a field to account for it, which we will meet in the next chapter.
Kata needs hardware virtualization support. On a bare-metal server this is usually available. On a cloud virtual machine, you need a machine type that allows nested virtualization, which means a VM inside a VM. Not every cloud instance type permits this, so check before you plan a cluster around it.
The same VM-based design underpins the Confidential Containers effort, which combines Kata with hardware features that encrypt memory so that even the host administrator cannot read a pod’s data. It builds on the isolation idea you just learned.
8RuntimeClass: How a Pod Picks Its Runtime
You now know the two sandboxes. The next question is practical: how do you tell Kubernetes, pod by pod, which one to use?
What is a RuntimeClass?
A RuntimeClass is a cluster-wide Kubernetes object that gives a friendly name to a runtime configuration. Think of it as a menu item. The kitchen, which is the node, knows how to prepare each dish. The customer, which is the pod, just orders by name. RuntimeClass became generally available in Kubernetes 1.20, so every supported cluster today has it.
A RuntimeClass has three important parts.
handler
The name of the runtime as configured on the node. It must match a handler name registered in containerd or CRI-O. If the names do not match, the pod cannot start.
overhead
The extra CPU and memory that the sandbox itself consumes. The scheduler adds this to the pod’s own requests so that nodes are not over-packed. This matters most for Kata, which has a fixed VM cost.
scheduling
Node selectors and tolerations that guide the pod to nodes that actually have the runtime installed. This prevents a gVisor pod from landing on a node that cannot run it.
runtimeClassName
A single field in the pod specification. Setting it to the name of a RuntimeClass is all a developer must do to request the sandbox. Leaving it empty means the default runtime.
The full journey
Install the runtime on chosen nodes
A platform engineer installs gVisor or Kata on a set of nodes, usually a dedicated group, and does not touch the rest.
Register a handler
In the containerd or CRI-O settings on those nodes, a handler name such as gvisor or kata is mapped to the new runtime.
Mark the nodes
The sandbox nodes get a label so selectors can find them, and often a taint so that ordinary pods stay away and do not waste the expensive capacity.
Create the RuntimeClass
A cluster administrator creates the object with the handler name, the overhead values, and the scheduling hints for the labeled and tainted nodes.
Request it from a pod
A workload sets runtimeClassName. An admission step automatically merges the overhead and scheduling rules into the pod, so developers do not need to repeat them.
Verify
The team checks that the pod is running on a sandbox node and that the sandbox is really in use, for example by looking at the kernel version seen from inside the pod, which differs under gVisor and Kata.
sequenceDiagram
participant Dev as Developer
participant API as API Server
participant Sch as Scheduler
participant Kub as Kubelet
participant Run as containerd
participant Sbx as Sandbox runtime
Dev->>API: Create pod with runtimeClassName
API->>API: Admission adds overhead and node rules
API->>Sch: Pod needs scheduling
Sch->>Kub: Assign to a sandbox-capable node
Kub->>Run: Create pod sandbox via CRI
Run->>Sbx: Start with matching handler
Sbx-->>Kub: Sandbox and containers running
Cluster policy tools such as admission controllers can enforce that certain namespaces, for example one that runs customer code, must always use a sandbox RuntimeClass. This turns a good habit into a guarantee that no one can forget.
Common failures at this stage
- Handler name mismatch. The RuntimeClass names a handler that the node does not know. The pod stays stuck in a creating state, and the event log mentions an unknown runtime.
- Missing node selection. The scheduler places the pod on a node without the runtime. Always pair the RuntimeClass with scheduling rules.
- Forgotten overhead. Without it, nodes look emptier than they are, and pods can get squeezed or evicted under load.
- Unsupported pod settings. Privileged mode, sharing the host network or process space, and some host-path mounts conflict with the whole point of a sandbox and may be rejected or ignored.
9gVisor vs Kata vs runc: Side by Side
Now that you know how each one works, here is a direct comparison. The figures here are directional, since real numbers depend heavily on your hardware and workload.
| Aspect | runc (regular) | gVisor | Kata Containers |
|---|---|---|---|
| Isolation method | Namespaces and cgroups | User-space kernel intercepting calls | Separate guest kernel in a lightweight VM |
| Kernel the app talks to | Host kernel | Sentry, written in Go | Guest Linux kernel |
| Host kernel exposure | Full system call surface | Small filtered set | Mostly the hypervisor and virtual devices |
| Start-up time | Very fast | Fast, close to regular | Slower, due to VM boot |
| Fixed memory overhead | Negligible | Small | Noticeable per pod |
| Linux compatibility | Complete | Very good, with gaps | Very high, since it is a real Linux kernel |
| CPU-heavy work | Native speed | Near native | Near native |
| System call, file and network heavy work | Native speed | Can be slower | Moderate cost from virtualization |
| Hardware needs | None special | Works on ordinary hosts | Hardware virtualization, or nested virtualization in the cloud |
| Best fit | Trusted internal services | Multi-tenant platforms, many short tasks | Strongest isolation, regulated and sensitive workloads |
Reading the table like an architect
No column wins every row. A regular container is the fastest and simplest, but has the weakest wall. gVisor gives a strong wall for a modest price, but you pay with compatibility questions and slower system-call-heavy work. Kata gives the strongest wall and the highest compatibility, but you pay with start-up time, per-pod memory, and a hardware requirement.
Choosing a runtime is like choosing how to ship a parcel. Regular post is cheap and quick, and fine for a birthday card. A courier with tamper-proof packaging costs a little more and suits valuables, though not every odd-shaped item fits their rules. Hiring an armored truck with its own driver is safest for jewels, but it is slow and expensive for ordinary mail. You match the protection to the value of what you carry.
A simple way to decide
You own and trust the code
Internal services from your own team, with strong pod security settings, rarely need a sandbox. Keep things simple.
Many small, untrusted tasks
Function platforms, notebooks, build jobs and plugin hosts benefit from its light weight and quick starts, if your programs fit its supported calls.
You need the hardest boundary
Hostile or regulated workloads, or programs that need full Linux behavior and specialized kernel features, suit a real kernel in a real VM.
Different pools for different risk
Many clusters offer all three through separate RuntimeClasses, and let teams pick based on the sensitivity of each workload.
10Advantages, Disadvantages and Trade-offs
Every engineering choice is a trade. Here is an honest ledger for each sandbox, so you can decide with open eyes.
gVisor
Advantages
- Big reduction in host kernel exposure, with a second layer around the Sentry itself.
- Memory-safe Go implementation avoids a whole class of memory bugs.
- Light on resources, so many sandboxes fit on one node.
- Works on ordinary machines without needing hardware virtualization.
- Quick to start, which suits short-lived tasks.
Disadvantages
- Not every Linux feature or system call is available.
- Syscall-heavy, file-heavy and network-heavy work can slow down.
- Some low-level tools and specialized devices may not work as expected.
- Debugging can be harder because the usual host-level tools see less.
- Still shares the host kernel for the few calls it passes through.
Kata Containers
Advantages
- Hardware-enforced isolation with a separate guest kernel per pod.
- Runs a real Linux kernel, so compatibility is excellent.
- Choice of hypervisors, so teams can pick a trade between speed and features.
- Supports confidential computing options for protecting data in use.
- Lets a pod have its own kernel settings without touching the host.
Disadvantages
- Slower start than a regular container because a VM must boot.
- Fixed memory and CPU cost for every pod.
- Needs hardware virtualization, which can be complicated on cloud instances.
- Storage and network crossing the VM boundary can reduce throughput.
- More moving parts to install, upgrade and keep patched.
The three-way tension
You can think of the decision as a triangle with three corners: security, performance and compatibility. Regular containers sit at performance and compatibility. gVisor leans to security and performance, at some cost to compatibility. Kata leans to security and compatibility, at some cost to performance and density. No point inside the triangle gives you all three corners at once, and anyone who promises otherwise is selling something.
Trade-offs shift over time. Both projects keep improving their speed, and hardware gets better at virtualization. Re-measure your own workload every year or so rather than relying on a number you read once.
11Security Deep Dive: What You Gain and What You Do Not
Security is not a switch. It is a stack of layers, and a sandbox is only one of them. Here is how to think about it clearly.
Start with a threat model
A threat model is simply a written answer to three questions: who might attack, what are they after, and how could they try. For sandboxed runtimes, the typical attacker is a tenant or a piece of untrusted code that already runs inside your cluster. Their goals are usually to reach the host, to read other tenants’ data, to steal credentials, or to use your machines for mining and attacks.
A sandbox mainly addresses the first goal and, by extension, the second. It makes the jump from “I control this container” to “I control this node” far harder. That jump is called a container escape, and stopping it is the main reason these runtimes exist.
Layers that work alongside the sandbox
Least privilege inside the pod
Run as a non-root user, drop extra capabilities, and use a read-only root file system. A hacked app with little power is a small problem.
Pod security rules
Use Kubernetes Pod Security Standards to forbid privileged pods, host namespaces and risky volume types in sensitive namespaces.
Network policy
Allow only the connections each workload truly needs. Block access to internal services and cloud metadata endpoints by default.
Identity and secrets
Give each workload narrow, short-lived credentials. Avoid mounting service account tokens in pods that do not need them.
Image hygiene
Scan images, pin versions and pull from trusted registries so that a bad image never starts in the first place.
Patching
Keep the sandbox runtime, the hypervisor, the node operating system and Kubernetes itself up to date. A sandbox with old code is a weaker sandbox.
What a sandbox does not cover
- Application bugs. A sandbox does not stop someone exploiting your web app. It only limits what they can do next.
- Over-broad permissions. If the pod carries a powerful cloud role, the attacker uses that role from inside the sandbox, with no escape needed.
- Shared mounts. Mounting sensitive host directories into the pod hands the attacker a path out, and it is often unsupported anyway.
- Network trust. A compromised pod can still call services reachable on the network. Isolation of the kernel is not isolation of traffic.
- Hardware side channels. Attacks that infer secrets from shared processor behavior are a separate problem. VMs and sandboxes help in different ways, but neither erases the risk fully.
- Supply chain. A poisoned dependency running inside a sandbox is still poisoned. The sandbox just keeps the blast radius smaller.
Locking a thief inside a cell does not help if you also hand them the keys to the building through the food slot. The cell, which is the sandbox, only works if what you pass in and out of it, such as credentials, mounts and network access, is also carefully limited.
Real escape history and the sandbox lesson
Recall the bugs from the first chapter. Flaws that live in the host kernel or in the container runner are reachable by a normal container, because the container shares them. In a sandbox, the application is aimed at a different target first, either the Sentry or a guest kernel. An attacker needs to find a flaw in that new target and then another one past it. This does not make an escape impossible, and both projects have had their own security fixes over the years. What it does is raise the price and force an attacker to chain several bugs together, which is much rarer and much noisier.
Never treat a sandbox as a reason to relax the other layers. Teams that think “we have gVisor, so we can run it as root with full permissions” have thrown away the very depth that makes the design work.
12Performance, Scalability and Cost
A stronger wall always costs something. The job of an architect is to know where the cost lands and how to keep it small.
The figures above come from the design goals that the Firecracker project publishes for its own microVMs. Treat them as best-case examples of how small a VM can be made, not as promises for every Kata setup. Your numbers will depend on the hypervisor, the guest kernel, the image and the hardware.
Where the cost appears
- Start-up latency. Regular containers start in tens of milliseconds. gVisor adds a little. Kata adds the time to boot a guest, which can reach a few hundred milliseconds or more depending on settings. For long-running services this is irrelevant. For functions that must start on every request, it matters a lot.
- Per-pod memory. Kata’s VM has a fixed footprint, so a node holds fewer pods than it would with regular containers. gVisor’s footprint is lighter.
- System call and I/O speed. gVisor’s detour through the Sentry affects call-heavy workloads such as databases that do many small reads. Kata’s cost shows up in shared file systems and virtual network paths.
- CPU-bound work. Pure computation, such as encoding or number crunching, runs at close to native speed in both.
Ways to keep the cost down
Sandbox only what needs it
Do not blanket the whole cluster. Put the risky, untrusted workloads in the sandbox pool and leave trusted services on the fast path.
Use dedicated node pools
Group sandbox nodes together, tune them for the runtime, and let the autoscaler grow or shrink that group independently.
Account for overhead
Set the RuntimeClass overhead honestly so that the scheduler packs nodes correctly and capacity planning stays truthful.
Warm things up
For latency-sensitive tasks, keep a small pool of ready sandboxes, or use snapshot and restore features that the ecosystem offers, so users do not wait for a cold boot.
Measure with your real workload
Benchmark your own application on regular, gVisor and Kata runtimes. A generic benchmark tells you little about your database or your API.
Scaling to many thousands of sandboxes
At large scale, the limiting factors are usually node memory for Kata and system-call throughput for gVisor. Platforms that run huge numbers of short tasks tune the guest image to be tiny, trim the number of devices exposed to each VM, and place the sandbox pool in its own autoscaling group. They also watch the rate at which new sandboxes are created, since mass start-ups can saturate the container runtime and the image pull path before the sandbox layer itself.
13Monitoring, Logging and Troubleshooting
A sandbox hides your workload from the host, and that includes hiding it from some of your usual tools. Plan your observability before the first incident, not during it.
What keeps working
The good news is that the everyday Kubernetes experience is unchanged. Application logs written to standard output still flow through the kubelet, so your log shippers keep collecting them. Commands such as viewing logs or opening a shell in a pod still work. Resource requests, limits, readiness checks and liveness checks behave as expected. Metrics for CPU and memory still reach your monitoring system through the normal channels.
What changes
Host-level tracing
Tools that watch system calls on the host, including many security agents built on eBPF, see the sandbox as a few host calls, not the inner activity of your application. The detailed calls happen inside the Sentry or the guest kernel.
Process view
Looking at the host process list does not reveal the processes inside a Kata guest. They live inside a virtual machine, which appears as just a few host processes.
Runtime logs
Both runtimes have their own logs. gVisor can write debug logs for the sandbox, and Kata components log through the node’s system journal, with levels that can be raised during an investigation.
Runtime metrics
Kata ships a monitoring helper that exposes sandbox metrics in a format Prometheus can scrape, and gVisor offers its own metrics server for sandbox statistics.
Putting security monitoring in the right place
If your security team relies on agents that watch the host kernel, they will need a plan for sandboxed pods. One common choice is to run a lightweight agent inside the sandbox, as a sidecar or part of the image, where it sees the sandboxed system calls. Another is to rely on network-level monitoring and audit logs from the cluster API, which are unaffected by the sandbox. Whichever you choose, test it on a sandbox node before declaring the detection coverage complete.
A short troubleshooting checklist
Pod stuck in creating
Read the pod events. Look for an unknown handler message, which points to a name mismatch, or a failure to start the VM, which points to missing virtualization support.
Pod runs but behaves oddly
Suspect a missing system call or file feature under gVisor. Enable the runtime’s debug logging and look for messages about unsupported calls.
Pod is slow
Compare the same workload on a regular runtime. If only the sandbox is slow, check whether it is system-call-heavy, file-heavy, or limited by a small VM size.
Pod killed for memory
Confirm that the overhead is set and the limits leave room for the guest kernel and agent. A VM that is too small can run out of memory even when the app seems fine.
Not sure the sandbox is active
Check from inside the pod. The kernel information reported by the sandbox differs from the host’s, and that difference confirms which runtime is actually in use.
14Design Patterns and Anti-Patterns
Teams that run sandboxes well tend to follow the same few patterns. Teams that struggle tend to fall into the same few traps.
Patterns that work
Dedicated sandbox node pool
Install the runtime only on a labeled and tainted group of nodes. Only pods that ask for the sandbox land there, which keeps costs and complexity contained.
Policy-enforced RuntimeClass
Use an admission policy so that every pod in an untrusted namespace is automatically given the sandbox class. Developers cannot forget it or turn it off.
One namespace per tenant
Combine the sandbox with a namespace, a network policy and a resource quota per customer. The sandbox protects the kernel, and the rest protects everything above it.
Tiered runtimes
Offer regular, gVisor and Kata classes with clear guidance. Teams choose a tier based on how sensitive and how trusted their workload is.
Ephemeral sandboxes
For untrusted jobs, create a fresh sandbox per task and destroy it afterward. Nothing an attacker leaves behind survives to the next run.
Warm pool
Keep a handful of ready sandboxes for latency-sensitive work so that users do not pay the cold-start price.
Anti-patterns to avoid
What it looks like
The team decides to run every workload in a sandbox, including trusted internal services and databases, because “more security is always better.”
Why it hurts
The cluster pays the memory, latency and compatibility costs for workloads that did not need the protection. Databases and message brokers, which are heavy on system calls and storage, often suffer most.
Better approach
Classify workloads by trust level. Sandbox the untrusted ones and apply strong standard hardening to the rest.
What it looks like
A pod is run in a sandbox but with elevated settings: broad Linux capabilities, a powerful service account, a mounted host directory, or open access to the cloud metadata service.
Why it hurts
The attacker no longer needs to escape. The pod already holds the keys to important resources, so the sandbox walls protect nothing that matters.
Better approach
Keep least privilege inside the sandbox. Treat the sandbox as an extra layer on top of those settings, never as a replacement for them.
What it looks like
The sandbox is rolled out to production without testing the real application, and without setting overhead or node scheduling in the RuntimeClass.
Why it hurts
Hidden compatibility problems appear at the worst moment, and nodes become overcommitted because the scheduler does not know about the sandbox’s own cost.
Better approach
Run a staged rollout. Test in a staging cluster, measure performance, set overhead truthfully and widen the rollout slowly.
15Best Practices and Common Mistakes
This chapter pulls the lessons together into a checklist you can actually use.
Best practices
- Write down your threat model first. Know who you are defending against. It guides whether you need gVisor, Kata or neither.
- Test your actual workload. Run your real container, with real traffic patterns, in the sandbox before you commit. Check both correctness and speed.
- Use RuntimeClass fully. Set the handler, the overhead and the scheduling rules together, so that the platform handles placement and capacity for you.
- Separate the pools. Keep sandbox nodes apart from regular nodes with labels and taints. This keeps cost visible and troubleshooting simple.
- Keep all the other layers. Non-root users, limited capabilities, network policies, narrow credentials and image scanning all stay in place.
- Patch promptly. Track security announcements for your chosen runtime, hypervisor, guest kernel and host operating system. Upgrade on a regular schedule.
- Plan observability early. Decide how logs, metrics and security detections will work from inside or around the sandbox.
- Enforce with policy. Use admission controls so that untrusted namespaces cannot run without the sandbox class.
- Document the limits. Tell developers which features are unsupported, such as privileged mode or host networking, so they do not discover the rules by failure.
- Rehearse failure. Practice what happens if the sandbox node pool is full, if a runtime upgrade fails, or if a guest crashes.
Common mistakes
- Believing a sandbox makes an insecure application safe.
- Choosing a runtime from a blog post benchmark instead of measuring your own workload.
- Forgetting that Kata on cloud virtual machines needs nested virtualization or bare-metal nodes.
- Leaving the RuntimeClass overhead empty and then wondering why nodes run out of memory.
- Mounting host paths or enabling host networking and expecting the sandbox to cope.
- Using the sandbox for databases or very I/O-heavy systems without checking the performance impact.
- Skipping upgrades because the sandbox “feels” secure already.
- Relying only on host-level security agents that cannot see inside the sandbox.
A good rule of thumb for a first rollout: pick one clearly untrusted workload, put it in a sandbox in staging for two weeks, compare it with the regular runtime, and only then plan wider adoption.
16Real-World Usage
These ideas are not experimental. Some of the largest cloud platforms run on them every day.
Google Cloud: GKE Sandbox and serverless
gVisor started at Google, and Google Cloud offers it to Kubernetes users as GKE Sandbox, where a node pool can be enabled to run pods under gVisor. Google’s serverless container platform, Cloud Run, has also used gVisor to separate customers’ workloads. The pattern is simple: untrusted customer code, many tenants and a strong boundary at low overhead.
Amazon: Lambda and Fargate on Firecracker
Amazon built Firecracker, a minimal virtual machine monitor, to run AWS Lambda functions and Fargate tasks inside lightweight VMs. Firecracker is not Kata itself, but it follows the same philosophy of one small VM per workload, and Kata Containers can use Firecracker as one of its hypervisors. It shows that the micro VM approach scales to enormous numbers of short tasks.
Microsoft Azure: Pod Sandboxing on AKS
Azure Kubernetes Service offers a pod sandboxing feature built on Kata Containers, so that sensitive pods run in their own isolated VM on shared nodes. Microsoft has also built confidential container options on top of this approach for workloads that need protection of data while it is in use.
Ant Group and Alibaba Cloud: Kata at scale
Companies in the Alibaba ecosystem were among the early drivers of Kata Containers and have publicly discussed running sandboxed containers in production at very large scale, including for shared-tenant cloud services. Their work helped shape the performance improvements and the Rust-based runtime in the project.
Code execution and CI platforms
Platforms that run user code, such as online notebooks, coding challenge sites, build systems and AI code interpreters, commonly place each job in a gVisor or Kata sandbox. The job is short-lived, the code is unknown, and the sandbox is destroyed afterward, which is exactly the situation these runtimes were designed for.
17Frequently Asked Questions
Quick answers to the questions that come up most often.
18Summary and Key Takeaways
What to remember
- Containers share one kernel. That shared kernel is their biggest security weakness when the code inside is not fully trusted.
- A sandboxed runtime adds a second wall. It replaces runc deep in the stack, so Kubernetes, images and tooling keep working as before.
- gVisor intercepts and re-implements. Its Go-based Sentry answers most system calls itself, and a Gofer and a strict filter keep its reach to the host very small.
- Kata virtualizes. Each pod gets its own lightweight VM and guest kernel, with hardware isolation and excellent Linux compatibility, at the cost of start-up time and memory.
- RuntimeClass is the switch. Use its handler, overhead and scheduling fields together, and pair it with dedicated, tainted node pools.
- Sandboxes complement, not replace, other defenses. Keep least privilege, network policies, narrow credentials, image hygiene and fast patching.
- Choose by threat model and measurement. Sandbox the untrusted workloads, test your real application, and re-measure over time rather than trusting a single benchmark.