Docker — Runtime Security (gVisor and Kata Containers)¶
🎯 Introduction¶
Hardening an image is not enough if the container shares the host kernel directly. With the default runtime (runc), a container is just another host process isolated by namespaces, cgroups, and seccomp: any kernel isolation failure (a privilege-escalation CVE, a poorly filtered syscall) can turn into an escape to the host and its neighbors.
Runtime isolation (sandboxing) tackles that problem by placing a layer between the container and the real kernel. Here we cover the two mature solutions: gVisor (a userspace kernel) and Kata Containers (containers inside microVMs). We do not repeat image hardening, secrets, or scanning; for that see Docker — Security and Scanning, the natural companion to this guide.
What this doc does NOT cover
Dockerfile hardening, non-root users, secret management, and vulnerability scanning. All of that lives in docker_security.md. Here we talk only about kernel isolation at runtime.
🧠 The shared-kernel problem¶
With runc, every container on the host shares a single Linux kernel. The attack surface is huge: hundreds of syscalls, drivers, filesystems, and networking subsystems. seccomp shrinks that surface, but it is still the same kernel for everyone.
graph TB
subgraph runc["runc (default)"]
C1[Container A] --> K1[Host kernel]
C2[Container B] --> K1
K1 --> HW1[Hardware]
end
subgraph gvisor["gVisor / runsc"]
C3[Container A] --> S1["Sentry<br/>(userspace kernel)"]
C4[Container B] --> S2["Sentry<br/>(userspace kernel)"]
S1 --> K2[Host kernel<br/>very limited syscalls]
S2 --> K2
K2 --> HW2[Hardware]
end
subgraph kata["Kata Containers"]
C5[Container A] --> VM1["microVM<br/>(own guest kernel)"]
C6[Container B] --> VM2["microVM<br/>(own guest kernel)"]
VM1 --> HYP[Hypervisor<br/>QEMU / Cloud Hypervisor]
VM2 --> HYP
HYP --> K3[Host kernel]
K3 --> HW3[Hardware]
end
- gVisor intercepts the container's syscalls and serves them from a kernel reimplemented in Go (the Sentry), which only makes a handful of real syscalls to the host.
- Kata boots each container (or pod) inside a lightweight microVM with its own guest kernel, isolated by hardware virtualization.
🔍 gVisor (runsc)¶
gVisor implements an application kernel in userspace. The Sentry process intercepts the container's syscalls (via ptrace or, by default, the systrap/KVM platform) and handles them without passing them to the real kernel. Filesystem access goes through a separate process, the Gofer, over the 9P protocol.
Installation¶
# Download runsc and its containerd shim
(
set -e
ARCH=$(uname -m)
URL=https://storage.googleapis.com/gvisor/releases/release/latest/${ARCH}
wget ${URL}/runsc ${URL}/runsc.sha512 \
${URL}/containerd-shim-runsc-v1 ${URL}/containerd-shim-runsc-v1.sha512
sha512sum -c runsc.sha512 -c containerd-shim-runsc-v1.sha512
rm -f *.sha512
chmod a+rx runsc containerd-shim-runsc-v1
sudo mv runsc containerd-shim-runsc-v1 /usr/local/bin
)
Configuration with Docker¶
# Register runsc as a Docker runtime
sudo runsc install
sudo systemctl reload docker
This adds the runtime to /etc/docker/daemon.json:
{
"runtimes": {
"runsc": {
"path": "/usr/local/bin/runsc"
}
}
}
Run a container with gVisor:
docker run --rm --runtime=runsc hello-world
# Check the kernel is the Sentry's, not the host's
docker run --rm --runtime=runsc alpine dmesg | head
# -> shows "Starting gVisor..." instead of the real kernel
Configuration with containerd¶
In /etc/containerd/config.toml:
version = 2
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"
sudo systemctl restart containerd
Debugging gVisor
Enable per-sandbox logs with runsc --debug --debug-log=/tmp/runsc/. It is the fastest way to spot an unsupported syscall that breaks an application.
🔍 Kata Containers (microVMs)¶
Kata boots each pod inside a lightweight VM using a hypervisor (QEMU by default, or Cloud Hypervisor / Firecracker). The container sees a full guest kernel, so isolation is guaranteed by hardware virtualization (VT-x/AMD-V), not just by software.
Requirements¶
# Verify hardware virtualization support
kata-runtime check
# or, manually:
egrep -c '(vmx|svm)' /proc/cpuinfo # > 0 = supported
Nested virtualization
On cloud VMs, Kata with QEMU needs nested virtualization enabled by the provider. Without it, it boots via software emulation (much slower) or fails. Firecracker and Cloud Hypervisor have the same KVM requirements.
Installation¶
# Via kata-manager (official script: installs binaries + config)
bash -c "$(curl -fsSL https://raw.githubusercontent.com/kata-containers/kata-containers/main/utils/kata-manager.sh)"
Binaries land in /opt/kata/bin/ and the config in /opt/kata/share/defaults/kata-containers/configuration.toml.
Configuration with containerd¶
version = 2
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata]
runtime_type = "io.containerd.kata.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata.options]
ConfigPath = "/opt/kata/share/defaults/kata-containers/configuration.toml"
sudo systemctl restart containerd
Direct test with ctr:
sudo ctr images pull docker.io/library/alpine:latest
sudo ctr run --runtime io.containerd.kata.v2 --rm \
docker.io/library/alpine:latest kata-test uname -r
# The kernel shown is Kata's guest kernel, different from the host's
Lighter hypervisor
For faster boots and lower footprint, switch to Cloud Hypervisor or Firecracker in configuration.toml ([hypervisor.clh] / [hypervisor.fc]). Firecracker prioritizes density and boot time; QEMU prioritizes device compatibility.
☸️ Kubernetes integration (RuntimeClass)¶
Both runtimes are exposed to Kubernetes through the RuntimeClass resource. The handler must match the runtime name configured in containerd.
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc # matches [...runtimes.runsc]
---
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: kata
handler: kata # matches [...runtimes.kata]
Assign a pod to a sandboxed runtime:
apiVersion: v1
kind: Pod
metadata:
name: untrusted-workload
spec:
runtimeClassName: gvisor # or "kata"
containers:
- name: app
image: nginx:1.27-alpine
Dedicated nodes
It is common to label/taint nodes that have the runtime installed and let the RuntimeClass select them via scheduling.nodeSelector/tolerations, so you don't require gVisor or Kata on every node in the cluster.
# RuntimeClass fragment scheduling onto labeled nodes
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: kata
handler: kata
scheduling:
nodeSelector:
katacontainers.io/kata-runtime: "true"
tolerations:
- key: "kata"
operator: "Exists"
effect: "NoSchedule"
⚖️ Comparison: runc vs gVisor vs Kata¶
| Criterion | runc (default) | gVisor (runsc) | Kata Containers |
|---|---|---|---|
| Isolation mechanism | Namespaces + cgroups + seccomp | Userspace kernel (Sentry) | microVM with its own guest kernel |
| Syscall surface to host | Wide (mitigable with seccomp) | Very small | Only the hypervisor's |
| Kernel | Shared with the host | Reimplemented (Go) | Independent Linux guest kernel |
| Boot overhead | Minimal | Low | Medium (VM boot) |
| I/O / syscall overhead | Native | Noticeable on I/O-heavy loads | Low-medium (virtio) |
| Syscall compatibility | Full | Partial (implemented subset) | Full (real kernel) |
| Requires HW virtualization | No | No | Yes (VT-x/AMD-V or nested) |
| GPU / device access | Direct | Limited | Via passthrough (more complex) |
| Ideal use case | Trusted workloads | Multi-tenant, untrusted code | Strong VM-grade isolation |
🧭 When to use each¶
Rule of thumb
- runc: your own trusted workloads where performance is king.
- gVisor: running untrusted or multi-tenant code (serverless functions, CI sandboxes, user code execution) with fast boot, accepting some I/O overhead and partial syscall compatibility.
- Kata: you need VM-grade isolation, full syscall compatibility, or strict compliance, and you can pay the hypervisor cost and require hardware virtualization.
⚠️ Limitations¶
gVisor
- Does not implement 100% of syscalls: apps using exotic kernel interfaces may fail or degrade.
- Noticeable overhead on network- or disk-heavy I/O due to the Sentry/Gofer indirection.
- Limited support for GPUs and special devices.
Kata Containers
- Requires hardware virtualization; in the cloud,
nested virtualizationenabled. - Higher memory usage per microVM and slower boot than runc/gVisor.
- Device passthrough (GPU, SR-IOV networking) is more complex to configure.
🔗 Related links¶
- Docker — Security and Scanning — image hardening, secrets, and scanning (companion to this guide).
- Docker — Base
- Docker — Optimizations
- gVisor — Official documentation
- Kata Containers — Official documentation
- Kubernetes — RuntimeClass