Docker Setup for OpenClaw Gateway: Optional Isolated Environment
Learn how to use Docker for an isolated, disposable gateway environment when local installations are not available. This page covers prerequisites, building the containerized gateway, and alternatives like SSH and OpenShell.
Read this when
- You want a containerized gateway instead of local installs
- You are validating the Docker flow
Docker is optional. Use it when you want an isolated, disposable gateway environment or a machine that lacks local installations. If you already develop on your own computer, follow the standard install process instead.
When agents.defaults.sandbox is enabled, the default sandbox backend relies on Docker. However, sandboxing is turned off by default and the gateway itself does not need to run inside Docker. Alternative backends like SSH and OpenShell are available; refer to Sandboxing.
Managing multiple users? Check Multi-tenant hosting for the one cell per tenant approach.
Prerequisites
- Docker Desktop (or Docker Engine) plus Docker Compose v2
- Minimum 2 GB RAM for building images (
pnpm installmay be killed by OOM on hosts with 1 GB, exiting with code 137) - Sufficient disk space for images and logs
- On a VPS or public host, review Security hardening for network exposure, especially the Docker
DOCKER-USERfirewall chain
Containerized gateway
Build the image
From the repository root:
./scripts/docker/setup.sh
This command builds the gateway image locally and tags it as openclaw:local. To pull a pre-built image instead:
export OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:latest"
./scripts/docker/setup.sh
Pre-built images are first released to the GitHub Container Registry. GHCR serves as the main registry for release automation, pinned deployments, and provenance verification. The same release also pushes a Docker Hub mirror at openclaw/openclaw:
export OPENCLAW_IMAGE="openclaw/openclaw:latest"
./scripts/docker/setup.sh
Use ghcr.io/openclaw/openclaw or openclaw/openclaw and stay away from unofficial mirrors, which do not follow OpenClaw's release schedule or retention policies. Version-specific tags include releases such as 2026.2.26 and prereleases such as 2026.2.26-beta.1. Stable releases update latest and main; trailing-month Gateway releases update only extended-stable. Available variants are slim, main-slim, extended-stable-slim, latest-browser, main-browser, and extended-stable-browser. The default images include the codex and diagnostics-otel plugins. A -browser variant also bundles Chromium, useful for the sandboxed browser tool without needing a first-run Playwright installation.
Airgapped rerun
On offline hosts, transfer and load the image first:
docker load -i openclaw-image.tar
export OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:latest"
./scripts/docker/setup.sh --offline
--offline checks whether OPENCLAW_IMAGE already exists locally, disables implicit Compose pulls and builds, then proceeds with the normal workflow: .env sync, permission fixes, onboarding, gateway configuration sync, and Compose startup.
If OPENCLAW_SANDBOX=1, the offline setup also inspects the configured default and per-agent sandbox images on the daemon behind OPENCLAW_DOCKER_SOCKET, including the browser-contract label on Docker-backed browser images. When a required image is missing or outdated, setup exits without modifying sandbox configuration rather than reporting a false success.
Complete onboarding
The setup script handles onboarding automatically:
- asks for provider API keys
- creates a gateway token and saves it to
.env - sets up the auth-profile secret key directory
- launches the gateway with Docker Compose
Pre-start onboarding and configuration writes run through openclaw-gateway directly (using --no-deps --entrypoint node), because openclaw-cli shares the gateway's network namespace and only functions once the gateway container exists.
Open the Control UI
Open http://127.0.0.1:18789/ and paste the token stored in .env into Settings. If you configured the container to use password authentication, enter that password instead.
Need the URL again?
docker compose run --rm openclaw-cli dashboard --no-open
Configure channels (optional)
# WhatsApp (QR)
docker compose run --rm openclaw-cli channels login
# Telegram
docker compose run --rm openclaw-cli channels add --channel telegram --token "<token>"
# Discord
docker compose run --rm openclaw-cli channels add --channel discord --token "<token>"
Documentation: WhatsApp, Telegram, Discord
Manual flow
BUILD_GIT_COMMIT="$(git rev-parse HEAD)"
BUILD_TIMESTAMP="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
docker build \
--build-arg "GIT_COMMIT=${BUILD_GIT_COMMIT}" \
--build-arg "OPENCLAW_BUILD_TIMESTAMP=${BUILD_TIMESTAMP}" \
-t openclaw:local -f Dockerfile .
docker compose run --rm --no-deps --entrypoint node openclaw-gateway \
dist/index.js onboard --mode local --no-install-daemon
docker compose run --rm --no-deps --entrypoint node openclaw-gateway \
dist/index.js config set --batch-json '[{"path":"gateway.mode","value":"local"},{"path":"gateway.bind","value":"lan"},{"path":"gateway.controlUi.allowedOrigins","value":["http://localhost:18789","http://127.0.0.1:18789"]}]'
docker compose up -d openclaw-gateway
The Docker context excludes .git. Pass the source identity as build arguments as shown above so the image's About screen displays the checked-out commit and one build timestamp. scripts/docker/setup.sh resolves and passes both values automatically.
Note
Execute
docker composefrom the project root. If you haveOPENCLAW_EXTRA_MOUNTSorOPENCLAW_HOME_VOLUMEenabled, the setup script producesdocker-compose.extra.yml; append it after anydocker-compose.override.ymlyou manage yourself, for example-f docker-compose.yml -f docker-compose.override.yml -f docker-compose.extra.yml.
Upgrading container images
When you swap the OpenClaw image while keeping the same mounted state or configuration, the new gateway runs startup-safe upgrade migrations and plugin convergence before it becomes ready. Routine image updates should not need a separate openclaw doctor --fix step.
If startup cannot complete those repairs safely, the gateway exits rather than reporting itself as healthy. With a restart policy, Docker, Podman, or Kubernetes might show the gateway container restarting. Preserve the mounted state volume, then run the same image once with openclaw doctor --fix as the container command, using the same state and configuration mounts that the gateway uses:
docker run --rm -v <openclaw-state>:/home/node/.openclaw <image> openclaw doctor --fix
podman run --rm -v <openclaw-state>:/home/node/.openclaw <image> openclaw doctor --fix
After doctor finishes, restart the gateway container using its default command. In Kubernetes, execute the same command in a one-off Job or debug pod mounted to the same PVC, then restart the Deployment or StatefulSet.
Environment variables
Optional variables accepted by scripts/docker/setup.sh (and, for the gateway container, directly by docker-compose.yml):
| Variable | Purpose |
|---|---|
OPENCLAW_IMAGE | Pull a remote image instead of building locally |
OPENCLAW_IMAGE_APT_PACKAGES | Add extra apt packages during the build (space-separated). Legacy alias: OPENCLAW_DOCKER_APT_PACKAGES |
OPENCLAW_IMAGE_PIP_PACKAGES | Add extra Python packages during the build (space-separated) |
OPENCLAW_EXTENSIONS | Build and package selected supported plugins, and install their runtime dependencies (comma- or space-separated ids) |
OPENCLAW_DOCKER_BUILD_NODE_OPTIONS | Override the local source-build Node options (default --max-old-space-size=8192) |
OPENCLAW_DOCKER_BUILD_TSDOWN_MAX_OLD_SPACE_MB | Override the local source-build tsdown heap in MB |
OPENCLAW_DOCKER_BUILD_SKIP_DTS | Omit declaration output during runtime-only local image builds (default 1) |
OPENCLAW_INSTALL_BROWSER | Include Chromium and Xvfb in the image at build time |
OPENCLAW_EXTRA_MOUNTS | Additional host bind mounts (comma-separated source:target[:opts]) |
OPENCLAW_HOME_VOLUME | Keep /home/node in a named Docker volume |
OPENCLAW_SANDBOX | Enable sandbox bootstrap (1, true, yes, on) |
OPENCLAW_SKIP_ONBOARDING | Bypass the interactive onboarding step (1, true, yes, on) |
OPENCLAW_DOCKER_SOCKET | Override the Docker socket path |
OPENCLAW_DISABLE_BONJOUR | Force Bonjour/mDNS advertising on (0) or off (1); see Bonjour / mDNS |
OPENCLAW_DISABLE_BUNDLED_SOURCE_OVERLAYS | Turn off bundled plugin source bind-mount overlays |
OTEL_EXPORTER_OTLP_ENDPOINT | Shared OTLP/HTTP collector endpoint for OpenTelemetry export |
OTEL_EXPORTER_OTLP_*_ENDPOINT | Signal-specific OTLP endpoints for traces, metrics, or logs |
OTEL_EXPORTER_OTLP_PROTOCOL | OTLP protocol override. Only http/protobuf is supported today |
OTEL_SERVICE_NAME | Service name used for OpenTelemetry resources |
OTEL_SEMCONV_STABILITY_OPT_IN | Enable latest experimental GenAI semantic attributes |
OPENCLAW_OTEL_PRELOADED | Avoid starting a second OpenTelemetry SDK when one is already loaded |
The official image does not ship Homebrew. During onboarding, OpenClaw hides brew-only skill dependency installers in a Linux container without brew; provide those dependencies through a custom image or install them manually. Use OPENCLAW_IMAGE_APT_PACKAGES for Debian-packaged dependencies and OPENCLAW_IMAGE_PIP_PACKAGES for Python dependencies (runs python3 -m pip install --break-system-packages at build time, so pin versions and use only indexes you trust).
If Docker reports ResourceExhausted, cannot allocate memory, or aborts during tsdown, raise the Docker builder memory limit or retry with smaller explicit heaps:
OPENCLAW_DOCKER_BUILD_NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_DOCKER_BUILD_TSDOWN_MAX_OLD_SPACE_MB=4096
Source-built images with selected plugins
OPENCLAW_EXTENSIONS picks plugin manifest IDs from the source checkout; it also accepts existing source-directory names when those differ. The Docker build resolves the selection to source directories exactly once, installs production dependencies, and, when a chosen plugin is published independently using openclaw.build.bundledDist: false, compiles its runtime into the root bundled dist. This Docker-only packaging leaves the plugin's npm or ClawHub artifact contract unchanged. Unknown, invalid, or ambiguous IDs cause the image build to fail. Known dependency or source-only IDs keep their current source and dependency staging without gaining a compiled root dist entry. A selected plugin with unified build entries must compile successfully; unselected external plugin source and runtime output are removed.
As an example, these commands produce separate, multi-architecture standalone FakeCo gateway images for ClickClack, Slack, and Microsoft Teams. ClawRouter already resides in the root OpenClaw runtime, so the ClickClack image selects only clickclack. The explicit empty browser argument keeps the default image free of Chromium:
SOURCE_SHA="$(git rev-parse HEAD)"
BUILD_TIMESTAMP="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
REGISTRY="registry.example.com/fakeco"
build_gateway_image() {
gateway="$1"
selected_plugin="$2"
docker buildx build \
--platform linux/amd64,linux/arm64 \
--build-arg "GIT_COMMIT=${SOURCE_SHA}" \
--build-arg "OPENCLAW_BUILD_TIMESTAMP=${BUILD_TIMESTAMP}" \
--build-arg "OPENCLAW_EXTENSIONS=${selected_plugin}" \
--build-arg OPENCLAW_INSTALL_BROWSER= \
--provenance=mode=max \
--sbom=true \
--tag "${REGISTRY}/openclaw-${gateway}:${SOURCE_SHA}" \
--push \
.
}
build_gateway_image clickclack clickclack
build_gateway_image slack slack
build_gateway_image teams msteams
For a single native local build, use --platform linux/arm64 --load or --platform linux/amd64 --load. Multi-platform output and attached SBOM or provenance require a registry or another Buildx output that preserves attestations. After pushing, inspect the manifest and deploy the immutable digest instead of the mutable source-SHA tag:
docker buildx imagetools inspect \
"${REGISTRY}/openclaw-clickclack:${SOURCE_SHA}"
# Deploy: registry.example.com/fakeco/openclaw-clickclack@sha256:<manifest-digest>
These images target standalone OCI-based gateways and general Docker users. Crabhelm-managed gateways do not use them: that delivery path builds a separate x86_64 appliance archive containing an OpenClaw npm tarball and pins the Node, archive, and manifest digests. Build that appliance independently from the same landed OpenClaw source.
To test bundled plugin source against a packaged image, mount one plugin source directory over its packaged source path, for example OPENCLAW_EXTRA_MOUNTS=/path/to/fork/extensions/synology-chat:/app/extensions/synology-chat:ro. That overrides the matching compiled /app/dist/extensions/synology-chat bundle for the same plugin id.
Observability
OpenTelemetry export goes outbound from the Gateway container to your OTLP collector; no published Docker port is needed. To include the bundled exporter in a locally built image:
export OPENCLAW_EXTENSIONS="diagnostics-otel"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://otel-collector:4318"
export OTEL_SERVICE_NAME="openclaw-gateway"
./scripts/docker/setup.sh
Official prebuilt images already bundle diagnostics-otel; install clawhub:@openclaw/diagnostics-otel yourself only if you removed it. To enable export, allow and enable the diagnostics-otel plugin in config, then set diagnostics.otel.enabled=true (see the full example in OpenTelemetry export). Collector auth headers go through diagnostics.otel.headers, not Docker environment variables.
Prometheus metrics reuse the already-published Gateway port. Install clawhub:@openclaw/diagnostics-prometheus, enable the diagnostics-prometheus plugin, then scrape:
http://<gateway-host>:18789/api/diagnostics/prometheus
The route is protected by Gateway authentication; do not expose a separate public /metrics port or unauthenticated reverse-proxy path. See Prometheus metrics.
Health checks
Container probe endpoints (no auth required):
curl -fsS http://127.0.0.1:18789/healthz # liveness
curl -fsS http://127.0.0.1:18789/readyz # readiness
The image's built-in HEALTHCHECK pings /healthz; repeated failures mark the container unhealthy so orchestrators can restart or replace it.
Authenticated deep health snapshot:
docker compose exec openclaw-gateway node dist/index.js health --token "$OPENCLAW_GATEWAY_TOKEN"
LAN vs loopback
scripts/docker/setup.sh defaults OPENCLAW_GATEWAY_BIND=lan so http://127.0.0.1:18789 on the host works with Docker port publishing.
lan(default): host browser and host CLI can reach the published gateway port.loopback: only processes inside the container network namespace can reach the gateway directly.
Note
Use bind mode values in
gateway.bind(lan/loopback/custom/tailnet/auto), not host aliases like0.0.0.0or127.0.0.1.
Host local providers
Inside the container, 127.0.0.1 is the container itself, not the host. Use host.docker.internal for providers running on the host:
| Provider | Host default URL | Docker setup URL |
|---|---|---|
| LM Studio | http://127.0.0.1:1234 | http://host.docker.internal:1234 |
| Ollama | http://127.0.0.1:11434 | http://host.docker.internal:11434 |
The bundled setup uses those URLs as LM Studio/Ollama onboarding defaults, and docker-compose.yml maps host.docker.internal to the host gateway on Linux Docker Engine (Docker Desktop provides the same alias on macOS/Windows). Host services must listen on an address Docker can reach:
lms server start --port 1234 --bind 0.0.0.0
OLLAMA_HOST=0.0.0.0:11434 ollama serve
Using your own Compose file or docker run? Add the same mapping yourself, for example --add-host=host.docker.internal:host-gateway.
Claude CLI backend in Docker
The official image does not pre-install Claude Code. Install and log in inside the container's node user, then persist that container home so image upgrades do not erase the binary or auth state.
For a new install, enable a persistent /home/node volume before running setup:
export OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:latest"
export OPENCLAW_HOME_VOLUME="openclaw_home"
./scripts/docker/setup.sh
For an existing install, stop the stack and reload the current .env values first, the setup script always rewrites .env from the current shell and defaults, it does not read the file on its own:
set -a
. ./.env
set +a
export OPENCLAW_HOME_VOLUME="${OPENCLAW_HOME_VOLUME:-openclaw_home}"
./scripts/docker/setup.sh
If .env holds values your shell cannot source, manually re-export the ones you depend on first (OPENCLAW_IMAGE, ports, bind mode, custom paths, OPENCLAW_EXTRA_MOUNTS, sandbox, skip-onboarding). The generated overlay mounts the home volume for both openclaw-gateway and openclaw-cli; execute the remaining commands with that overlay (and docker-compose.override.yml first, if you have one):
docker compose -f docker-compose.yml -f docker-compose.extra.yml run --rm \
--entrypoint sh openclaw-cli -lc \
'curl -fsSL https://claude.ai/install.sh | bash'
The native installer writes claude to /home/node/.local/bin/claude. The OpenClaw image includes /home/node/.local/bin on PATH, so the bundled Anthropic plugin resolves it without overriding the adapter configuration.
Log in and confirm from the same persisted home:
docker compose -f docker-compose.yml -f docker-compose.extra.yml run --rm \
--entrypoint /home/node/.local/bin/claude openclaw-cli auth login
docker compose -f docker-compose.yml -f docker-compose.extra.yml run --rm \
--entrypoint /home/node/.local/bin/claude openclaw-cli auth status --text
docker compose -f docker-compose.yml -f docker-compose.extra.yml run --rm \
openclaw-cli models auth login \
--provider anthropic --method cli --set-default
docker compose -f docker-compose.yml -f docker-compose.extra.yml run --rm \
openclaw-cli models list --provider anthropic
After that, use the bundled claude-cli backend:
docker compose -f docker-compose.yml -f docker-compose.extra.yml run --rm \
openclaw-cli agent \
--agent main \
--model claude-cli/claude-sonnet-4-6 \
--message "Say hello from Docker Claude CLI"
OPENCLAW_HOME_VOLUME stores the native install under /home/node/.local/bin and /home/node/.local/share/claude, along with Claude Code settings and authentication under /home/node/.claude and /home/node/.claude.json. Persisting only /home/node/.openclaw is insufficient; if you use OPENCLAW_EXTRA_MOUNTS instead of a home volume, mount all those Claude paths into both services.
Note
For shared production automation or predictable Anthropic billing, the Anthropic API-key route is preferred. Reusing the Claude CLI follows the installed version, account login, billing, and update behavior of Claude Code.
Bonjour / mDNS
Docker bridge networking usually does not forward Bonjour/mDNS multicast (224.0.0.251:5353) reliably. When OPENCLAW_DISABLE_BONJOUR is not set, the bundled Bonjour plugin automatically disables LAN advertising after detecting it is running inside a container, so it will not crash-loop retrying multicast that the bridge drops. Set OPENCLAW_DISABLE_BONJOUR=1 to disable it regardless of detection, or 0 to enable it (only on host networking, macvlan, or another network where mDNS multicast is known to work).
Otherwise, use the published Gateway URL, Tailscale, or wide-area DNS-SD for Docker hosts. Refer to Bonjour discovery for caveats and troubleshooting.
Storage and persistence
Docker Compose bind-mounts OPENCLAW_CONFIG_DIR to /home/node/.openclaw, OPENCLAW_WORKSPACE_DIR to /home/node/.openclaw/workspace, and OPENCLAW_AUTH_PROFILE_SECRET_DIR to /home/node/.config/openclaw, so those paths survive container replacement. When a variable is not set, docker-compose.yml falls back under ${HOME}, or /tmp if HOME itself is missing, so docker compose up never emits an empty-source volume spec on bare environments.
That mounted config directory contains:
openclaw.jsonfor behavior configurationagents/<agentId>/agent/auth-profiles.jsonfor stored provider OAuth/API-key authentication.envfor env-backed runtime secrets such asOPENCLAW_GATEWAY_TOKEN
The auth-profile secret directory holds the local encryption key for OAuth-backed auth profile token material. Keep it with your Docker host state, but separate from OPENCLAW_CONFIG_DIR.
Installed downloadable plugins store package state under the mounted OpenClaw home, so installation records and package roots survive container replacement; gateway startup does not regenerate bundled-plugin dependency trees.
For complete VM persistence details, see Docker VM Runtime - What persists where.
Disk growth hotspots: media/, per-agent SQLite databases, legacy session JSONL transcripts, the shared SQLite state database, installed plugin package roots, and rolling file logs under /tmp/openclaw/.
Shell helpers (optional)
For shorter day-to-day commands, install ClawDock:
mkdir -p ~/.clawdock && curl -sL https://raw.githubusercontent.com/openclaw/openclaw/main/scripts/clawdock/clawdock-helpers.sh -o ~/.clawdock/clawdock-helpers.sh
echo 'source ~/.clawdock/clawdock-helpers.sh' >> ~/.zshrc && source ~/.zshrc
If you installed from the older scripts/shell-helpers/clawdock-helpers.sh path, rerun the command above so your local helper tracks the current location. Then use clawdock-start, clawdock-stop, clawdock-dashboard, and so on (run clawdock-help for the complete list).
Enable agent sandbox for Docker gateway
export OPENCLAW_SANDBOX=1
./scripts/docker/setup.sh
Custom socket path (for example, rootless Docker):
export OPENCLAW_SANDBOX=1
export OPENCLAW_DOCKER_SOCKET=/run/user/1000/docker.sock
./scripts/docker/setup.sh
The script only mounts docker.sock once the sandbox prerequisites are satisfied. If the sandbox setup fails, it restores agents.defaults.sandbox.mode to off. Codex code mode is turned off during turns where the OpenClaw sandbox is active (refer to Sandboxing § Docker backend); the host Docker socket must never be mounted into agent sandbox containers.
Automation / CI (non-interactive)
Turn off Compose pseudo-TTY allocation using -T:
docker compose run -T --rm openclaw-cli gateway probe
docker compose run -T --rm openclaw-cli devices list --json
Shared-network security note
openclaw-cli depends on network_mode: "service:openclaw-gateway" so that CLI commands can access the gateway through 127.0.0.1. Consider this a shared trust boundary. The compose configuration removes NET_RAW/NET_ADMIN and activates no-new-privileges on both openclaw-gateway and openclaw-cli.
Docker Desktop DNS failures in openclaw-cli
Certain Docker Desktop configurations experience DNS lookup failures from the shared-network openclaw-cli sidecar after NET_RAW is removed, manifesting as EAI_AGAIN during npm-backed commands such as openclaw plugins install. Keep the default hardened compose file for standard use. The following override restores default capabilities for only the openclaw-cli container, apply it for the one-off command needing registry access, not as your regular invocation:
printf '%s\n' \
'services:' \
' openclaw-cli:' \
' cap_drop: !reset []' \
> docker-compose.cli-no-dropped-caps.local.yml
docker compose -f docker-compose.yml -f docker-compose.cli-no-dropped-caps.local.yml run --rm openclaw-cli plugins install <package>
If a long-running openclaw-cli container already exists, recreate it with the same override, docker compose exec/docker exec cannot modify Linux capabilities on a container that is already running.
Permissions and EACCES
The image operates as node (uid 1000). When permission errors appear on /home/node/.openclaw, confirm that your host bind mounts are owned by uid 1000:
sudo chown -R 1000:1000 /path/to/openclaw-config /path/to/openclaw-workspace
This same mismatch may also present as blocked plugin candidate: suspicious ownership (... uid=1000, expected uid=0 or root) followed by plugin present but blocked, the process uid and the owner of the mounted plugin directory are out of sync. It is better to run as the default uid 1000 and correct the bind mount ownership. Only change the owner of /path/to/openclaw-config/npm to root:root if you intend to run OpenClaw as root on a long-term basis.
Faster rebuilds
Arrange your Dockerfile so dependency layers are cached, preventing a full pnpm install rebuild unless lockfiles change:
FROM node:24-bookworm
RUN curl -fsSL https://bun.sh/install | bash
ENV PATH="/root/.bun/bin:${PATH}"
RUN corepack enable
WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
COPY ui/package.json ./ui/package.json
COPY scripts ./scripts
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
RUN pnpm ui:install
RUN pnpm ui:build
ENV NODE_ENV=production
CMD ["node","dist/index.js"]
Power-user container options
The default image prioritizes security and runs as non-root node. For a container with more features:
- Persist
/home/node:export OPENCLAW_HOME_VOLUME="openclaw_home" - Bake system dependencies:
export OPENCLAW_IMAGE_APT_PACKAGES="git curl jq" - Bake Python dependencies:
export OPENCLAW_IMAGE_PIP_PACKAGES="requests==2.32.5 humanize==4.14.0" - Bake Playwright Chromium:
export OPENCLAW_INSTALL_BROWSER=1, or use the official-browserimage tag - Or install Playwright browsers into a persisted volume:
docker compose run --rm openclaw-cli \ node /app/node_modules/playwright-core/cli.js install chromium - Persist browser downloads: use
OPENCLAW_HOME_VOLUMEorOPENCLAW_EXTRA_MOUNTS. OpenClaw automatically detects the image's Playwright-managed Chromium on Linux.
OpenAI Codex OAuth (headless Docker)
Selecting OpenAI Codex OAuth in the wizard opens a browser URL. In Docker or headless environments, copy the complete redirect URL you are sent to and paste it back into the wizard to complete authentication.
Base image metadata
The runtime image uses node:24-bookworm-slim and launches tini as PID 1, ensuring zombie processes are cleaned up and signals are handled correctly in long-running containers. It publishes OCI base-image annotations including org.opencontainers.image.base.name and org.opencontainers.image.source. Dependabot updates the pinned Node base digest; release builds do not run a separate distro upgrade layer. See OCI image annotations.
Running on a VPS?
Refer to Hetzner (Docker VPS) and Docker VM Runtime for shared VM deployment instructions covering binary baking, persistence, and updates.
Agent sandbox
When agents.defaults.sandbox is activated with the Docker backend, the gateway executes agent tool operations (shell, file read/write, etc.) inside isolated Docker containers while the gateway itself remains on the host, creating a strong boundary around untrusted or multi-tenant agent sessions without requiring the entire gateway to be containerized.
Sandbox scope can be set to per-agent (the default), per-session, or shared. Each scope has its own workspace mounted at /workspace. You can also define allow or deny tool policies, enable network isolation, set resource limits, and use browser containers.
For complete configuration details, images, security notes, and multi-agent profiles:
- Sandboxing -- the full sandbox reference
- OpenShell -- interactive shell access inside sandbox containers
- Multi-Agent Sandbox and Tools -- overrides for individual agents
Quick enable
{
agents: {
defaults: {
sandbox: {
mode: "non-main", // off | non-main | all
scope: "agent", // session | agent | shared
},
},
},
}
From a source checkout, build the default sandbox image:
scripts/sandbox-setup.sh
If you are using npm installs without a source checkout, refer to Sandboxing § Images and setup for inline docker build commands.
Troubleshooting
Image missing or sandbox container not starting
Build the sandbox image using scripts/sandbox-setup.sh (with a source checkout) or the inline docker build command from Sandboxing § Images and setup (via npm install). Alternatively, set agents.defaults.sandbox.docker.image to point at your own custom image. Containers are created automatically per session as needed.
Permission errors in sandbox
Set docker.user to a UID:GID that matches the ownership of your mounted workspace, or run chown on the workspace folder.
Custom tools not found in sandbox
OpenClaw uses sh -lc (a login shell) to run commands. This shell sources /etc/profile and may override PATH. To prepend your custom tool paths, set docker.env.PATH, or add a script under /etc/profile.d/ inside your Dockerfile.
OOM-killed during image build (exit 137)
A minimum of 2 GB RAM is required for the VM. Choose a larger machine class and try again.
Unauthorized or pairing required in Control UI
Obtain a new dashboard link and approve the browser device:
docker compose run --rm openclaw-cli dashboard --no-open
docker compose run --rm openclaw-cli devices list
docker compose run --rm openclaw-cli devices approve <requestId>
Further information: Dashboard, Devices.
Gateway target shows ws://172.x.x.x or pairing errors from Docker CLI
Reset the gateway mode and binding:
docker compose run --rm openclaw-cli config set --batch-json '[{"path":"gateway.mode","value":"local"},{"path":"gateway.bind","value":"lan"}]'
docker compose run --rm openclaw-cli devices list --url ws://127.0.0.1:18789
Related
- Install Overview -- every installation method
- Podman -- using Podman instead of Docker
- ClawDock -- community Docker Compose setup
- Updating -- keeping OpenClaw current
- Configuration -- gateway configuration after installation