Guide
OpenClaw with docker compose
OpenClaw ships a docker-compose.yml in its repository root, and it is more opinionated than most people expect — two services, three published ports, and a set of environment pins that exist to work around a real bug. This guide explains what each part does and which lines you must not delete.
Short answer: the compose file defines two services sharing one image — openclaw-gateway (the long-running agent, publishing port 18789) and openclaw-cli (a one-shot admin container that joins the gateway's network namespace). Mount ~/.openclaw to /home/node/.openclaw, keep the OPENCLAW_*_DIR pins, and use docker compose run --rm openclaw-cli for CLI work.
The Two Services
People often try to run everything through one container and then wonder why CLI commands interfere with the running gateway. The upstream layout deliberately splits them:
| Service | Role |
|---|---|
openclaw-gateway | The long-running process. Starts the WebSocket gateway, holds channel connections, publishes ports, has the healthcheck and restart: unless-stopped. |
openclaw-cli | Admin/one-shot container on the same network namespace (network_mode: "service:openclaw-gateway"). Run interactive commands here so you never restart the gateway to run a CLI task. |
Ports
| Port | Variable | Purpose |
|---|---|---|
| 18789 | OPENCLAW_GATEWAY_PORT | WebSocket gateway / web UI |
| 18790 | OPENCLAW_BRIDGE_PORT | Bridge port |
| 3978 | OPENCLAW_MSTEAMS_PORT | Microsoft Teams channel |
Only 18789 matters for most deployments. If you are not using Teams, you can drop the 3978 mapping rather than exposing a port you never serve.
Volumes
volumes:
- "${OPENCLAW_CONFIG_DIR:-${HOME:-/tmp}/.openclaw}:/home/node/.openclaw"
- "${OPENCLAW_WORKSPACE_DIR:-${HOME:-/tmp}/.openclaw/workspace}:/home/node/.openclaw/workspace"
- "${OPENCLAW_AUTH_PROFILE_SECRET_DIR:-${HOME:-/tmp}/.openclaw-auth-profile-secrets}:/home/node/.config/openclaw"Three mounts: config and state, the workspace, and auth profile secrets kept separate from the rest of the config. Losing the first one loses your pairing state and chat history, which is the most common “why did my bot forget everything” report.
The Gotcha That Breaks First Replies
The compose file pins four environment variables that look redundant:
OPENCLAW_STATE_DIR: /home/node/.openclaw
OPENCLAW_CONFIG_PATH: /home/node/.openclaw/openclaw.json
OPENCLAW_CONFIG_DIR: /home/node/.openclaw
OPENCLAW_WORKSPACE_DIR: /home/node/.openclaw/workspaceDo not delete these. Compose reads your .env file to resolve the bind mount sources on the host — but env_file also imports those same values into the container. Without the pins, a macOS host path like /Users/you/.openclaw/... leaks into code running inside a Linux container, which then tries to create /Users and fails with an EACCES error on the very first reply. This is upstream issue #77436, and the pins are the fix.
If your containerised agent starts cleanly but dies the moment someone sends the first message, check for a host path in the container environment before anything else.
Running CLI Commands
Because openclaw-cli shares the gateway's network namespace, it can talk to the gateway on localhost without extra networking:
# One-off command, container removed afterwards
docker compose run --rm openclaw-cli openclaw status
# Interactive shell
docker compose run --rm openclaw-cli bashThe CLI service also sets BROWSER: echo so commands that would normally try to open a browser print the URL instead — which is what you want on a headless server.
Health Check and Restarts
restart: unless-stopped
healthcheck:
test: ["CMD", "node", "dist/docker-healthcheck.js"]
interval: 30s
timeout: 5s
retries: 5
start_period: 20sThe 20 second start_period matters: OpenClaw needs time to load config and connect channels, and a shorter grace period makes Docker mark a perfectly healthy container as unhealthy during startup. Because the container is supervised by Docker, you should not also install a systemd service inside it — see the daemon guide for when that command is appropriate.
Security Defaults Worth Keeping
cap_drop: [NET_RAW, NET_ADMIN]— the agent never needs raw socketssecurity_opt: [no-new-privileges:true]— blocks privilege escalation via setuid binariesinit: true— proper PID 1 so signals and zombie processes are handled
The Docker socket mount is commented out by default. Leave it that way unless you are deliberately enabling sandbox isolation — mounting /var/run/docker.sock gives the container effective root on the host.
Reaching Ollama or LM Studio on the Host
The compose file sets extra_hosts: ["host.docker.internal:host-gateway"], which makes that hostname resolve on Linux Docker Engine too, not just Docker Desktop. Point local model providers at http://host.docker.internal:11434 rather than localhost, which inside the container refers to the container itself.
Bring It Up
# Build or pull, then start in the background
docker compose up -d
# Watch the gateway come up
docker compose logs -f openclaw-gateway
# Confirm health
docker compose psThen open http://localhost:18789 to reach the gateway UI.
Or Skip Compose Entirely
OpenClaw Launch runs this same container layout as a managed service with health checks, backups and TLS already wired up. If you would rather self-host, see the OpenClaw Docker guide, system requirements, and VPS recommendations.