Run agent shell commands inside an isolated Docker container so untrusted code never touches your host.
When sandbox mode is enabled, every exec or shell tool call is routed into a Docker container instead of running directly on the host. The container is ephemeral, network-isolated, and heavily restricted by default — dropped capabilities, read-only root filesystem, tmpfs for /tmp, and a 512 MB memory cap.
If Docker is unavailable at runtime, GoClaw falls back to host execution and logs a warning.
graph LR
Agent -->|exec tool call| ExecTool
ExecTool -->|sandbox enabled| DockerManager
DockerManager -->|Get or Create| Container["Docker Container\ngoclaw-sbx-*"]
Container -->|docker exec| Command
Command -->|stdout/stderr| ExecTool
ExecTool -->|result| Agent
ExecTool -->|Docker unavailable| HostExec["Host exec\n(fallback)"]
Set GOCLAW_SANDBOX_MODE (or sandbox.mode in config) to one of:
| Mode | Which agents are sandboxed |
|---|---|
off |
None — all commands run on host (default) |
non-main |
All agents except main and default |
all |
Every agent |
Scope controls how containers are reused across requests:
| Scope | Container lifetime | Best for |
|---|---|---|
session |
One container per session | Maximum isolation (default) |
agent |
One container shared across all sessions for an agent | Persistent state within an agent |
shared |
One container for all agents | Lowest overhead |
Out of the box, every sandbox container runs with:
| Setting | Value |
|---|---|
| Root filesystem | Read-only (--read-only) |
| Capabilities | All dropped (--cap-drop ALL) |
| New privileges | Blocked (--security-opt no-new-privileges) |
| tmpfs mounts | /tmp, /var/tmp, /run |
| Network | Disabled (--network none) |
| Memory limit | 512 MB |
| CPUs | 1.0 |
| Execution timeout | 300 seconds |
| Max output | 1 MB (stdout + stderr combined) |
| Container prefix | goclaw-sbx- |
| Working directory | /workspace |
If a command produces more than 1 MB of output, the output is truncated and ...[output truncated] is appended.
All settings can be provided as environment variables or in config.json under a sandbox key.
GOCLAW_SANDBOX_MODE=all
GOCLAW_SANDBOX_IMAGE=goclaw-sandbox:bookworm-slim
GOCLAW_SANDBOX_WORKSPACE_ACCESS=rw # none | ro | rw
GOCLAW_SANDBOX_SCOPE=session # session | agent | shared
GOCLAW_SANDBOX_MEMORY_MB=512
GOCLAW_SANDBOX_CPUS=1.0
GOCLAW_SANDBOX_TIMEOUT_SEC=300
GOCLAW_SANDBOX_NETWORK=false{
"sandbox": {
"mode": "all",
"image": "goclaw-sandbox:bookworm-slim",
"workspace_access": "rw",
"scope": "session",
"memory_mb": 512,
"cpus": 1.0,
"timeout_sec": 300,
"network_enabled": false,
"read_only_root": true,
"cap_drop": ["ALL"],
"tmpfs": ["/tmp", "/var/tmp", "/run"],
"max_output_bytes": 1048576,
"idle_hours": 24,
"max_age_days": 7,
"prune_interval_min": 5
}
}| Field | Type | Default | Description |
|---|---|---|---|
mode |
string | off |
off, non-main, or all |
image |
string | goclaw-sandbox:bookworm-slim |
Docker image to use |
workspace_access |
string | rw |
Mount workspace as none, ro, or rw |
scope |
string | session |
Container reuse: session, agent, or shared |
memory_mb |
int | 512 | Memory limit in MB |
cpus |
float | 1.0 | CPU quota |
timeout_sec |
int | 300 | Per-command timeout in seconds |
network_enabled |
bool | false | Enable container networking |
restricted_domains |
string[] | — | Allowed domains when network is enabled |
read_only_root |
bool | true | Mount root filesystem read-only |
cap_drop |
string[] | ["ALL"] |
Linux capabilities to drop |
tmpfs |
string[] | /tmp, /var/tmp, /run |
Writable tmpfs mounts |
tmpfs_size_mb |
int | 0 | Default size for tmpfs mounts (0 = Docker default) |
pids_limit |
int | 0 | Max PIDs in container (0 = unlimited) |
user |
string | — | Container user, e.g. 1000:1000 or nobody |
max_output_bytes |
int | 1048576 | Max stdout+stderr capture per exec (1 MB) |
setup_command |
string | — | Shell command run once after container creation |
container_prefix |
string | goclaw-sbx- |
Prefix for container names |
workdir |
string | /workspace |
Container working directory |
idle_hours |
int | 24 | Prune containers idle longer than N hours |
max_age_days |
int | 7 | Prune containers older than N days |
prune_interval_min |
int | 5 | Background prune check interval (minutes) |
The workspace directory is mounted at /workspace inside the container:
none— no filesystem mount; container has no access to your project filesro— read-only mount; agent can read files but cannot writerw— read-write mount (default); agent can read and write project files
- Creation — on first exec call for a scope key,
docker run -d ... sleep infinitystarts a long-lived container. - Execution — each command runs via
docker execinside the running container. - Pruning — a background goroutine checks every
prune_interval_minminutes and destroys containers that have been idle longer thanidle_hoursor exist longer thanmax_age_days. - Destruction —
docker rm -f <id>is called on pruning, session end, orReleaseAllat shutdown.
Container names follow the pattern goclaw-sbx-<sanitized-scope-key>, where the scope key is derived from the session key, agent ID, or "shared" depending on the configured scope.
Build the sandbox image first:
docker build -t goclaw-sandbox:bookworm-slim -f Dockerfile.sandbox .Then add the sandbox overlay to your compose command:
docker compose \
-f docker-compose.yml \
-f docker-compose.postgres.yml \
-f docker-compose.sandbox.yml \
upThe docker-compose.sandbox.yml overlay mounts the Docker socket and sets sandbox environment variables:
services:
goclaw:
build:
args:
ENABLE_SANDBOX: "true"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- GOCLAW_SANDBOX_MODE=all
- GOCLAW_SANDBOX_IMAGE=goclaw-sandbox:bookworm-slim
- GOCLAW_SANDBOX_WORKSPACE_ACCESS=rw
- GOCLAW_SANDBOX_SCOPE=session
- GOCLAW_SANDBOX_MEMORY_MB=512
- GOCLAW_SANDBOX_TIMEOUT_SEC=300
- GOCLAW_SANDBOX_NETWORK=false
group_add:
- ${DOCKER_GID:-999}Security note: Mounting the Docker socket gives the GoClaw container control over the host Docker daemon. Only use sandbox mode in environments where you trust the GoClaw process itself.
GOCLAW_SANDBOX_MODE=non-mainThe main and default agents run commands on the host. All other agents (sub-agents, specialized workers) are sandboxed.
{
"sandbox": {
"mode": "all",
"workspace_access": "ro",
"setup_command": "pip install -q pandas numpy",
"memory_mb": 1024,
"timeout_sec": 120
}
}The setup_command runs once after the container is created. Use it to pre-install dependencies so they are available on every subsequent exec.
GET /v1/sandbox/stats{
"mode": "all",
"image": "goclaw-sandbox:bookworm-slim",
"active": 3,
"containers": {
"agent:session-abc123": "a1b2c3d4e5f6",
"agent:session-def456": "b2c3d4e5f6a1"
}
}| Issue | Cause | Fix |
|---|---|---|
docker not available in logs |
Docker daemon not running or socket not mounted | Start Docker; ensure socket is mounted in compose |
| Commands fall back to host execution | Docker unavailable at exec time | Check sandbox unavailable, falling back to host exec warning in logs |
docker run failed on container creation |
Image not found or insufficient permissions | Build the sandbox image; check DOCKER_GID |
| Output truncated at 1 MB | Command produced very large output | Increase max_output_bytes or pipe output to a file |
| Container not cleaned up after session | Pruner not running or idle_hours too high |
Lower idle_hours; check sandbox pruning started in logs |
| Write fails inside container | workspace_access: ro or read_only_root: true with no tmpfs |
Switch to rw or add a tmpfs mount for the target path |
- Custom Tools — define shell tools that also benefit from sandbox isolation
- Exec Approval — require human approval before any command runs, sandboxed or not
- Scheduling & Cron — run sandboxed agent turns on a schedule