Files
openclaw/docs/install/docker.md
Peter Steinberger f91de52f0d refactor: move runtime state to SQLite
* refactor: remove stale file-backed shims

* fix: harden sqlite state ci boundaries

* refactor: store matrix idb snapshots in sqlite

* fix: satisfy rebased CI guardrails

* refactor: store current conversation bindings in sqlite table

* refactor: store tui last sessions in sqlite table

* refactor: reset sqlite schema history

* refactor: drop unshipped sqlite table migration

* refactor: remove plugin index file rollback

* refactor: drop unshipped sqlite sidecar migrations

* refactor: remove runtime commitments kv migration

* refactor: preserve kysely sync result types

* refactor: drop unshipped sqlite schema migration table

* test: keep session usage coverage sqlite-backed

* refactor: keep sqlite migration doctor-only

* refactor: isolate device legacy imports

* refactor: isolate push voicewake legacy imports

* refactor: isolate remaining runtime legacy imports

* refactor: tighten sqlite migration guardrails

* test: cover sqlite persisted enum parsing

* refactor: isolate legacy update and tui imports

* refactor: tighten sqlite state ownership

* refactor: move legacy imports behind doctor

* refactor: remove legacy session row lookup

* refactor: canonicalize memory transcript locators

* refactor: drop transcript path scope fallbacks

* refactor: drop runtime legacy session delivery pruning

* refactor: store tts prefs only in sqlite

* refactor: remove cron store path runtime

* refactor: use cron sqlite store keys

* refactor: rename telegram message cache scope

* refactor: read memory dreaming status from sqlite

* refactor: rename cron status store key

* refactor: stop remembering transcript file paths

* test: use sqlite locators in agent fixtures

* refactor: remove file-shaped commitments and cron store surfaces

* refactor: keep compaction transcript handles out of session rows

* refactor: derive transcript handles from session identity

* refactor: derive runtime transcript handles

* refactor: remove gateway session locator reads

* refactor: remove transcript locator from session rows

* refactor: store raw stream diagnostics in sqlite

* refactor: remove file-shaped transcript rotation

* refactor: hide legacy trajectory paths from runtime

* refactor: remove runtime transcript file bridges

* refactor: repair database-first rebase fallout

* refactor: align tests with database-first state

* refactor: remove transcript file handoffs

* refactor: sync post-compaction memory by transcript scope

* refactor: run codex app-server sessions by id

* refactor: bind codex runtime state by session id

* refactor: pass memory transcripts by sqlite scope

* refactor: remove transcript locator cleanup leftovers

* test: remove stale transcript file fixtures

* refactor: remove transcript locator test helper

* test: make cron sqlite keys explicit

* test: remove cron runtime store paths

* test: remove stale session file fixtures

* test: use sqlite cron keys in diagnostics

* refactor: remove runtime delivery queue backfill

* test: drop fake export session file mocks

* refactor: rename acp session read failure flag

* refactor: rename acp row session key

* refactor: remove session store test seams

* refactor: move legacy session parser tests to doctor

* refactor: reindex managed memory in place

* refactor: drop stale session store wording

* refactor: rename session row helpers

* refactor: rename sqlite session entry modules

* refactor: remove transcript locator leftovers

* refactor: trim file-era audit wording

* refactor: clean managed media through sqlite

* fix: prefer explicit agent for exports

* fix: use prepared agent for session resets

* fix: canonicalize legacy codex binding import

* test: rename state cleanup helper

* docs: align backup docs with sqlite state

* refactor: drop legacy Pi usage auth fallback

* refactor: move legacy auth profile imports to doctor

* refactor: keep Pi model discovery auth in memory

* refactor: remove MSTeams legacy learning key fallback

* refactor: store model catalog config in sqlite

* refactor: use sqlite model catalog at runtime

* refactor: remove model json compatibility aliases

* refactor: store auth profiles in sqlite

* refactor: seed copied auth profiles in sqlite

* refactor: make auth profile runtime sqlite-addressed

* refactor: migrate hermes secrets into sqlite auth store

* refactor: move plugin install config migration to doctor

* refactor: rename plugin index audit checks

* test: drop auth file assumptions

* test: remove legacy transcript file assertions

* refactor: drop legacy cli session aliases

* refactor: store skill uploads in sqlite

* refactor: keep subagent attachments in sqlite vfs

* refactor: drop subagent attachment cleanup state

* refactor: move legacy session aliases to doctor

* refactor: require node 24 for sqlite state runtime

* refactor: move provider caches into sqlite state

* fix: harden virtual agent filesystem

* refactor: enforce database-first runtime state

* refactor: rename compaction transcript rotation setting

* test: clean sqlite refactor test types

* refactor: consolidate sqlite runtime state

* refactor: model session conversations in sqlite

* refactor: stop deriving cron delivery from session keys

* refactor: stop classifying sessions from key shape

* refactor: hydrate announce targets from typed delivery

* refactor: route heartbeat delivery from typed sqlite context

* refactor: tighten typed sqlite session routing

* refactor: remove session origin routing shadow

* refactor: drop session origin shadow fixtures

* perf: query sqlite vfs paths by prefix

* refactor: use typed conversation metadata for sessions

* refactor: prefer typed session routing metadata

* refactor: require typed session routing metadata

* refactor: resolve group tool policy from typed sessions

* refactor: delete dead session thread info bridge

* Show Codex subscription reset times in channel errors (#80456)

* feat(plugin-sdk): consolidate session workflow APIs

* fix(agents): allow read-only agent mount reads

* [codex] refresh plugin regression fixtures

* fix(agents): restore compaction gateway logs

* test: tighten gateway startup assertions

* Redact persisted secret-shaped payloads [AI] (#79006)

* test: tighten device pair notify assertions

* test: tighten hermes secret assertions

* test: assert matrix client error shapes

* test: assert config compat warnings

* fix(heartbeat): remap cron-run exec events to session keys (#80214)

* fix(codex): route btw through native side threads

* fix(auth): accept friendly OpenAI order for Codex profiles

* fix(codex): rotate auth profiles inside harness

* fix: keep browser status page probe within timeout

* test: assert agents add outputs

* test: pin cron read status

* fix(agents): avoid Pi resource discovery stalls

Co-authored-by: dataCenter430 <titan032000@gmail.com>

* fix: retire timed-out codex app-server clients

* test: tighten qa lab runtime assertions

* test: check security fix outputs

* test: verify extension runtime messages

* feat(wake): expose typed sessionKey on wake protocol + system event CLI

* fix(gateway): await session_end during shutdown drain and track channel + compaction lifecycle paths (#57790)

* test: guard talk consult call helper

* fix(codex): scale context engine projection (#80761)

* fix(codex): scale context engine projection

* fix: document Codex context projection scaling

* fix: document Codex context projection scaling

* fix: document Codex context projection scaling

* fix: document Codex context projection scaling

* chore: align Codex projection changelog

* chore: realign Codex projection changelog

* fix: isolate Codex projection patch

---------

Co-authored-by: Eva (agent) <eva+agent-78055@100yen.org>
Co-authored-by: Josh Lehman <josh@martian.engineering>

* refactor: move agent runtime state toward piless

* refactor: remove cron session reaper

* refactor: move session management to sqlite

* refactor: finish database-first state migration

* chore: refresh generated sqlite db types

* refactor: remove stale file-backed shims

* test: harden kysely type coverage

# Conflicts:
#	.agents/skills/kysely-database-access/SKILL.md
#	src/infra/kysely-sync.types.test.ts
#	src/proxy-capture/store.sqlite.test.ts
#	src/state/openclaw-agent-db.test.ts
#	src/state/openclaw-state-db.test.ts

* refactor: remove cron store path runtime

* refactor: keep compaction transcript handles out of session rows

* refactor: derive embedded transcripts from sqlite identity

* refactor: remove embedded transcript locator handoff

* refactor: remove runtime transcript file bridges

* refactor: remove transcript file handoffs

* refactor: remove MSTeams legacy learning key fallback

* refactor: store model catalog config in sqlite

* refactor: use sqlite model catalog at runtime

# Conflicts:
#	docs/cli/secrets.md
#	docs/gateway/authentication.md
#	docs/gateway/secrets.md

* fix: keep oauth sibling sync sqlite-local

# Conflicts:
#	src/commands/onboard-auth.test.ts

* refactor: remove task session store maintenance

# Conflicts:
#	src/commands/tasks.ts

* refactor: keep diagnostics in state sqlite

* refactor: enforce database-first runtime state

* refactor: consolidate sqlite runtime state

* Show Codex subscription reset times in channel errors (#80456)

* fix(codex): refresh subscription limit resets

* fix(codex): format reset times for channels

* Update CHANGELOG with latest changes and fixes

Updated CHANGELOG with recent fixes and improvements.

* fix(codex): keep command load failures on codex surface

* fix(codex): format account rate limits as rows

* fix(codex): summarize account limits as usage status

* fix(codex): simplify account limit status

* test: tighten subagent announce queue assertion

* test: tighten session delete lifecycle assertions

* test: tighten cron ops assertions

* fix: track cron execution milestones

* test: tighten hermes secret assertions

* test: assert matrix sync store payloads

* test: assert config compat warnings

* fix(codex): align btw side thread semantics

* fix(codex): honor codex fallback blocking

* fix(agents): avoid Pi resource discovery stalls

* test: tighten codex event assertions

* test: tighten cron assertions

* Fix Codex app-server OAuth harness auth

* refactor: move agent runtime state toward piless

* refactor: move device and push state to sqlite

* refactor: move runtime json state imports to doctor

* refactor: finish database-first state migration

* chore: refresh generated sqlite db types

* refactor: clarify cron sqlite store keys

* refactor: remove stale file-backed shims

* refactor: bind codex runtime state by session id

* test: expect sqlite trajectory branch export

* refactor: rename session row helpers

* fix: keep legacy device identity import in doctor

* refactor: enforce database-first runtime state

* refactor: consolidate sqlite runtime state

* build: align pi contract wrappers

* chore: repair database-first rebase

* refactor: remove session file test contracts

* test: update gateway session expectations

* refactor: stop routing from session compatibility shadows

* refactor: stop persisting session route shadows

* refactor: use typed delivery context in clients

* refactor: stop echoing session route shadows

* refactor: repair embedded runner rebase imports

# Conflicts:
#	src/agents/pi-embedded-runner/run/attempt.tool-call-argument-repair.ts

* refactor: align pi contract imports

* refactor: satisfy kysely sync helper guard

* refactor: remove file transcript bridge remnants

* refactor: remove session locator compatibility

* refactor: remove session file test contracts

* refactor: keep rebase database-first clean

* refactor: remove session file assumptions from e2e

* docs: clarify database-first goal state

* test: remove legacy store markers from sqlite runtime tests

* refactor: remove legacy store assumptions from runtime seams

* refactor: align sqlite runtime helper seams

* test: update memory recall sqlite audit mock

* refactor: align database-first runtime type seams

* test: clarify doctor cron legacy store names

* fix: preserve sqlite session route projections

* test: fix copilot token cache test syntax

* docs: update database-first proof status

* test: align database-first test fixtures

* docs: update database-first proof status

* refactor: clean extension database-first drift

* test: align agent session route proof

* test: clarify doctor legacy path fixtures

* chore: clean database-first changed checks

* chore: repair database-first rebase markers

* build: allow baileys git subdependency

* chore: repair exp-vfs rebase drift

* chore: finish exp-vfs rebase cleanup

* chore: satisfy rebase lint drift

* chore: fix qqbot rebase type seam

* chore: fix rebase drift leftovers

* fix: keep auth profile oauth secrets out of sqlite

* fix: repair rebase drift tests

* test: stabilize pairing request ordering

* test: use source manifests in plugin contract checks

* fix: restore gateway session metadata after rebase

* fix: repair database-first rebase drift

* fix: clean up database-first rebase fallout

* test: stabilize line quick reply receipt time

* fix: repair extension rebase drift

* test: keep transcript redaction tests sqlite-backed

* fix: carry injected transcript redaction through sqlite

* chore: clean database branch rebase residue

* fix: repair database branch CI drift

* fix: repair database branch CI guard drift

* fix: stabilize oauth tls preflight test

* test: align database branch fast guards

* test: repair build artifact boundary guards

* chore: clean changelog rebase markers

---------

Co-authored-by: pashpashpash <nik@vault77.ai>
Co-authored-by: Eva <eva@100yen.org>
Co-authored-by: stainlu <stainlu@newtype-ai.org>
Co-authored-by: Jason Zhou <jason.zhou.design@gmail.com>
Co-authored-by: Ruben Cuevas <hi@rubencu.com>
Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com>
Co-authored-by: Shakker <shakkerdroid@gmail.com>
Co-authored-by: Kaspre <36520309+Kaspre@users.noreply.github.com>
Co-authored-by: dataCenter430 <titan032000@gmail.com>
Co-authored-by: Kaspre <kaspre@gmail.com>
Co-authored-by: pandadev66 <nova.full.stack@outlook.com>
Co-authored-by: Eva <admin@100yen.org>
Co-authored-by: Eva (agent) <eva+agent-78055@100yen.org>
Co-authored-by: Josh Lehman <josh@martian.engineering>
Co-authored-by: jeffjhunter <support@aipersonamethod.com>
2026-05-13 13:15:12 +01:00

22 KiB

summary, read_when, title
summary read_when title
Optional Docker-based setup and onboarding for OpenClaw
You want a containerized gateway instead of local installs
You are validating the Docker flow
Docker

Docker is optional. Use it only if you want a containerized gateway or to validate the Docker flow.

Is Docker right for me?

  • Yes: you want an isolated, throwaway gateway environment or to run OpenClaw on a host without local installs.
  • No: you are running on your own machine and just want the fastest dev loop. Use the normal install flow instead.
  • Sandboxing note: the default sandbox backend uses Docker when sandboxing is enabled, but sandboxing is off by default and does not require the full gateway to run in Docker. SSH and OpenShell sandbox backends are also available. See Sandboxing.

Prerequisites

  • Docker Desktop (or Docker Engine) + Docker Compose v2
  • At least 2 GB RAM for image build (pnpm install may be OOM-killed on 1 GB hosts with exit 137)
  • Enough disk for images and logs
  • If running on a VPS/public host, review Security hardening for network exposure, especially Docker DOCKER-USER firewall policy.

Containerized gateway

From the repo root, run the setup script:
```bash
./scripts/docker/setup.sh
```

This builds the gateway image locally. To use a pre-built image instead:

```bash
export OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:latest"
./scripts/docker/setup.sh
```

Pre-built images are published at the
[GitHub Container Registry](https://github.com/openclaw/openclaw/pkgs/container/openclaw).
Common tags: `main`, `latest`, `<version>` (e.g. `2026.2.26`).
The setup script runs onboarding automatically. It will:
- prompt for provider API keys
- generate a gateway token and write it to `.env`
- create the auth-profile secret key directory
- start the gateway via Docker Compose

During setup, pre-start onboarding and config writes run through
`openclaw-gateway` directly. `openclaw-cli` is for commands you run after
the gateway container already exists.
Open `http://127.0.0.1:18789/` in your browser and paste the configured shared secret into Settings. The setup script writes a token to `.env` by default; if you switch the container config to password auth, use that password instead.
Need the URL again?

```bash
docker compose run --rm openclaw-cli dashboard --no-open
```
Use the CLI container to add messaging channels:
```bash
# 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>"
```

Docs: [WhatsApp](/channels/whatsapp), [Telegram](/channels/telegram), [Discord](/channels/discord)

Manual flow

If you prefer to run each step yourself instead of using the setup script:

docker build -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
Run `docker compose` from the repo root. If you enabled `OPENCLAW_EXTRA_MOUNTS` or `OPENCLAW_HOME_VOLUME`, the setup script writes `docker-compose.extra.yml`; include it with `-f docker-compose.yml -f docker-compose.extra.yml`. Because `openclaw-cli` shares `openclaw-gateway`'s network namespace, it is a post-start tool. Before `docker compose up -d openclaw-gateway`, run onboarding and setup-time config writes through `openclaw-gateway` with `--no-deps --entrypoint node`.

Environment variables

The setup script accepts these optional environment variables:

Variable Purpose
OPENCLAW_IMAGE Use a remote image instead of building locally
OPENCLAW_DOCKER_APT_PACKAGES Install extra apt packages during build (space-separated)
OPENCLAW_EXTENSIONS Include selected bundled plugin helpers at build time
OPENCLAW_EXTRA_MOUNTS Extra host bind mounts (comma-separated source:target[:opts])
OPENCLAW_HOME_VOLUME Persist /home/node in a named Docker volume
OPENCLAW_SANDBOX Opt in to sandbox bootstrap (1, true, yes, on)
OPENCLAW_SKIP_ONBOARDING Skip the interactive onboarding step (1, true, yes, on)
OPENCLAW_DOCKER_SOCKET Override Docker socket path
OPENCLAW_DISABLE_BONJOUR Disable Bonjour/mDNS advertising (defaults to 1 for Docker)
OPENCLAW_DISABLE_BUNDLED_SOURCE_OVERLAYS Disable 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 Opt in to latest experimental GenAI semantic attributes
OPENCLAW_OTEL_PRELOADED Skip starting a second OpenTelemetry SDK when one is preloaded

Maintainers can test bundled plugin source against a packaged image by mounting 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 mounted source directory overrides the matching compiled /app/dist/extensions/synology-chat bundle for the same plugin id.

Observability

OpenTelemetry export is outbound from the Gateway container to your OTLP collector. It does not require a published Docker port. If you build the image locally and want the bundled OpenTelemetry exporter available inside the image, include its runtime dependencies:

export OPENCLAW_EXTENSIONS="diagnostics-otel"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://otel-collector:4318"
export OTEL_SERVICE_NAME="openclaw-gateway"
./scripts/docker/setup.sh

Install the official @openclaw/diagnostics-otel plugin from ClawHub in packaged Docker installs before enabling export. Custom source-built images can still include the local plugin source with OPENCLAW_EXTENSIONS=diagnostics-otel. To enable export, allow and enable the diagnostics-otel plugin in config, then set diagnostics.otel.enabled=true or use the config example in OpenTelemetry export. Collector auth headers are configured through diagnostics.otel.headers, not through Docker environment variables.

Prometheus metrics use 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 Docker image includes a built-in HEALTHCHECK that pings /healthz. If checks keep failing, Docker marks the container as unhealthy and orchestration systems 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 host access to http://127.0.0.1:18789 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.
Use bind mode values in `gateway.bind` (`lan` / `loopback` / `custom` / `tailnet` / `auto`), not host aliases like `0.0.0.0` or `127.0.0.1`.

Host Local Providers

When OpenClaw runs in Docker, 127.0.0.1 inside the container is the container itself, not your host machine. Use host.docker.internal for AI providers that run 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 Docker setup uses those host URLs as the LM Studio and Ollama onboarding defaults, and docker-compose.yml maps host.docker.internal to Docker's host gateway for Linux Docker Engine. Docker Desktop already provides the same hostname on macOS and Windows.

Host services must also listen on an address reachable from Docker:

lms server start --port 1234 --bind 0.0.0.0
OLLAMA_HOST=0.0.0.0:11434 ollama serve

If you use your own Compose file or docker run command, add the same host mapping yourself, for example --add-host=host.docker.internal:host-gateway.

Bonjour / mDNS

Docker bridge networking usually does not forward Bonjour/mDNS multicast (224.0.0.251:5353) reliably. The bundled Compose setup therefore defaults OPENCLAW_DISABLE_BONJOUR=1 so the Gateway does not crash-loop or repeatedly restart advertising when the bridge drops multicast traffic.

Use the published Gateway URL, Tailscale, or wide-area DNS-SD for Docker hosts. Set OPENCLAW_DISABLE_BONJOUR=0 only when running with host networking, macvlan, or another network where mDNS multicast is known to work.

For gotchas and troubleshooting, see Bonjour discovery.

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 any variable is unset, the bundled docker-compose.yml falls back under ${HOME}, or /tmp when HOME itself is also missing. That keeps docker compose up from emitting an empty-source volume spec on bare environments.

That mounted config directory is where OpenClaw keeps:

  • openclaw.json for behavior config
  • state/openclaw.sqlite#table/auth_profile_stores/<agentDir> for stored provider OAuth/API-key auth
  • .env for env-backed runtime secrets such as OPENCLAW_GATEWAY_TOKEN

The auth-profile secret key directory stores the local encryption key used for OAuth-backed auth profile token material. Keep it with your Docker host state, but separate from OPENCLAW_CONFIG_DIR.

Installed downloadable plugins store their package state under the mounted OpenClaw home, so plugin install records and package roots survive container replacement. Gateway startup does not generate bundled-plugin dependency trees.

For full persistence details on VM deployments, see Docker VM Runtime - What persists where.

Disk growth hotspots: watch media/, the shared SQLite state database, installed plugin package roots, and rolling file logs under /tmp/openclaw/.

Shell helpers (optional)

For easier day-to-day Docker management, 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 ClawDock from the older scripts/shell-helpers/clawdock-helpers.sh raw path, rerun the install command above so your local helper file tracks the new location.

Then use clawdock-start, clawdock-stop, clawdock-dashboard, etc. Run clawdock-help for all commands. See ClawDock for the full helper guide.

```bash export OPENCLAW_SANDBOX=1 ./scripts/docker/setup.sh ```
Custom socket path (e.g. rootless Docker):

```bash
export OPENCLAW_SANDBOX=1
export OPENCLAW_DOCKER_SOCKET=/run/user/1000/docker.sock
./scripts/docker/setup.sh
```

The script mounts `docker.sock` only after sandbox prerequisites pass. If
sandbox setup cannot complete, the script resets `agents.defaults.sandbox.mode`
to `off`. Codex code-mode turns are still constrained to Codex
`workspace-write` while the OpenClaw sandbox is active; do not mount the
host Docker socket into agent sandbox containers.
Disable Compose pseudo-TTY allocation with `-T`:
```bash
docker compose run -T --rm openclaw-cli gateway probe
docker compose run -T --rm openclaw-cli devices list --json
```
`openclaw-cli` uses `network_mode: "service:openclaw-gateway"` so CLI commands can reach the gateway over `127.0.0.1`. Treat this as a shared trust boundary. The compose config drops `NET_RAW`/`NET_ADMIN` and enables `no-new-privileges` on both `openclaw-gateway` and `openclaw-cli`. Some Docker Desktop setups fail DNS lookups from the shared-network `openclaw-cli` sidecar after `NET_RAW` is dropped, which shows up as `EAI_AGAIN` during npm-backed commands such as `openclaw plugins install`. Keep the default hardened compose file for normal gateway operation. The local override below loosens the CLI container's security posture by restoring Docker's default capabilities, so use it only for the one-off CLI command that needs package registry access, not as your default Compose invocation:
```bash
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 you already created a long-running `openclaw-cli` container, recreate it
with the same override. `docker compose exec` and `docker exec` cannot
change Linux capabilities on an already-created container.
The image runs as `node` (uid 1000). If you see permission errors on `/home/node/.openclaw`, make sure your host bind mounts are owned by uid 1000:
```bash
sudo chown -R 1000:1000 /path/to/openclaw-config /path/to/openclaw-workspace
```

The same mismatch can show up as a plugin warning such as
`blocked plugin candidate: suspicious ownership (... uid=1000, expected uid=0 or root)`
followed by `plugin present but blocked`. That means the process uid and the
mounted plugin directory owner disagree. Prefer running the container as the
default uid 1000 and fixing the bind mount ownership. Only chown
`/path/to/openclaw-config/npm` to `root:root` if you intentionally run
OpenClaw as root long term.
Order your Dockerfile so dependency layers are cached. This avoids re-running `pnpm install` unless lockfiles change:
```dockerfile
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"]
```
The default image is security-first and runs as non-root `node`. For a more full-featured container:
1. **Persist `/home/node`**: `export OPENCLAW_HOME_VOLUME="openclaw_home"`
2. **Bake system deps**: `export OPENCLAW_DOCKER_APT_PACKAGES="git curl jq"`
3. **Bake Playwright Chromium**: `export OPENCLAW_INSTALL_BROWSER=1`
4. **Or install Playwright browsers into a persisted volume**:
   ```bash
   docker compose run --rm openclaw-cli \
     node /app/node_modules/playwright-core/cli.js install chromium
   ```
5. **Persist browser downloads**: use `OPENCLAW_HOME_VOLUME` or
   `OPENCLAW_EXTRA_MOUNTS`. OpenClaw auto-detects the Docker image's
   Playwright-managed Chromium on Linux.
If you pick OpenAI Codex OAuth in the wizard, it opens a browser URL. In Docker or headless setups, copy the full redirect URL you land on and paste it back into the wizard to finish auth. The main Docker runtime image uses `node:24-bookworm-slim` and includes `tini` as the entrypoint init process (PID 1) to ensure zombie processes are reaped and signals are handled correctly in long-running containers. It publishes OCI base-image annotations including `org.opencontainers.image.base.name`, `org.opencontainers.image.source`, and others. The Node base digest is refreshed through Dependabot Docker base-image PRs; release builds do not run a distro upgrade layer. See [OCI image annotations](https://github.com/opencontainers/image-spec/blob/main/annotations.md).

Running on a VPS?

See Hetzner (Docker VPS) and Docker VM Runtime for shared VM deployment steps including binary baking, persistence, and updates.

Agent sandbox

When agents.defaults.sandbox is enabled with the Docker backend, the gateway runs agent tool execution (shell, file read/write, etc.) inside isolated Docker containers while the gateway itself stays on the host. This gives you a hard wall around untrusted or multi-tenant agent sessions without containerizing the entire gateway.

Sandbox scope can be per-agent (default), per-session, or shared. Each scope gets its own workspace mounted at /workspace. You can also configure allow/deny tool policies, network isolation, resource limits, and browser containers.

For full configuration, images, security notes, and multi-agent profiles, see:

Quick enable

{
  agents: {
    defaults: {
      sandbox: {
        mode: "non-main", // off | non-main | all
        scope: "agent", // session | agent | shared
      },
    },
  },
}

Build the default sandbox image (from a source checkout):

scripts/sandbox-setup.sh

For npm installs without a source checkout, see Sandboxing § Images and setup for inline docker build commands.

Troubleshooting

Build the sandbox image with [`scripts/sandbox-setup.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/sandbox-setup.sh) (source checkout) or the inline `docker build` command from [Sandboxing § Images and setup](/gateway/sandboxing#images-and-setup) (npm install), or set `agents.defaults.sandbox.docker.image` to your custom image. Containers are auto-created per session on demand. Set `docker.user` to a UID:GID that matches your mounted workspace ownership, or chown the workspace folder. OpenClaw runs commands with `sh -lc` (login shell), which sources `/etc/profile` and may reset PATH. Set `docker.env.PATH` to prepend your custom tool paths, or add a script under `/etc/profile.d/` in your Dockerfile. The VM needs at least 2 GB RAM. Use a larger machine class and retry. Fetch a fresh dashboard link and approve the browser device:
```bash
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>
```

More detail: [Dashboard](/web/dashboard), [Devices](/cli/devices).
Reset gateway mode and bind:
```bash
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
```