$ ssh clawdbot.space --loading...
$ ssh clawdbot.space --loading...
Run OpenClaw in Docker containers — from first pull to production-grade compose stacks.
Docker is the recommended way to deploy OpenClaw in production. It provides isolation, reproducibility, and easy updates. This guide walks you through everything from a basic single-container setup to a full multi-service compose stack with monitoring, backups, and auto-restart.
docker pull ghcr.io/openclaw/openclaw:latest
mkdir -p ~/.openclaw && cd ~/.openclaw
docker run -d --name openclaw \ -p 127.0.0.1:18789:18789 \ -v ~/.openclaw:/app/data \ -e ANTHROPIC_API_KEY=sk-ant-xxx \ --restart unless-stopped \ ghcr.io/openclaw/openclaw:latest
docker logs -f openclaw
For production, use Docker Compose with proper resource limits, health checks, and logging.
version: "3.8"
services:
openclaw:
image: ghcr.io/openclaw/openclaw:latest
container_name: openclaw
restart: unless-stopped
ports:
- "127.0.0.1:18789:18789"
volumes:
- ./data:/app/data
- ./config:/app/config:ro
environment:
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- OPENCLAW_TZ=Asia/Shanghai
- OPENCLAW_LOG_LEVEL=info
deploy:
resources:
limits:
memory: 4g
cpus: "2.0"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:18789/health"]
interval: 30s
timeout: 10s
retries: 3
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"Add --read-only flag. Mount /tmp as tmpfs for temporary files.
--read-only --tmpfs /tmp:rw,noexec,nosuidRemove all Linux capabilities the container doesn't need.
--cap-drop=ALLRun as non-root inside the container.
--user 1000:1000Only bind to localhost. Never expose 18789 to 0.0.0.0.
-p 127.0.0.1:18789:18789Always mount /app/data to a host directory. This contains SOUL.md, memory, and all agent state.
Mount config files with :ro flag to prevent the agent from modifying its own configuration.
Use 'docker cp' or volume snapshots. Automate with cron: tar czf backup-$(date +%F).tar.gz ./data
Containerising the gateway is mostly an exercise in deciding what state lives outside the container. Get this wrong and the symptoms are delayed: everything works until the first rebuild, at which point the agent has forgotten who it is.
| Host path | Mounted at | Loses what if you skip it |
|---|---|---|
| $HOME/.openclaw | /home/node/.openclaw | Configuration and gateway state. Every pairing, every setting, gone on rebuild. |
| $HOME/.openclaw/workspace | /home/node/.openclaw/workspace | The agent's working files and any skills you wrote. |
| $HOME/.openclaw-auth-profile-secrets | /home/node/.config/openclaw | The recovery key. This one is not regenerable — treat it like an SSH private key. |
One constraint from the documentation is worth quoting because it is easy to get wrong and produces confusing failures: mount the gateway state as a directory, never as a single file. Bind-mounting an individual JSON file works right up until something rewrites it atomically, at which point the container is holding a stale inode and your edits appear to vanish.
The repository ships a compose file and a setup script, so the useful work is in the environment around them rather than in writing your own compose definition.
# The three paths the container expects on the host: export OPENCLAW_CONFIG_DIR="$HOME/.openclaw" export OPENCLAW_WORKSPACE_DIR="$HOME/.openclaw/workspace" export OPENCLAW_AUTH_PROFILE_SECRET_DIR="$HOME/.openclaw-auth-profile-secrets" # Use the prebuilt image rather than building locally: export OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:latest" # lan is the normal setting; loopback restricts access further: export OPENCLAW_GATEWAY_BIND=lan
Point at the prebuilt image unless you have a reason to build locally — building pulls in a toolchain you otherwise do not need on the host. `OPENCLAW_GATEWAY_BIND=lan` is the normal value; loopback restricts it further and is a reasonable default if you will only reach it through a tunnel.
./scripts/docker/setup.sh
This is the supported path. Hand-rolling compose is possible, and it means you own the mount semantics above, including the file-versus-directory trap.
docker compose ps docker compose logs --tail=100 openclaw-gateway curl -fsS http://127.0.0.1:18789/healthz
`ps` tells you the container is up, `logs` tells you it started cleanly, and the healthz endpoint tells you it is actually serving. A container can pass the first two and fail the third, which is exactly the state that looks fine and is not.
Administration happens through a second, short-lived container rather than by exec-ing into the running one. That keeps the gateway process undisturbed and means the CLI you run is the same version as the gateway.
docker compose run --rm openclaw-cli dashboard --no-open
`--no-open` matters on a headless host, where the CLI would otherwise try to launch a browser that does not exist.
docker compose run --rm openclaw-cli devices list docker compose run --rm openclaw-cli devices approve <requestId>
New devices need approval before they can talk to the gateway. If a request appears that you do not recognise, that is a security event, not a nuisance.
docker compose run --rm openclaw-cli doctor --json
`--json` is worth using here — container logs interleave and the structured output is far easier to search than a scrollback.
# Rebuilding without going through onboarding again: export OPENCLAW_SKIP_ONBOARDING=1 docker compose up -d --build
`OPENCLAW_SKIP_ONBOARDING=1` prevents the wizard from running again and overwriting a config you have since customised. This is the flag people find out about after losing a config.