Skip to content

Instantly share code, notes, and snippets.

@ioggstream
Created May 22, 2026 16:13
Show Gist options
  • Select an option

  • Save ioggstream/54a630d28f210f52539a53430f8811e0 to your computer and use it in GitHub Desktop.

Select an option

Save ioggstream/54a630d28f210f52539a53430f8811e0 to your computer and use it in GitHub Desktop.
Quickstart for docker compose + runc.

Container Security: gVisor (runsc) Adoption Guide

A short guide to sandboxing dev containers (e.g. an agent like Claude Code, or untrusted test runs) with gVisor under Docker / Docker Compose.

What runsc secures - and what it doesn't

runsc interposes a user-space "Sentry" kernel between the container and the host. It intercepts syscalls and re-implements a subset of the Linux 4.4 ABI in Go, so a compromised container cannot directly invoke host syscalls.

Threat Mitigated by runsc?
Linux kernel exploit via unusual syscall Yes - Sentry is the only target
Container escape via /proc, /sys, capabilities Yes - stripped, virtualised
Accidental damage outside the bind mount Yes - host FS not visible
Untrusted code execution inside the container Yes - same kernel boundary
Application-level bugs (SQLi, SSRF, deserialisation) No - same app code runs
Supply-chain compromise of the image No - image is trusted by you
Host network exfiltration (with --network=host) No - host stack is shared
GPU / KVM passthrough / --privileged workloads Not supported (will fail)
Heavy syscall- or I/O-bound workloads Works, but noticeably slower

Reported kernel is always a synthetic 4.4.0, regardless of host - useful as a sandbox fingerprint, irrelevant for security.

Adoption in three steps

1. Install runsc on the host

Linux ≥ 4.14.77 required. KVM (/dev/kvm) recommended; falls back to ptrace otherwise (slower, works rootless).

apt-get install -y runsc
sudo runsc install

2. Register the runtime with Docker

This should be added automatically by runsc install, just verify that...

cat /etc/docker/daemon.json:

{
  "runtimes": {
    "runsc": {
      "path": "/usr/local/bin/runsc",
      "runtimeArgs": ["--network=host"]
    }
  }
}

sudo systemctl restart docker, then verify:

docker run --rm --runtime=runsc alpine dmesg | head -1   # "Starting gVisor..."

To sandbox every container by default, add "default-runtime": "runsc".

3. Opt-in from docker-compose.yml

services:
  claude:
    image: my-claude-sandbox:latest
    runtime: runsc
    volumes:
      - ./:/workspace
    working_dir: /workspace

Only services with runtime: runsc are sandboxed; the rest keep using runc.

Recognising a gVisor sandbox from inside

dmesg | head -1            # Starting gVisor...
cat /proc/version          # Linux version 4.4.0 ...
ls /sys/firmware 2>&1      # absent - gVisor exposes a stripped /sys

The fixed 4.4.0 kernel version is the most reliable signal.

Docker DNS resolution caveat

gVisor's default --network=sandbox uses its own user-space network stack (netstack). Docker's embedded DNS server at 127.0.0.11:53 relies on iptables DNAT rules that netstack does not honour the same way, so Compose service-name resolution silently fails: getent hosts db returns nothing even though db is in the same project.

Pick one fix:

  1. --network=host in runtimeArgs (used in step 2 above). Network stack passes through to the host; syscall/FS/process isolation is preserved. Lose: network-stack isolation. Recommended for dev sandboxing.
  2. Static IPs + extra_hosts:. Keep --network=sandbox; declare each service's IP in the compose network and add hostname entries so resolution happens via /etc/hosts, not DNS.
  3. External DNS via dns:. Fixes resolution of pypi.org etc., but not of Compose service names - those only exist in Docker's embedded resolver.

Quick diagnosis from inside the container:

getent hosts db          # compose service name
getent hosts pypi.org    # external
cat /etc/resolv.conf

If only the service name fails, you are hitting the netstack/embedded-DNS gap - apply fix 1 or 2.

Default runtime

Opting in to runsc as the default runtime may cause some existing container workloads to fail due to unsupported features (e.g. --privileged).

The runtimeArgs: ["--network=host"] relaxes a networking constraint that would otherwise break the internal Docker Compose DNS resolution of service names (see above).

    "default-runtime": "runsc",
    "runtimes": {
        "runsc": {
            "path": "/usr/bin/runsc",
            "runtimeArgs": ["--network=host"]
        }
    }

When not to use runsc

  • Workloads that legitimately need --privileged, raw sockets, GPU, or nested virtualisation.
  • Hot paths doing heavy I/O against bind mounts (use a named volume or tmpfs, or stay on runc).
  • Threat models that include hostile kernel-network exploits and service-to-service DNS - these conflict; consider Firecracker microVMs instead.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment