GumpboxGumpbox
DemosPricingBlog
Back to Blog
Back to all articles
Engineering

Why Our Agent Sandboxes Run on Docker + gVisor

The full architecture behind Gumpbox sandboxes: what the gVisor boundary actually is, why microVMs don't fit the servers you already own, and where the design is honestly weak.

August 17, 2026
11 min read

Why Our Agent Sandboxes Run on Docker + gVisor

Every AI agent sandbox answers one question: what happens when the agent is wrong? Not philosophically wrong — rm -rf in the wrong directory wrong. Leaked a .env wrong. Curled a malicious install script wrong.

Gumpbox's answer is a disposable Linux environment that runs on the server you already own, built on two pieces of boring technology: Docker and gVisor. This post is the full architecture — what the gVisor boundary actually is, why we didn't build on microVMs like everyone else seems to be doing, how a sandbox is constructed end to end, and the parts of the design we'd defend and the parts we wouldn't.

The constraints choose the architecture

Gumpbox manages your servers. Not our cloud — your VPS, your EC2 instances, the Hetzner box running three things, the homelab machine. Any sandbox we ship has to live inside that reality:

ConstraintImplication
Hosts are heterogeneous — any distro, x86_64 and arm64No assumptions about kernel features, packages, or architecture
Most servers are VMs (EC2, VPS)No /dev/kvm, so no nested virtualization, so no microVMs
Docker is the one thing almost everyone already runsBuild on the existing daemon; don't replace it
The server is doing other workNo resident agents, no new daemons, reversible changes

That last column eliminates more options than you'd think. It eliminates anything that needs a hypervisor. It eliminates anything that replaces your container runtime. It eliminates anything that installs a permanent control daemon.

What's left is a short list, and gVisor sits at the top of it.

What gVisor actually is

gVisor is not "stricter Docker." It's a reimplementation of the Linux kernel's syscall interface, running as an ordinary userspace process. Google built it and runs it in production for exactly this problem — sandboxing untrusted code at scale.

The piece that matters is called the sentry. When a process inside a gVisor sandbox calls open(), fork(), or socket(), that syscall never reaches your host kernel. It's intercepted and answered by the sentry's own implementation:

  • File operations are served by a broker process (the gofer) that holds the actual host file descriptors.
  • Network operations run on netstack, a userspace TCP/IP stack. The sandbox has no direct access to host networking machinery.
  • Everything else — process management, pipes, signals, memory mapping — is implemented in the sentry itself.

This changes the security math in a specific way. Escaping a normal container puts you in contact with the host kernel: namespaces and seccomp filters narrow the attack surface, but the kernel is still answering the syscalls, and kernel 0-days are a steady drumbeat. Inside gVisor, a syscall bug class aimed at the host kernel's implementation simply doesn't apply — the syscall is served by a much smaller reimplementation. A container escape lands the attacker in the sentry, a Go process with no host privileges, not in kernel context.

How syscalls get intercepted

The interception mechanism is called the platform. Three exist:

  • systrap — syscalls are blocked with seccomp and delivered as signals to the sentry. It has been gVisor's default since 2023, works everywhere, and — counterintuitively — performs best inside virtual machines, because it doesn't depend on hardware virtualization extensions at all.
  • KVM platform — the sentry uses hardware virtualization extensions (/dev/kvm) for faster address-space switches. Best on bare metal; on cloud VMs, nested-virtualization overhead often makes it slower than systrap.
  • ptrace — the legacy mechanism. Deprecated upstream.

So the platform choice inverts depending on where you run: bare metal wants KVM, cloud VMs want systrap. Gumpbox sandboxes mostly live on cloud VMs, so the default is the right answer — which is a nice property, because it means there's nothing to tune.

What it costs

gVisor intercepts every syscall, so syscall-heavy workloads (heavy find-style filesystem walks, some benchmarks) pay measurably. Interactive shells, builds, git, and HTTP — the shape of agent workloads — pay little. We enforce per-sandbox CPU and memory caps and per-direction network rate limits on top, so a runaway agent burns its own budget, not your server.

The option space, honestly scored

