Add opt-in `sandbox.docker.gpus` config plumbing for Docker sandbox containers. - thread the optional GPU passthrough field through config types, schema, resolution, and Docker create args - reject empty config values and emit `--gpus` as a separate Docker argv pair - document the Docker-only behavior and credit the original contributor in the changelog Fixes #57976. Carries forward #58124 from @cyan-ember. Co-authored-by: cyan-ember <5855097+cyan-ember@users.noreply.github.com>
24 KiB
summary, title, sidebarTitle, read_when, status
| summary | title | sidebarTitle | read_when | status |
|---|---|---|---|---|
| How OpenClaw sandboxing works: modes, scopes, workspace access, and images | Sandboxing | Sandboxing | You want a dedicated explanation of sandboxing or need to tune agents.defaults.sandbox. | active |
OpenClaw can run tools inside sandbox backends to reduce blast radius. This is optional and controlled by configuration (agents.defaults.sandbox or agents.list[].sandbox). If sandboxing is off, tools run on the host. The Gateway stays on the host; tool execution runs in an isolated sandbox when enabled.
What gets sandboxed
- Tool execution (
exec,read,write,edit,apply_patch,process, etc.). - Optional sandboxed browser (
agents.defaults.sandbox.browser).
Not sandboxed:
- The Gateway process itself.
- Any tool explicitly allowed to run outside the sandbox (e.g.
tools.elevated).- Elevated exec bypasses sandboxing and uses the configured escape path (
gatewayby default, ornodewhen the exec target isnode). - If sandboxing is off,
tools.elevateddoes not change execution (already on host). See Elevated Mode.
- Elevated exec bypasses sandboxing and uses the configured escape path (
Modes
agents.defaults.sandbox.mode controls when sandboxing is used:
`"non-main"` is based on `session.mainKey` (default `"main"`), not agent id. Group/channel sessions use their own keys, so they count as non-main and will be sandboxed.
Every session runs in a sandbox.
Scope
agents.defaults.sandbox.scope controls how many containers are created:
"agent"(default): one container per agent."session": one container per session."shared": one container shared by all sandboxed sessions.
Backend
agents.defaults.sandbox.backend controls which runtime provides the sandbox:
"docker"(default when sandboxing is enabled): local Docker-backed sandbox runtime."ssh": generic SSH-backed remote sandbox runtime."openshell": OpenShell-backed sandbox runtime.
SSH-specific config lives under agents.defaults.sandbox.ssh. OpenShell-specific config lives under plugins.entries.openshell.config.
Choosing a backend
| Docker | SSH | OpenShell | |
|---|---|---|---|
| Where it runs | Local container | Any SSH-accessible host | OpenShell managed sandbox |
| Setup | scripts/sandbox-setup.sh |
SSH key + target host | OpenShell plugin enabled |
| Workspace model | Bind-mount or copy | Remote-canonical (seed once) | mirror or remote |
| Network control | docker.network (default: none) |
Depends on remote host | Depends on OpenShell |
| Browser sandbox | Supported | Not supported | Not supported yet |
| Bind mounts | docker.binds |
N/A | N/A |
| Best for | Local dev, full isolation | Offloading to a remote machine | Managed remote sandboxes with optional two-way sync |
Docker backend
Sandboxing is off by default. If you enable sandboxing and do not choose a backend, OpenClaw uses the Docker backend. It executes tools and sandbox browsers locally via the Docker daemon socket (/var/run/docker.sock). Sandbox container isolation is determined by Docker namespaces.
To expose host GPUs to Docker sandboxes, set agents.defaults.sandbox.docker.gpus or the per-agent agents.list[].sandbox.docker.gpus override. The value is passed to Docker's --gpus flag as a separate argument, for example "all" or "device=GPU-uuid", and requires a compatible host runtime such as NVIDIA Container Toolkit.
If you deploy the OpenClaw Gateway itself as a Docker container, it orchestrates sibling sandbox containers using the host's Docker socket (DooD). This introduces a specific path mapping constraint:
- Config requires host paths: The
openclaw.jsonworkspaceconfiguration MUST contain the Host's absolute path (e.g./home/user/.openclaw/workspaces), not the internal Gateway container path. When OpenClaw asks the Docker daemon to spawn a sandbox, the daemon evaluates paths relative to the Host OS namespace, not the Gateway namespace. - FS bridge parity (identical volume map): The OpenClaw Gateway native process also writes heartbeat and bridge files to the
workspacedirectory. Because the Gateway evaluates the exact same string (the host path) from within its own containerized environment, the Gateway deployment MUST include an identical volume map linking the host namespace natively (-v /home/user/.openclaw:/home/user/.openclaw).
If you map paths internally without absolute host parity, OpenClaw natively throws an EACCES permission error attempting to write its heartbeat inside the container environment because the fully qualified path string doesn't exist natively.
SSH backend
Use backend: "ssh" when you want OpenClaw to sandbox exec, file tools, and media reads on an arbitrary SSH-accessible machine.
{
agents: {
defaults: {
sandbox: {
mode: "all",
backend: "ssh",
scope: "session",
workspaceAccess: "rw",
ssh: {
target: "user@gateway-host:22",
workspaceRoot: "/tmp/openclaw-sandboxes",
strictHostKeyChecking: true,
updateHostKeys: true,
identityFile: "~/.ssh/id_ed25519",
certificateFile: "~/.ssh/id_ed25519-cert.pub",
knownHostsFile: "~/.ssh/known_hosts",
// Or use SecretRefs / inline contents instead of local files:
// identityData: { source: "env", provider: "default", id: "SSH_IDENTITY" },
// certificateData: { source: "env", provider: "default", id: "SSH_CERTIFICATE" },
// knownHostsData: { source: "env", provider: "default", id: "SSH_KNOWN_HOSTS" },
},
},
},
},
}
- Host-local edits made outside OpenClaw after the seed step are not visible remotely until you recreate the sandbox.
- `openclaw sandbox recreate` deletes the per-scope remote root and seeds again from local on next use.
- Browser sandboxing is not supported on the SSH backend.
- `sandbox.docker.*` settings do not apply to the SSH backend.
OpenShell backend
Use backend: "openshell" when you want OpenClaw to sandbox tools in an OpenShell-managed remote environment. For the full setup guide, configuration reference, and workspace mode comparison, see the dedicated OpenShell page.
OpenShell reuses the same core SSH transport and remote filesystem bridge as the generic SSH backend, and adds OpenShell-specific lifecycle (sandbox create/get/delete, sandbox ssh-config) plus the optional mirror workspace mode.
{
agents: {
defaults: {
sandbox: {
mode: "all",
backend: "openshell",
scope: "session",
workspaceAccess: "rw",
},
},
},
plugins: {
entries: {
openshell: {
enabled: true,
config: {
from: "openclaw",
mode: "remote", // mirror | remote
remoteWorkspaceDir: "/sandbox",
remoteAgentWorkspaceDir: "/agent",
},
},
},
},
}
OpenShell modes:
mirror(default): local workspace stays canonical. OpenClaw syncs local files into OpenShell before exec and syncs the remote workspace back after exec.remote: OpenShell workspace is canonical after the sandbox is created. OpenClaw seeds the remote workspace once from the local workspace, then file tools and exec run directly against the remote sandbox without syncing changes back.
Workspace modes
OpenShell has two workspace models. This is the part that matters most in practice.
Use `plugins.entries.openshell.config.mode: "mirror"` when you want the **local workspace to stay canonical**.Behavior:
- Before `exec`, OpenClaw syncs the local workspace into the OpenShell sandbox.
- After `exec`, OpenClaw syncs the remote workspace back to the local workspace.
- File tools still operate through the sandbox bridge, but the local workspace remains the source of truth between turns.
Use this when:
- you edit files locally outside OpenClaw and want those changes to show up in the sandbox automatically
- you want the OpenShell sandbox to behave as much like the Docker backend as possible
- you want the host workspace to reflect sandbox writes after each exec turn
Tradeoff: extra sync cost before and after exec.
Use `plugins.entries.openshell.config.mode: "remote"` when you want the **OpenShell workspace to become canonical**.
Behavior:
- When the sandbox is first created, OpenClaw seeds the remote workspace from the local workspace once.
- After that, `exec`, `read`, `write`, `edit`, and `apply_patch` operate directly against the remote OpenShell workspace.
- OpenClaw does **not** sync remote changes back into the local workspace after exec.
- Prompt-time media reads still work because file and media tools read through the sandbox bridge instead of assuming a local host path.
- Transport is SSH into the OpenShell sandbox returned by `openshell sandbox ssh-config`.
Important consequences:
- If you edit files on the host outside OpenClaw after the seed step, the remote sandbox will **not** see those changes automatically.
- If the sandbox is recreated, the remote workspace is seeded from the local workspace again.
- With `scope: "agent"` or `scope: "shared"`, that remote workspace is shared at that same scope.
Use this when:
- the sandbox should live primarily on the remote OpenShell side
- you want lower per-turn sync overhead
- you do not want host-local edits to silently overwrite remote sandbox state
Choose mirror if you think of the sandbox as a temporary execution environment. Choose remote if you think of the sandbox as the real workspace.
OpenShell lifecycle
OpenShell sandboxes are still managed through the normal sandbox lifecycle:
openclaw sandbox listshows OpenShell runtimes as well as Docker runtimesopenclaw sandbox recreatedeletes the current runtime and lets OpenClaw recreate it on next use- prune logic is backend-aware too
For remote mode, recreate is especially important:
- recreate deletes the canonical remote workspace for that scope
- the next use seeds a fresh remote workspace from the local workspace
For mirror mode, recreate mainly resets the remote execution environment because the local workspace remains canonical anyway.
Workspace access
agents.defaults.sandbox.workspaceAccess controls what the sandbox can see:
With the OpenShell backend:
mirrormode still uses the local workspace as the canonical source between exec turnsremotemode uses the remote OpenShell workspace as the canonical source after the initial seedworkspaceAccess: "ro"and"none"still restrict write behavior the same way
Inbound media is copied into the active sandbox workspace (media/inbound/*).
Custom bind mounts
agents.defaults.sandbox.docker.binds mounts additional host directories into the container. Format: host:container:mode (e.g., "/home/user/source:/source:rw").
Global and per-agent binds are merged (not replaced). Under scope: "shared", per-agent binds are ignored.
agents.defaults.sandbox.browser.binds mounts additional host directories into the sandbox browser container only.
- When set (including
[]), it replacesagents.defaults.sandbox.docker.bindsfor the browser container. - When omitted, the browser container falls back to
agents.defaults.sandbox.docker.binds(backwards compatible).
Example (read-only source + an extra data directory):
{
agents: {
defaults: {
sandbox: {
docker: {
binds: ["/home/user/source:/source:ro", "/var/data/myapp:/data:ro"],
},
},
},
list: [
{
id: "build",
sandbox: {
docker: {
binds: ["/mnt/cache:/cache:rw"],
},
},
},
],
},
}
- Binds bypass the sandbox filesystem: they expose host paths with whatever mode you set (
:roor:rw). - OpenClaw blocks dangerous bind sources (for example:
docker.sock,/etc,/proc,/sys,/dev, and parent mounts that would expose them). - OpenClaw also blocks common home-directory credential roots such as
~/.aws,~/.cargo,~/.config,~/.docker,~/.gnupg,~/.netrc,~/.npm, and~/.ssh. - Bind validation is not just string matching. OpenClaw normalizes the source path, then resolves it again through the deepest existing ancestor before re-checking blocked paths and allowed roots.
- That means symlink-parent escapes still fail closed even when the final leaf does not exist yet. Example:
/workspace/run-link/new-filestill resolves as/var/run/...ifrun-linkpoints there. - Allowed source roots are canonicalized the same way, so a path that only looks inside the allowlist before symlink resolution is still rejected as
outside allowed roots. - Sensitive mounts (secrets, SSH keys, service credentials) should be
:rounless absolutely required. - Combine with
workspaceAccess: "ro"if you only need read access to the workspace; bind modes stay independent. - See Sandbox vs Tool Policy vs Elevated for how binds interact with tool policy and elevated exec.
Images and setup
Default Docker image: openclaw-sandbox:bookworm-slim
The default image does **not** include Node. If a skill needs Node (or other runtimes), either bake a custom image or install via `sandbox.docker.setupCommand` (requires network egress + writable root + root user).
OpenClaw does not silently substitute plain `debian:bookworm-slim` when `openclaw-sandbox:bookworm-slim` is missing. Sandbox runs that target the default image fail fast with a build instruction until you run `scripts/sandbox-setup.sh`, because the bundled image carries `python3` for sandbox write/edit helpers.
For a more functional sandbox image with common tooling (for example `curl`, `jq`, `nodejs`, `python3`, `git`):
```bash
scripts/sandbox-common-setup.sh
```
Then set `agents.defaults.sandbox.docker.image` to `openclaw-sandbox-common:bookworm-slim`.
```bash
scripts/sandbox-browser-setup.sh
```
By default, Docker sandbox containers run with no network. Override with agents.defaults.sandbox.docker.network.
- `--remote-debugging-address=127.0.0.1`
- `--remote-debugging-port=<derived from OPENCLAW_BROWSER_CDP_PORT>`
- `--user-data-dir=${HOME}/.chrome`
- `--no-first-run`
- `--no-default-browser-check`
- `--disable-3d-apis`
- `--disable-gpu`
- `--disable-dev-shm-usage`
- `--disable-background-networking`
- `--disable-extensions`
- `--disable-features=TranslateUI`
- `--disable-breakpad`
- `--disable-crash-reporter`
- `--disable-software-rasterizer`
- `--no-zygote`
- `--metrics-recording-only`
- `--renderer-process-limit=2`
- `--no-sandbox` when `noSandbox` is enabled.
- The three graphics hardening flags (`--disable-3d-apis`, `--disable-software-rasterizer`, `--disable-gpu`) are optional and are useful when containers lack GPU support. Set `OPENCLAW_BROWSER_DISABLE_GRAPHICS_FLAGS=0` if your workload requires WebGL or other 3D/browser features.
- `--disable-extensions` is enabled by default and can be disabled with `OPENCLAW_BROWSER_DISABLE_EXTENSIONS=0` for extension-reliant flows.
- `--renderer-process-limit=2` is controlled by `OPENCLAW_BROWSER_RENDERER_PROCESS_LIMIT=<N>`, where `0` keeps Chromium's default.
If you need a different runtime profile, use a custom browser image and provide your own entrypoint. For local (non-container) Chromium profiles, use `browser.extraArgs` to append additional startup flags.
- `network: "host"` is blocked.
- `network: "container:"` is blocked by default (namespace join bypass risk).
- Break-glass override: `agents.defaults.sandbox.docker.dangerouslyAllowContainerNamespaceJoin: true`.
Docker installs and the containerized gateway live here: Docker
For Docker gateway deployments, scripts/docker/setup.sh can bootstrap sandbox config. Set OPENCLAW_SANDBOX=1 (or true/yes/on) to enable that path. You can override socket location with OPENCLAW_DOCKER_SOCKET. Full setup and env reference: Docker.
setupCommand (one-time container setup)
setupCommand runs once after the sandbox container is created (not on every run). It executes inside the container via sh -lc.
Paths:
- Global:
agents.defaults.sandbox.docker.setupCommand - Per-agent:
agents.list[].sandbox.docker.setupCommand
Tool policy and escape hatches
Tool allow/deny policies still apply before sandbox rules. If a tool is denied globally or per-agent, sandboxing doesn't bring it back.
tools.elevated is an explicit escape hatch that runs exec outside the sandbox (gateway by default, or node when the exec target is node). /exec directives only apply for authorized senders and persist per session; to hard-disable exec, use tool policy deny (see Sandbox vs Tool Policy vs Elevated).
Debugging:
- Use
openclaw sandbox explainto inspect effective sandbox mode, tool policy, and fix-it config keys. - See Sandbox vs Tool Policy vs Elevated for the "why is this blocked?" mental model.
Keep it locked down.
Multi-agent overrides
Each agent can override sandbox + tools: agents.list[].sandbox and agents.list[].tools (plus agents.list[].tools.sandbox.tools for sandbox tool policy). See Multi-Agent Sandbox & Tools for precedence.
Minimal enable example
{
agents: {
defaults: {
sandbox: {
mode: "non-main",
scope: "session",
workspaceAccess: "none",
},
},
},
}
Related
- Multi-Agent Sandbox & Tools — per-agent overrides and precedence
- OpenShell — managed sandbox backend setup, workspace modes, and config reference
- Sandbox configuration
- Sandbox vs Tool Policy vs Elevated — debugging "why is this blocked?"
- Security