gVisor is a user-space kernel that implements a significant portion of the Linux kernel surface using Go. Unlike Docker which shares the host kernel, gVisor intercepts application system calls and acts as a protective barrier between the application and the host kernel.
Example - gVisor emulated kernel:
# gVisor reports an emulated kernel version (typically 4.4.x)
# NOT the host kernel version - this confirms isolation
$ docker run --runtime=runsc --rm alpine uname -r
4.4.0
$ uname -r # On host (different!)
6.8.0-...Note for Multipass/ARM64 users: gVisor binaries are architecture-specific. The install script auto-detects aarch64 vs x86_64.
Key Benefits:
- User-space kernel (intercepts syscalls)
- Prevents kernel exploits (CVE protection)
- Strong isolation (better than Docker alone)
- Compatible with standard containers
Different container runtimes offer different security and performance tradeoffs. For multi-tenant sandbox environments where security is paramount, gVisor provides the best balance of strong isolation with reasonable performance overhead.
| Feature | Docker (runc) | gVisor (runsc) | Kata Containers |
|---|---|---|---|
| Isolation | Namespace | User-space kernel | Lightweight VM |
| Performance | 5/5 stars | 4/5 stars | 3/5 stars |
| Security | 3/5 stars | 5/5 stars | 5/5 stars |
| Overhead | Low | Medium | High |
| Best For | General use | Multi-tenant SaaS | Extreme isolation |
The following steps install gVisor on your host system. This needs to be done once per host before running sandboxed containers.
# Install gVisor runtime (auto-detects architecture)
(
set -e
ARCH=$(uname -m)
# Map architecture names
if [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
GVISOR_ARCH="aarch64"
else
GVISOR_ARCH="x86_64"
fi
echo "Downloading gVisor for $GVISOR_ARCH..."
URL=https://storage.googleapis.com/gvisor/releases/release/latest/${GVISOR_ARCH}
wget ${URL}/runsc ${URL}/runsc.sha512
sha512sum -c runsc.sha512
rm -f *.sha512
chmod a+rx runsc
sudo mv runsc /usr/local/bin
)Important: Using the wrong architecture binary (e.g., x86_64 on ARM64/M1 Macs) will fail with exec format error.
After installation, configure Docker to recognize gVisor as a valid runtime. This allows you to use --runtime=runsc with Docker commands.
File: /etc/docker/daemon.json
{
"runtimes": {
"runsc": {
"path": "/usr/local/bin/runsc",
"runtimeArgs": ["--platform=ptrace", "--network=none"]
}
},
"default-runtime": "runsc"
}Restart Docker:
After modifying the daemon configuration, restart Docker to apply the changes. The restart will temporarily stop running containers, so plan this during a maintenance window.
sudo systemctl restart dockerFor Kubernetes deployments using containerd as the CRI runtime, configure gVisor as a runtime class. This allows Pods to request gVisor isolation via runtimeClassName: gvisor.
File: /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc.options]
TypeUrl = "io.containerd.runsc.v1.options"
ConfigPath = "/etc/containerd/runsc.toml"File: /etc/containerd/runsc.toml
[runsc_config]
platform = "ptrace"
network = "none"
debug = false
debug-log = "/var/log/runsc/%ID%/"
strace = false
file-access = "exclusive"
overlay = false
fsgofer-host-uds = true
# Disable direct access
vfs2 = true
directfs = falseThese flags control gVisor's runtime behavior. Choose based on your environment: ptrace for compatibility, KVM for maximum isolation.
--platform=ptrace # Use ptrace for syscall interception (more compatible)
--platform=kvm # Use KVM for better isolation (if available)
--network=none # Disable default networking
--file-access=exclusive # Exclusive file access (no shared mounts)
--overlay=false # Disable overlay filesystem
--directfs=false # Disable direct filesystem accessAfter installation, verify that gVisor is working correctly and actually filtering system calls. These tests confirm the security layer is active.
# Test gVisor installation
docker run --runtime=runsc --rm hello-world
# Check gVisor is running
docker run --runtime=runsc --rm alpine uname -r
# Should show: 4.4.0 (or similar gVisor emulated kernel)
# NOT the same as host kernel (use `uname -r` on host to compare)
# Verify syscall filtering
docker run --runtime=runsc --rm alpine cat /proc/kallsyms 2>&1
# Should fail or show limited entries (gVisor filters kernel access)
# Check kernel version differs from host (confirms gVisor isolation)
HOST_KERNEL=$(uname -r)
CONTAINER_KERNEL=$(docker run --runtime=runsc --rm alpine uname -r)
echo "Host: $HOST_KERNEL"
echo "Container: $CONTAINER_KERNEL"
# Should be DIFFERENT valuesThese examples show the difference between insecure and secure configurations. Never use the default Docker runtime for untrusted code - it shares the host kernel and provides no real isolation.
Don't use default Docker runtime for multi-tenant workloads
# INSECURE - shares host kernel
docker run ubuntu bashDo use gVisor for isolation
# SECURE - isolated user-space kernel
docker run --runtime=runsc ubuntu bash