ApproachBoundaryOwn kernel?Runs on your servers?Verdict
No sandboxNone—✅One typo from disaster
Plain Docker containerNamespaces + seccomp❌✅Escape = host kernel contact
Docker socket mount / DinDPrivileged or socket❌✅Worse than nothing — hands over the host
WASM isolatesV8/Wasm runtime❌✅Not an OS: no apt install, no shells, no tooling
MicroVM (Firecracker, Kata, Cloud Hypervisor)Hardware hypervisor✅❌ mostlyStrongest boundary; needs /dev/kvm
Docker + gVisorUserspace kernel❌✅Strongest boundary that runs everywhere

The microVM question

MicroVMs are genuinely the stronger isolation primitive — a hypervisor wall and a dedicated kernel per sandbox is more than a userspace kernel can promise. Firecracker powers AWS Lambda; Kata pairs KVM with real VMs; Docker's new Sandboxes product built a custom VMM on the same idea. We didn't choose gVisor because microVMs are bad. We chose it because they mostly can't run where our users are:

  1. Every microVM stack needs /dev/kvm. Standard EC2 instances, most VPSes, and all Graviton machines don't expose it. As of February 2026, AWS offers nested virtualization on virtual instances — but only on recent Intel generations (m7i/c7i/r7i, m8i and siblings), opt-in per instance with an instance stop. Real fleets are mostly t-series, m5/m6, and Graviton. A sandbox that requires an instance migration isn't a feature.
  2. Firecracker itself targets cloud infrastructure hosts — Linux with KVM. Docker's own architecture post on Sandboxes rejected it for exactly this class of reason: their product runs on laptops, where KVM doesn't exist outside Linux.
  3. MicroVM overhead is real. A VM with its own kernel and daemon per session costs memory and boot time, and filesystem passthrough into VMs has known performance teeth. On a $6 VPS running three services, that's the wrong trade.

And note where the microVM penalty lands heaviest: cloud VMs — precisely where nested virt is weakest. If you do have bare metal, gVisor's KVM platform gets you most of the way there without changing the architecture at all.

Docker Sandboxes, to be clear, is a good product — for a different deployment shape. It sandboxes coding agents on the developer's own laptop, which is why it needs its own cross-platform VMM. Our sandboxes run on Linux servers reached over SSH. Different problem, different answer.

How a Gumpbox sandbox is built

Concretely, end to end:

Runtime registration. Host setup installs the runsc binary (arch-detected) and registers it as an additional runtime in the Docker daemon:

{
  "runtimes": {
    "runsc": { "path": "/usr/local/bin/runsc" }
  }
}

runc stays the default. Every container already on the server is untouched. Remove the entry and the host is exactly as it was.

The sandbox itself is one container:

docker run -d --name agent-sandbox-7f3a \
  --runtime=runsc \
  --cpus 2 --memory 2048m \
  -p 2222:22 \
  -v agent-sandbox-7f3a-workspace:/workspace \
  gumpbox/base:latest

Published port for SSH (and optionally a web app), resource caps, and a Docker volume for /workspace. Outbound networking is the standard Docker bridge — pip install, git clone, and npm i work on the first boot, because a sandbox without network is a glorified calculator.

Self-healing SSH. The container's entrypoint is PID 1: it runs an idempotent bootstrap script on every start (fast-path if sshd is already up; fast-path if the binary exists; otherwise install via whatever package manager the base image has), then execs the sandbox command. sshd heals itself even if nothing outside the container ever intervenes.

Persistence, split deliberately. /workspace survives stop/start/restart — it's a volume. Everything else is ephemeral by design: installed packages, /tmp, process state all reset on restart. We think the everything-persists model ages badly (disk grows until someone notices); clean-slate-with-a-workspace is the right default, and snapshot-to-image covers repeatable environments when you want them.

Non-custodial keys. This part we're proud of. SSH access uses Ed25519 keys that the host generates, and the private key is emitted once, on stdout, riding the SSH channel that's already encrypting your session — then wiped from the host. The server never stores sandbox private keys; they live in your Mac's Keychain; they never appear in sandbox records, state files, share links, or audit logs (key output is redacted there). Access grants are revocable per sandbox, and grants survive restarts; private keys never touch the server's disk.

The agent surface. Sandboxes are first-class MCP resources, so any MCP client operates them:

