Skip to content

Commit cb491df

Browse files
jamtujestJakub Karwowskiclaudevincentkoc
authored
feat(docker): add opt-in sandbox support for Docker deployments (#29974)
* feat(docker): add opt-in sandbox support for Docker deployments Enable Docker-based sandbox isolation via OPENCLAW_SANDBOX=1 env var in docker-setup.sh. This is a prerequisite for agents.defaults.sandbox to function in any Docker deployment (self-hosted, Hostinger, DigitalOcean). Changes: - Dockerfile: add OPENCLAW_INSTALL_DOCKER_CLI build arg (~50MB, opt-in) - docker-compose.yml: add commented-out docker.sock mount with docs - docker-setup.sh: auto-detect Docker socket, inject mount, detect GID, build sandbox image, configure sandbox defaults, add group_add All changes are opt-in. Zero impact on existing deployments. Usage: OPENCLAW_SANDBOX=1 ./docker-setup.sh Closes #29933 Related: #7575, #7827, #28401, #10361, #12505, #28326 Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address code review feedback on sandbox support - Persist OPENCLAW_SANDBOX, DOCKER_GID, OPENCLAW_INSTALL_DOCKER_CLI to .env via upsert_env so group_add survives re-runs - Show config set errors instead of swallowing them silently; report partial failure when sandbox config is incomplete - Warn when Dockerfile.sandbox is missing but sandbox config is still applied (sandbox image won't exist) - Fix non-canonical whitespace in apt sources.list entry by using printf instead of echo with line continuation Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: remove `local` outside function and guard sandbox behind Docker CLI check - Remove `local` keyword from top-level `sandbox_config_ok` assignment which caused script exit under `set -euo pipefail` (bash `local` outside a function is an error) - Add Docker CLI prerequisite check for pre-built (non-local) images: runs `docker --version` inside the container and skips sandbox setup with a clear warning if the CLI is missing - Split sandbox block so config is only applied after prerequisites pass Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: defer docker.sock mount until sandbox prerequisites pass Move Docker socket mounting from the early setup phase (before image build/pull) to a dedicated compose overlay created only after: 1. Docker CLI is verified inside the container image 2. /var/run/docker.sock exists on the host Previously the socket was mounted optimistically at startup, leaving the host Docker daemon exposed even when sandbox setup was later skipped due to missing Docker CLI. Now the gateway starts without the socket, and a docker-compose.sandbox.yml overlay is generated only when all prerequisites pass. The gateway restart at the end of sandbox setup picks up both the socket mount and sandbox config. Also moves group_add from write_extra_compose() into the sandbox overlay, keeping all sandbox-specific compose configuration together. Co-Authored-By: Claude Opus 4.6 <[email protected]> * docs(docker): fix sandbox docs URL in setup output * Docker: harden sandbox setup fallback behavior * Tests: cover docker-setup sandbox edge paths * Docker: roll back sandbox mode on partial config failure * Tests: assert sandbox mode rollback on partial setup * Docs: document Docker sandbox bootstrap env controls * Changelog: credit Docker sandbox bootstrap hardening * Update CHANGELOG.md * Docker: verify Docker apt signing key fingerprint * Docker: avoid sandbox overlay deps during policy writes * Tests: assert no-deps sandbox rollback gateway recreate * Docs: mention OPENCLAW_INSTALL_DOCKER_CLI in Docker env vars --------- Co-authored-by: Jakub Karwowski <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: Vincent Koc <[email protected]>
1 parent f918b33 commit cb491df

6 files changed

Lines changed: 348 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ Docs: https://docs.openclaw.ai
5252
- Browser/Profile defaults: prefer `openclaw` profile over `chrome` in headless/no-sandbox environments unless an explicit `defaultProfile` is configured. (#14944) Thanks @BenediktSchackenberg.
5353
- Browser/Remote CDP ownership checks: skip local-process ownership errors for non-loopback remote CDP profiles when HTTP is reachable but the websocket handshake fails, and surface the remote websocket attach/retry path instead. (#15582) Landed from contributor (#28780) Thanks @stubbi, @bsormagec, @unblockedgamesstudio and @vincentkoc.
5454
- Docker/Image health checks: add Dockerfile `HEALTHCHECK` that probes gateway `GET /healthz` so container runtimes can mark unhealthy instances without requiring auth credentials in the probe command. (#11478) Thanks @U-C4N and @vincentkoc.
55+
- Docker/Sandbox bootstrap hardening: make `OPENCLAW_SANDBOX` opt-in parsing explicit (`1|true|yes|on`), support custom Docker socket paths via `OPENCLAW_DOCKER_SOCKET`, defer docker.sock exposure until sandbox prerequisites pass, and reset/roll back persisted sandbox mode to `off` when setup is skipped or partially fails to avoid stale broken sandbox state. (#29974) Thanks @jamtujest and @vincentkoc.
5556
- Daemon/systemd checks in containers: treat missing `systemctl` invocations (including `spawn systemctl ENOENT`/`EACCES`) as unavailable service state during `is-enabled` checks, preventing container flows from failing with `Gateway service check failed` before install/status handling can continue. (#26089) Thanks @sahilsatralkar and @vincentkoc.
5657
- Android/Nodes reliability: reject `facing=both` when `deviceId` is set to avoid mislabeled duplicate captures, allow notification `open`/`reply` on non-clearable entries while still gating dismiss, trigger listener rebind before notification actions, and scale invoke-result ack timeout to invoke budget for large clip payloads. (#28260) Thanks @obviyus.
5758
- Windows/Plugin install: avoid `spawn EINVAL` on Windows npm/npx invocations by resolving to `node` + npm CLI scripts instead of spawning `.cmd` directly. Landed from contributor PR #31147 by @codertony. Thanks @codertony.

Dockerfile

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,38 @@ RUN if [ -n "$OPENCLAW_INSTALL_BROWSER" ]; then \
5757
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*; \
5858
fi
5959

60+
# Optionally install Docker CLI for sandbox container management.
61+
# Build with: docker build --build-arg OPENCLAW_INSTALL_DOCKER_CLI=1 ...
62+
# Adds ~50MB. Only the CLI is installed — no Docker daemon.
63+
# Required for agents.defaults.sandbox to function in Docker deployments.
64+
ARG OPENCLAW_INSTALL_DOCKER_CLI=""
65+
ARG OPENCLAW_DOCKER_GPG_FINGERPRINT="9DC858229FC7DD38854AE2D88D81803C0EBFCD88"
66+
RUN if [ -n "$OPENCLAW_INSTALL_DOCKER_CLI" ]; then \
67+
apt-get update && \
68+
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
69+
ca-certificates curl gnupg && \
70+
install -m 0755 -d /etc/apt/keyrings && \
71+
# Verify Docker apt signing key fingerprint before trusting it as a root key.
72+
# Update OPENCLAW_DOCKER_GPG_FINGERPRINT when Docker rotates release keys.
73+
curl -fsSL https://download.docker.com/linux/debian/gpg -o /tmp/docker.gpg.asc && \
74+
expected_fingerprint="$(printf '%s' "$OPENCLAW_DOCKER_GPG_FINGERPRINT" | tr '[:lower:]' '[:upper:]' | tr -d '[:space:]')" && \
75+
actual_fingerprint="$(gpg --batch --show-keys --with-colons /tmp/docker.gpg.asc | awk -F: '$1 == \"fpr\" { print toupper($10); exit }')" && \
76+
if [ -z "$actual_fingerprint" ] || [ "$actual_fingerprint" != "$expected_fingerprint" ]; then \
77+
echo "ERROR: Docker apt key fingerprint mismatch (expected $expected_fingerprint, got ${actual_fingerprint:-<empty>})" >&2; \
78+
exit 1; \
79+
fi && \
80+
gpg --dearmor -o /etc/apt/keyrings/docker.gpg /tmp/docker.gpg.asc && \
81+
rm -f /tmp/docker.gpg.asc && \
82+
chmod a+r /etc/apt/keyrings/docker.gpg && \
83+
printf 'deb [arch=%s signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian bookworm stable\n' \
84+
"$(dpkg --print-architecture)" > /etc/apt/sources.list.d/docker.list && \
85+
apt-get update && \
86+
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
87+
docker-ce-cli docker-compose-plugin && \
88+
apt-get clean && \
89+
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*; \
90+
fi
91+
6092
USER node
6193
COPY --chown=node:node . .
6294
# Normalize copied plugin/agent paths so plugin safety checks do not reject

docker-compose.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ services:
1212
volumes:
1313
- ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
1414
- ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
15+
## Uncomment the lines below to enable sandbox isolation
16+
## (agents.defaults.sandbox). Requires Docker CLI in the image
17+
## (build with --build-arg OPENCLAW_INSTALL_DOCKER_CLI=1) or use
18+
## docker-setup.sh with OPENCLAW_SANDBOX=1 for automated setup.
19+
## Set DOCKER_GID to the host's docker group GID (run: stat -c '%g' /var/run/docker.sock).
20+
# - /var/run/docker.sock:/var/run/docker.sock
21+
# group_add:
22+
# - "${DOCKER_GID:-999}"
1523
ports:
1624
- "${OPENCLAW_GATEWAY_PORT:-18789}:18789"
1725
- "${OPENCLAW_BRIDGE_PORT:-18790}:18790"

docker-setup.sh

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ EXTRA_COMPOSE_FILE="$ROOT_DIR/docker-compose.extra.yml"
77
IMAGE_NAME="${OPENCLAW_IMAGE:-openclaw:local}"
88
EXTRA_MOUNTS="${OPENCLAW_EXTRA_MOUNTS:-}"
99
HOME_VOLUME_NAME="${OPENCLAW_HOME_VOLUME:-}"
10+
RAW_SANDBOX_SETTING="${OPENCLAW_SANDBOX:-}"
11+
SANDBOX_ENABLED=""
12+
DOCKER_SOCKET_PATH="${OPENCLAW_DOCKER_SOCKET:-}"
1013

1114
fail() {
1215
echo "ERROR: $*" >&2
@@ -20,6 +23,15 @@ require_cmd() {
2023
fi
2124
}
2225

26+
is_truthy_value() {
27+
local raw="${1:-}"
28+
raw="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')"
29+
case "$raw" in
30+
1 | true | yes | on) return 0 ;;
31+
*) return 1 ;;
32+
esac
33+
}
34+
2335
read_config_gateway_token() {
2436
local config_path="$OPENCLAW_CONFIG_DIR/openclaw.json"
2537
if [[ ! -f "$config_path" ]]; then
@@ -144,6 +156,16 @@ if ! docker compose version >/dev/null 2>&1; then
144156
exit 1
145157
fi
146158

159+
if [[ -z "$DOCKER_SOCKET_PATH" && "${DOCKER_HOST:-}" == unix://* ]]; then
160+
DOCKER_SOCKET_PATH="${DOCKER_HOST#unix://}"
161+
fi
162+
if [[ -z "$DOCKER_SOCKET_PATH" ]]; then
163+
DOCKER_SOCKET_PATH="/var/run/docker.sock"
164+
fi
165+
if is_truthy_value "$RAW_SANDBOX_SETTING"; then
166+
SANDBOX_ENABLED="1"
167+
fi
168+
147169
OPENCLAW_CONFIG_DIR="${OPENCLAW_CONFIG_DIR:-$HOME/.openclaw}"
148170
OPENCLAW_WORKSPACE_DIR="${OPENCLAW_WORKSPACE_DIR:-$HOME/.openclaw/workspace}"
149171

@@ -159,6 +181,9 @@ fi
159181
if contains_disallowed_chars "$EXTRA_MOUNTS"; then
160182
fail "OPENCLAW_EXTRA_MOUNTS cannot contain control characters."
161183
fi
184+
if [[ -n "$SANDBOX_ENABLED" ]]; then
185+
validate_mount_path_value "OPENCLAW_DOCKER_SOCKET" "$DOCKER_SOCKET_PATH"
186+
fi
162187

163188
mkdir -p "$OPENCLAW_CONFIG_DIR"
164189
mkdir -p "$OPENCLAW_WORKSPACE_DIR"
@@ -178,6 +203,15 @@ export OPENCLAW_DOCKER_APT_PACKAGES="${OPENCLAW_DOCKER_APT_PACKAGES:-}"
178203
export OPENCLAW_EXTRA_MOUNTS="$EXTRA_MOUNTS"
179204
export OPENCLAW_HOME_VOLUME="$HOME_VOLUME_NAME"
180205
export OPENCLAW_ALLOW_INSECURE_PRIVATE_WS="${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}"
206+
export OPENCLAW_SANDBOX="$SANDBOX_ENABLED"
207+
export OPENCLAW_DOCKER_SOCKET="$DOCKER_SOCKET_PATH"
208+
209+
# Detect Docker socket GID for sandbox group_add.
210+
DOCKER_GID=""
211+
if [[ -n "$SANDBOX_ENABLED" && -S "$DOCKER_SOCKET_PATH" ]]; then
212+
DOCKER_GID="$(stat -c '%g' "$DOCKER_SOCKET_PATH" 2>/dev/null || stat -f '%g' "$DOCKER_SOCKET_PATH" 2>/dev/null || echo "")"
213+
fi
214+
export DOCKER_GID
181215

182216
if [[ -z "${OPENCLAW_GATEWAY_TOKEN:-}" ]]; then
183217
EXISTING_CONFIG_TOKEN="$(read_config_gateway_token || true)"
@@ -255,6 +289,14 @@ YAML
255289
fi
256290
}
257291

292+
# When sandbox is requested, ensure Docker CLI build arg is set for local builds.
293+
# Docker socket mount is deferred until sandbox prerequisites are verified.
294+
if [[ -n "$SANDBOX_ENABLED" ]]; then
295+
if [[ -z "${OPENCLAW_INSTALL_DOCKER_CLI:-}" ]]; then
296+
export OPENCLAW_INSTALL_DOCKER_CLI=1
297+
fi
298+
fi
299+
258300
VALID_MOUNTS=()
259301
if [[ -n "$EXTRA_MOUNTS" ]]; then
260302
IFS=',' read -r -a mounts <<<"$EXTRA_MOUNTS"
@@ -279,6 +321,9 @@ fi
279321
for compose_file in "${COMPOSE_FILES[@]}"; do
280322
COMPOSE_ARGS+=("-f" "$compose_file")
281323
done
324+
# Keep a base compose arg set without sandbox overlay so rollback paths can
325+
# force a known-safe gateway service definition (no docker.sock mount).
326+
BASE_COMPOSE_ARGS=("${COMPOSE_ARGS[@]}")
282327
COMPOSE_HINT="docker compose"
283328
for compose_file in "${COMPOSE_FILES[@]}"; do
284329
COMPOSE_HINT+=" -f ${compose_file}"
@@ -333,12 +378,17 @@ upsert_env "$ENV_FILE" \
333378
OPENCLAW_EXTRA_MOUNTS \
334379
OPENCLAW_HOME_VOLUME \
335380
OPENCLAW_DOCKER_APT_PACKAGES \
381+
OPENCLAW_SANDBOX \
382+
OPENCLAW_DOCKER_SOCKET \
383+
DOCKER_GID \
384+
OPENCLAW_INSTALL_DOCKER_CLI \
336385
OPENCLAW_ALLOW_INSECURE_PRIVATE_WS
337386

338387
if [[ "$IMAGE_NAME" == "openclaw:local" ]]; then
339388
echo "==> Building Docker image: $IMAGE_NAME"
340389
docker build \
341390
--build-arg "OPENCLAW_DOCKER_APT_PACKAGES=${OPENCLAW_DOCKER_APT_PACKAGES}" \
391+
--build-arg "OPENCLAW_INSTALL_DOCKER_CLI=${OPENCLAW_INSTALL_DOCKER_CLI:-}" \
342392
-t "$IMAGE_NAME" \
343393
-f "$ROOT_DIR/Dockerfile" \
344394
"$ROOT_DIR"
@@ -399,6 +449,115 @@ echo ""
399449
echo "==> Starting gateway"
400450
docker compose "${COMPOSE_ARGS[@]}" up -d openclaw-gateway
401451

452+
# --- Sandbox setup (opt-in via OPENCLAW_SANDBOX=1) ---
453+
if [[ -n "$SANDBOX_ENABLED" ]]; then
454+
echo ""
455+
echo "==> Sandbox setup"
456+
457+
# Build sandbox image if Dockerfile.sandbox exists.
458+
if [[ -f "$ROOT_DIR/Dockerfile.sandbox" ]]; then
459+
echo "Building sandbox image: openclaw-sandbox:bookworm-slim"
460+
docker build \
461+
-t "openclaw-sandbox:bookworm-slim" \
462+
-f "$ROOT_DIR/Dockerfile.sandbox" \
463+
"$ROOT_DIR"
464+
else
465+
echo "WARNING: Dockerfile.sandbox not found in $ROOT_DIR" >&2
466+
echo " Sandbox config will be applied but no sandbox image will be built." >&2
467+
echo " Agent exec may fail if the configured sandbox image does not exist." >&2
468+
fi
469+
470+
# Defense-in-depth: verify Docker CLI in the running image before enabling
471+
# sandbox. This avoids claiming sandbox is enabled when the image cannot
472+
# launch sandbox containers.
473+
if ! docker compose "${COMPOSE_ARGS[@]}" run --rm --entrypoint docker openclaw-gateway --version >/dev/null 2>&1; then
474+
echo "WARNING: Docker CLI not found inside the container image." >&2
475+
echo " Sandbox requires Docker CLI. Rebuild with --build-arg OPENCLAW_INSTALL_DOCKER_CLI=1" >&2
476+
echo " or use a local build (OPENCLAW_IMAGE=openclaw:local). Skipping sandbox setup." >&2
477+
SANDBOX_ENABLED=""
478+
fi
479+
fi
480+
481+
# Apply sandbox config only if prerequisites are met.
482+
if [[ -n "$SANDBOX_ENABLED" ]]; then
483+
# Mount Docker socket via a dedicated compose overlay. This overlay is
484+
# created only after sandbox prerequisites pass, so the socket is never
485+
# exposed when sandbox cannot actually run.
486+
if [[ -S "$DOCKER_SOCKET_PATH" ]]; then
487+
SANDBOX_COMPOSE_FILE="$ROOT_DIR/docker-compose.sandbox.yml"
488+
cat >"$SANDBOX_COMPOSE_FILE" <<YAML
489+
services:
490+
openclaw-gateway:
491+
volumes:
492+
- ${DOCKER_SOCKET_PATH}:/var/run/docker.sock
493+
YAML
494+
if [[ -n "${DOCKER_GID:-}" ]]; then
495+
cat >>"$SANDBOX_COMPOSE_FILE" <<YAML
496+
group_add:
497+
- "${DOCKER_GID}"
498+
YAML
499+
fi
500+
COMPOSE_ARGS+=("-f" "$SANDBOX_COMPOSE_FILE")
501+
echo "==> Sandbox: added Docker socket mount"
502+
else
503+
echo "WARNING: OPENCLAW_SANDBOX enabled but Docker socket not found at $DOCKER_SOCKET_PATH." >&2
504+
echo " Sandbox requires Docker socket access. Skipping sandbox setup." >&2
505+
SANDBOX_ENABLED=""
506+
fi
507+
fi
508+
509+
if [[ -n "$SANDBOX_ENABLED" ]]; then
510+
# Enable sandbox in OpenClaw config.
511+
sandbox_config_ok=true
512+
if ! docker compose "${COMPOSE_ARGS[@]}" run --rm --no-deps openclaw-cli \
513+
config set agents.defaults.sandbox.mode "non-main" >/dev/null; then
514+
echo "WARNING: Failed to set agents.defaults.sandbox.mode" >&2
515+
sandbox_config_ok=false
516+
fi
517+
if ! docker compose "${COMPOSE_ARGS[@]}" run --rm --no-deps openclaw-cli \
518+
config set agents.defaults.sandbox.scope "agent" >/dev/null; then
519+
echo "WARNING: Failed to set agents.defaults.sandbox.scope" >&2
520+
sandbox_config_ok=false
521+
fi
522+
if ! docker compose "${COMPOSE_ARGS[@]}" run --rm --no-deps openclaw-cli \
523+
config set agents.defaults.sandbox.workspaceAccess "none" >/dev/null; then
524+
echo "WARNING: Failed to set agents.defaults.sandbox.workspaceAccess" >&2
525+
sandbox_config_ok=false
526+
fi
527+
528+
if [[ "$sandbox_config_ok" == true ]]; then
529+
echo "Sandbox enabled: mode=non-main, scope=agent, workspaceAccess=none"
530+
echo "Docs: https://docs.openclaw.ai/gateway/sandboxing"
531+
# Restart gateway with sandbox compose overlay to pick up socket mount + config.
532+
docker compose "${COMPOSE_ARGS[@]}" up -d openclaw-gateway
533+
else
534+
echo "WARNING: Sandbox config was partially applied. Check errors above." >&2
535+
echo " Skipping gateway restart to avoid exposing Docker socket without a full sandbox policy." >&2
536+
if ! docker compose "${BASE_COMPOSE_ARGS[@]}" run --rm --no-deps openclaw-cli \
537+
config set agents.defaults.sandbox.mode "off" >/dev/null; then
538+
echo "WARNING: Failed to roll back agents.defaults.sandbox.mode to off" >&2
539+
else
540+
echo "Sandbox mode rolled back to off due to partial sandbox config failure."
541+
fi
542+
if [[ -n "${SANDBOX_COMPOSE_FILE:-}" ]]; then
543+
rm -f "$SANDBOX_COMPOSE_FILE"
544+
fi
545+
# Ensure gateway service definition is reset without sandbox overlay mount.
546+
docker compose "${BASE_COMPOSE_ARGS[@]}" up -d --force-recreate openclaw-gateway
547+
fi
548+
else
549+
# Keep reruns deterministic: if sandbox is not active for this run, reset
550+
# persisted sandbox mode so future execs do not require docker.sock by stale
551+
# config alone.
552+
if ! docker compose "${COMPOSE_ARGS[@]}" run --rm openclaw-cli \
553+
config set agents.defaults.sandbox.mode "off" >/dev/null; then
554+
echo "WARNING: Failed to reset agents.defaults.sandbox.mode to off" >&2
555+
fi
556+
if [[ -f "$ROOT_DIR/docker-compose.sandbox.yml" ]]; then
557+
rm -f "$ROOT_DIR/docker-compose.sandbox.yml"
558+
fi
559+
fi
560+
402561
echo ""
403562
echo "Gateway running with host port mapping."
404563
echo "Access from tailnet devices via the host's tailnet IP."

docs/install/docker.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ Optional env vars:
5959
- `OPENCLAW_DOCKER_APT_PACKAGES` — install extra apt packages during build
6060
- `OPENCLAW_EXTRA_MOUNTS` — add extra host bind mounts
6161
- `OPENCLAW_HOME_VOLUME` — persist `/home/node` in a named volume
62+
- `OPENCLAW_SANDBOX` — opt in to Docker gateway sandbox bootstrap. Only explicit truthy values enable it: `1`, `true`, `yes`, `on`
63+
- `OPENCLAW_INSTALL_DOCKER_CLI` — build arg passthrough for local image builds (`1` installs Docker CLI in the image). `docker-setup.sh` sets this automatically when `OPENCLAW_SANDBOX=1` for local builds.
64+
- `OPENCLAW_DOCKER_SOCKET` — override Docker socket path (default: `DOCKER_HOST=unix://...` path, else `/var/run/docker.sock`)
6265
- `OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1` — break-glass: allow trusted private-network
6366
`ws://` targets for CLI/onboarding client paths (default is loopback-only)
6467

@@ -68,6 +71,38 @@ After it finishes:
6871
- Paste the token into the Control UI (Settings → token).
6972
- Need the URL again? Run `docker compose run --rm openclaw-cli dashboard --no-open`.
7073

74+
### Enable agent sandbox for Docker gateway (opt-in)
75+
76+
`docker-setup.sh` can also bootstrap `agents.defaults.sandbox.*` for Docker
77+
deployments.
78+
79+
Enable with:
80+
81+
```bash
82+
export OPENCLAW_SANDBOX=1
83+
./docker-setup.sh
84+
```
85+
86+
Custom socket path (for example rootless Docker):
87+
88+
```bash
89+
export OPENCLAW_SANDBOX=1
90+
export OPENCLAW_DOCKER_SOCKET=/run/user/1000/docker.sock
91+
./docker-setup.sh
92+
```
93+
94+
Notes:
95+
96+
- The script mounts `docker.sock` only after sandbox prerequisites pass.
97+
- If sandbox setup cannot be completed, the script resets
98+
`agents.defaults.sandbox.mode` to `off` to avoid stale/broken sandbox config
99+
on reruns.
100+
- If `Dockerfile.sandbox` is missing, the script prints a warning and continues;
101+
build `openclaw-sandbox:bookworm-slim` with `scripts/sandbox-setup.sh` if
102+
needed.
103+
- For non-local `OPENCLAW_IMAGE` values, the image must already contain Docker
104+
CLI support for sandbox execution.
105+
71106
### Automation/CI (non-interactive, no TTY noise)
72107

73108
For scripts and CI, disable Compose pseudo-TTY allocation with `-T`:

0 commit comments

Comments
 (0)