services: openclaw: image: ghcr.io/openclaw/openclaw:2026.10.3 restart: unless-stopped environment: OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN} OPENAI_API_KEY: ${OPENAI_API_KEY:-} ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-} # The gateway answers 403 until its config names the proxy's network and the # origin the Control UI is opened from, and it has no flag or variable for # either. So the config is written once, into the state volume, where the app # keeps and rewrites it; then the image's own entrypoint starts the gateway. command: - sh - -c - | set -e cfg=/home/node/.openclaw/openclaw.json if [ ! -s "$$cfg" ]; then cat > "$$cfg" <<'JSON' { "gateway": { "mode": "local", "bind": "lan", "auth": { "mode": "token" }, "trustedProxies": ["10.192.0.0/10"], "controlUi": { "allowedOrigins": ["${CONTROL_UI_ORIGIN}"] } } } JSON chmod 600 "$$cfg" fi if [ -f /app/docker-entrypoint.mjs ]; then exec node /app/docker-entrypoint.mjs node openclaw.mjs gateway --allow-unconfigured --bind lan --port 18789 fi exec node openclaw.mjs gateway --allow-unconfigured --bind lan --port 18789 volumes: - state:/home/node/.openclaw - auth-keys:/home/node/.config/openclaw # The image's own check runs every 3 minutes, so a deploy would wait that long. healthcheck: test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:18789/healthz').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))"] interval: 30s timeout: 10s retries: 3 start_period: 5m start_interval: 5s volumes: state: auth-keys: