How We Sandbox OpenClaw Agents with Per-Tenant Docker Socket Proxies
OpenClaw agents need Docker access. They spin up sandboxed tool-execution containers, run code, process files. Without Docker, half the agent capabilities disappear.
The default approach? Mount /var/run/docker.sock into the container.
This is root access to your host. Every container on the machine. Every volume. Every network. One prompt injection and the agent owns your server.
42,665 exposed OpenClaw instances on Shodan. Most of them had this exact setup.
We had to find a better way.
The Problem: Docker Socket = Root
When you mount docker.sock into a container, you are giving that container full Docker API access. The container can:
- List and inspect every container on the host
- Create new containers with host volume mounts
- Execute commands in other containers
- Pull and run arbitrary images
- Access the host filesystem through volume mounts
- Modify networks and expose ports
For a personal dev setup, this is fine. For a multi-tenant platform where each tenant runs an autonomous AI agent? This is a catastrophe waiting to happen.
One compromised agent can:
- Escape its container by creating a new container with
-v /:/host - Read other tenants' secrets by inspecting their containers
- Modify other tenants' agents by exec-ing into their containers
- Take down the host by removing critical containers
Our Solution: Per-Tenant Docker Socket Proxy
Instead of mounting docker.sock, each tenant gets a wollomatic/socket-proxy sidecar. The proxy sits between the agent and the Docker API, filtering every request.
┌─────────────────────────────────────────────────┐
│ Tenant A │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌─────┐ │
│ │ OpenClaw │────▶│ Socket Proxy │────▶│Docker│ │
│ │ Gateway │ │ (scoped API) │ │ API │ │
│ └──────────┘ └──────────────┘ └─────┘ │
│ │
│ Can only see: openclaw-sbx-tenant-a-* │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Tenant B │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌─────┐ │
│ │ OpenClaw │────▶│ Socket Proxy │────▶│Docker│ │
│ │ Gateway │ │ (scoped API) │ │ API │ │
│ └──────────┘ └──────────────┘ └─────┘ │
│ │
│ Can only see: openclaw-sbx-tenant-b-* │
└─────────────────────────────────────────────────┘
Each proxy is configured with regex-scoped API filtering. Tenant A's proxy only allows operations on containers matching openclaw-sbx-tenant-a-*. It cannot see, inspect, or modify Tenant B's containers.
How It Works
1. Proxy Configuration
Each tenant's Docker Compose includes a socket-proxy sidecar:
docker-proxy:
image: wollomatic/socket-proxy:1
restart: unless-stopped
environment:
# Read-only by default
LOG_LEVEL: warn
# Allow container lifecycle for sandbox operations
ALLOW_START: 1
ALLOW_STOP: 1
ALLOW_RESTARTS: 1
# Regex-scope to this tenant's sandbox containers only
MATCH_CONTAINER_NAME: "openclaw-sbx-TENANT_SLUG-*"
# Allow bind mounts only from tenant workspace
ALLOW_BIND_MOUNT_FROM: "/data/tenants/TENANT_SLUG/workspace"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:2375/_ping"]
interval: 10s
timeout: 5s
retries: 3
2. Gateway Configuration
The OpenClaw gateway connects to the proxy instead of docker.sock:
gateway:
environment:
DOCKER_HOST: tcp://docker-proxy:2375
depends_on:
docker-proxy:
condition: service_healthy
No socket mount. No host access. The gateway only sees Docker through the proxy's filtered lens.
3. Network Isolation
Each tenant runs on an internal bridge network. The proxy and gateway communicate over this network. Only the gateway's Traefik labels are exposed to the external network for HTTP routing.
networks:
internal:
driver: bridge
What the Proxy Blocks
| Operation | Raw docker.sock | Socket Proxy |
|---|---|---|
| List all containers | ✅ | ❌ Only tenant's sandbox containers |
| Inspect other tenants | ✅ | ❌ Regex filter rejects |
| Create container with host volume | ✅ | ❌ Bind mount restricted to tenant workspace |
| Exec into other containers | ✅ | ❌ Container name filter blocks |
| Pull arbitrary images | ✅ | ❌ Disabled by default |
| Modify networks | ✅ | ❌ Disabled by default |
| Stop/remove other containers | ✅ | ❌ Scoped to tenant only |
The Healthcheck Problem
Early on, we hit a race condition. The OpenClaw gateway starts, tries to create a sandbox container, gets "Docker is not available", and crashes. The Docker socket proxy wasn't ready yet.
The fix was a healthcheck on the proxy sidecar:
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:2375/_ping"]
interval: 10s
timeout: 5s
retries: 3
Combined with depends_on: condition: service_healthy, the gateway waits until the proxy is actually serving requests before starting.
Simple fix. Took us a day to figure out. The gateway's error message ("Docker is not available") pointed at Docker, not at the proxy that sits in front of it.
GID Resolution on Kubernetes
On a standard Docker host, the socket proxy needs the Docker group GID to access docker.sock. We resolve it automatically:
function resolveDockerGid() {
// Try getent first (works on EC2, standard Linux)
// Fall back to DOCKER_GID env var (required in K8s — no socket/getent)
// Last resort: GID 0 (will fail with permission denied)
}
On Kubernetes, there's no docker.sock and no getent command. The GID must come from an environment variable. We set DOCKER_GID: "988" in the K8s ConfigMap. Without this, every proxy sidecar gets GID 0 and fails silently with permission denied.
What This Doesn't Solve
The socket proxy is one layer. It prevents container escape and cross-tenant access. But a complete agent sandbox also needs:
- Egress filtering — The agent can still make HTTP requests to any domain. We add domain allowlists at the network level.
- Secret encryption — API keys must be encrypted at rest, not stored in environment variables readable by
docker inspect. - Audit logging — Every agent action must be logged. The proxy doesn't log at the application level.
- Human approvals — High-risk actions (sending emails, modifying databases) need human sign-off before execution.
The socket proxy is the foundation. The rest is built on top.
Why Not Just Use Firecracker/gVisor/Sysbox?
We evaluated all three:
- Firecracker (microVM) — Strongest isolation. But operational complexity is extreme. Custom AMIs, no Docker Compose, limited ecosystem.
- gVisor — Good compromise. But adds latency to every syscall. OpenClaw agents make thousands of syscalls during tool execution.
- Sysbox — Unprivileged containers with nested Docker. Closest to what we need. But requires a custom EKS AMI and RuntimeClass configuration. On our backlog for future upgrade.
The socket proxy gives us 90% of the security benefit with 10% of the operational complexity. For a startup at our stage, that tradeoff is correct. We'll move to Sysbox when we need it.
Results
Since deploying per-tenant socket proxies in February 2026:
- Zero cross-tenant access incidents
- Zero container escape attempts (that succeeded — we log blocked API calls)
- Gateway startup reliability went from ~85% to 99.5% (healthcheck fix)
- Tenant provisioning time unchanged — the proxy adds <100ms to Docker operations
Try It
If you're self-hosting OpenClaw and want to add socket proxy isolation yourself, the key components are:
- wollomatic/socket-proxy — the proxy image
- Regex container name scoping —
MATCH_CONTAINER_NAMEenvironment variable - Bind mount restrictions —
ALLOW_BIND_MOUNT_FROMlimits volume access - Health-gated startup —
depends_on: condition: service_healthy
Or skip the infrastructure work entirely. Clawctl deploys OpenClaw with socket proxy isolation in 60 seconds.
FAQ
What is a Docker socket proxy?
A Docker socket proxy is a reverse proxy that sits between a container and the Docker API socket. Instead of giving a container full Docker access via docker.sock, the proxy filters API requests — allowing only specific operations on specific containers. This prevents container escape and cross-tenant access.
Why can't you just use Docker's built-in security?
Docker's security model assumes trust within the Docker API. Any container with docker.sock access can control all other containers. Docker does not support per-container API scoping natively. The socket proxy adds this missing layer.
Is this the same as rootless Docker?
No. Rootless Docker runs the Docker daemon as a non-root user, reducing host-level privilege escalation. The socket proxy is orthogonal — it scopes API access at the request level. You can use both together for defense in depth.
How does this compare to Kubernetes pod security?
Kubernetes has PodSecurityPolicies and SecurityContexts that restrict pod capabilities. The socket proxy serves a similar purpose but works with Docker Compose, which is how OpenClaw deploys sandbox containers. On Kubernetes, we combine the socket proxy with pod-level security policies.
What happens if the socket proxy crashes?
The OpenClaw gateway loses Docker access and cannot create sandbox containers. Tool execution fails gracefully — the agent reports that Docker is unavailable. Our auto-recovery cron detects the unhealthy state and restarts the proxy within 5 minutes.