{
  "resource": "sandbox",
  "action": "create",
  "params": { "name": "rebuild-env", "cpu": 2, "mem_mb": 2048 }
}

create, start, stop, restart, destroy, execute_command, open_terminal, provision_key, revoke_key — with approvals and a full activity audit in the app. The terminal is a real SSH session to the sandbox port, tunneled through your server with the same connection stack everything else uses. No agent daemon lives on the host; the helper that drives Docker runs over SSH and exits.

The egress question

The decision we get asked about most: sandbox egress is always on. No per-sandbox allowlists today. That's deliberate, and the reasoning is worth writing down because every alternative failed for a concrete reason:

  1. In-container filtering is impossible under gVisor. gVisor's netstack has no netfilter — iptables and nft both fail with Protocol not supported. Not hard; impossible.
  2. Host-side iptables on DOCKER-USER works until it doesn't. Orphan rules after daemon restarts, root required, gone on reboot. Fragile filtering is worse than honest openness.
  3. Turning egress off breaks inbound too. Published ports need bidirectional TCP; a SYN-ACK is egress. --network=none-style isolation bricks the SSH port you need.
  4. Egress filtering doesn't stop exfiltration anyway. An agent with secrets in process can write them into command output, MCP responses, or files in /workspace. The channel out doesn't need the network.
  5. A sandbox that can't reach package registries is a sandbox nobody uses — and an unused sandbox protects nothing.

The strongest counterexample is Docker Sandboxes again: all egress crosses a host-side proxy that enforces deny-by-default allowlists and injects auth headers at request time, so raw API keys never enter the VM. That's a genuinely better secret model, and it's built the only way that works — a userspace proxy, not firewall rules. A proxy like that is the compatible evolution for us too: it needs no kernel features, no /dev/kvm, and no iptables, so it could ship as an opt-in per sandbox without disturbing the always-on default. It's the most likely thing to change in this architecture.

What the boundary buys — and what it doesn't

ThreatCovered by
Host kernel compromise via malicious syscallsSentry intercepts; host kernel never answers sandbox syscalls
Container escapeEscape lands in the sentry — an unprivileged process, not the kernel
Raw sockets / packet craftingnetstack exposes no raw socket path
Runaway resource useCPU/memory caps + per-direction rate limits
Secret persistence on the serverNon-custodial keys: emitted once, wiped, Keychain-only

Not covered, in plain terms:

  • Secrets the agent legitimately holds can leave through output channels. That's an application-layer problem, and our control there is the MCP approval and audit layer, not the sandbox.
  • Hardware-adjacent side channels — a microVM's hardware boundary is stronger there. That's the real isolation gap, and it's the price of running on hosts without KVM.
  • The human layer — prompt injection and social engineering are not sandbox problems. The approval surface is the defense.

We'd rather publish the gaps than let anyone assume a sandbox is a substitute for consent, audit, and least privilege. It's one layer of several.

It runs right in your stack

The part we optimized hardest: there is nothing new to run.

  • The only dependency is Docker, which the server already has. runsc installs beside runc — your existing containers never change behavior.
  • Standard primitives throughout: OCI images, docker inspect works, SSH keys you can rotate, MCP tools any client speaks.
  • Works on x86_64 and arm64, any distro, VM or metal — EC2, DigitalOcean, Hetzner, Oracle, the homelab. No KVM, no nested virtualization, no instance migration.
  • The engine layer is a protocol in our codebase. If a future tier runs Kata on the KVM-capable minority of hosts, it slots in without touching the key custody, sharing, terminal, or web-app surfaces that sit above it.

The short version

Boundary choice is deployment-shape choice. If your product sandboxes agents on laptops, build a microVM — that's what Docker did, correctly. If your product puts sandboxes on the Linux servers people already run, the strongest boundary that actually deploys is Docker + gVisor: a userspace kernel under every sandbox, zero new daemons, zero KVM requirements, reversible setup, standard interfaces all the way down.

It's the boring answer. Boring was the requirement.

Download Gumpbox on the App Store — free to download, sandboxes included, one-time $19.99 PRO for unlimited servers.

Ready to simplify your Linux server management?

Gumpbox makes server administration effortless with an intuitive interface designed for developers.

Get Started
© 2025 Gumpbox. All rights reserved.