15 KiB
OpenClaw Cluster Deployment Guide
This document describes the deployment setup for running OpenClaw Gateway on a Docker Swarm cluster using Dokploy.
Overview
- Gateway URL:
wss://klaatu.bendtstudio.com - Registry:
registry.lan:5000(internal) - Image:
registry.lan/openclaw:latest - Node:
tpi-n1(192.168.2.130)
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Docker Swarm │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Node: tpi-n1 (Manager + Worker) │ │
│ │ ┌────────────────────────────────────────────────┐ │ │
│ │ │ OpenClaw Gateway Service │ │ │
│ │ │ - Ports: 18789 (WebSocket), 18790 (Bridge) │ │ │
│ │ │ - Image: registry.lan/openclaw:latest │ │ │
│ │ │ - Volumes: │ │ │
│ │ │ - openclaw-config → /home/node/.openclaw │ │ │
│ │ │ - openclaw-workspace → /home/node/.openclaw/workspace │ │ │
│ │ └────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Registry Service (on tpi-n1) │ │
│ │ - URL: registry.lan:5000 │ │
│ │ - Accessible via Traefik at registry.lan:443 │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Pre-requisites
1. Registry Setup
Ensure the Docker registry is running and accessible:
# Check registry is running
docker ps | grep registry
# Check registry is accessible
curl -sk https://registry.lan/v2/_catalog
2. DNS Configuration
All nodes must resolve registry.lan to the registry IP:
# On each node
echo "192.168.2.130 registry.lan" | sudo tee -a /etc/hosts
# Verify
ping registry.lan
3. Registry Authentication
All nodes must be logged into the registry:
docker login registry.lan
# Username: docker
# Password: password
Building and Pushing the Image
Dockerfile Requirements
The Dockerfile must include:
FROM node:22-bookworm
# Install Bun
RUN curl -fsSL https://bun.sh/install | bash
ENV PATH="/root/.bun/bin:${PATH}"
RUN corepack enable
# Install gogcli (optional, for Google Workspace integration)
RUN curl -L -o /tmp/gogcli.tar.gz \
"https://github.com/steipete/gogcli/releases/download/v0.11.0/gogcli_0.11.0_linux_arm64.tar.gz" && \
tar -xzf /tmp/gogcli.tar.gz -C /tmp && \
mv /tmp/gog /usr/local/bin/gog && \
chmod +x /usr/local/bin/gog && \
rm -f /tmp/gogcli.tar.gz
# Install gcloud SDK (optional, for GCP integration)
RUN curl -L -o /tmp/google-cloud-sdk.tar.gz \
"https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-linux-arm.tar.gz" && \
tar -xzf /tmp/google-cloud-sdk.tar.gz -C /opt/ && \
/opt/google-cloud-sdk/install.sh --quiet --path-update=true --command-completion=false --usage-reporting=false && \
rm -f /tmp/google-cloud-sdk.tar.gz
ENV PATH="/opt/google-cloud-sdk/bin:${PATH}"
# ... rest of build steps
Build and Push
On the registry node (tpi-n1):
cd ~/openclaw
# Build image
docker build -t registry.lan/openclaw:latest .
# Push to registry
docker push registry.lan/openclaw:latest
Note: The push may fail on large layers. If so, build directly on the registry node.
Stack Configuration
stack.yml
version: "3.8"
services:
openclaw-gateway:
image: ${OPENCLAW_IMAGE:-registry.lan/openclaw:latest}
environment:
HOME: /home/node
TERM: xterm-256color
MOONSHOT_API_KEY: ${MOONSHOT_API_KEY}
OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN}
volumes:
- openclaw-config:/home/node/.openclaw
- openclaw-workspace:/home/node/.openclaw/workspace
ports:
- target: 18789
published: ${OPENCLAW_GATEWAY_PORT:-18789}
protocol: tcp
mode: host
- target: 18790
published: ${OPENCLAW_BRIDGE_PORT:-18790}
protocol: tcp
mode: host
init: true
deploy:
replicas: 1
placement:
constraints:
- node.hostname == tpi-n1
networks:
- dokploy-network
command:
[
"node",
"dist/index.js",
"gateway",
"--bind",
"lan",
"--port",
"18789",
]
volumes:
openclaw-config:
openclaw-workspace:
networks:
dokploy-network:
external: true
Environment Variables
Create a .env file:
OPENCLAW_IMAGE=registry.lan/openclaw:latest
OPENCLAW_GATEWAY_TOKEN=your-generated-token
MOONSHOT_API_KEY=your-moonshot-api-key
OPENCLAW_GATEWAY_PORT=18789
OPENCLAW_BRIDGE_PORT=18790
OPENCLAW_GATEWAY_BIND=lan
Generate gateway token:
openssl rand -hex 32
Configuration Setup
1. Seeding the Config Volume
Before first deployment, seed the config volume:
# On manager node (tpi-n1)
# Create config file
cat > /tmp/openclaw-config.json << 'EOF'
{
"env": {
"MOONSHOT_API_KEY": "${MOONSHOT_API_KEY}",
"OPENCLAW_GATEWAY_TOKEN": "${OPENCLAW_GATEWAY_TOKEN}"
},
"gateway": {
"port": 18789,
"bind": "lan",
"mode": "local"
},
"agents": {
"defaults": {
"model": {
"primary": "moonshot/kimi-k2.5"
},
"models": {
"moonshot/kimi-k2.5": {
"alias": "Kimi K2.5"
}
}
}
},
"models": {
"providers": {
"moonshot": {
"baseUrl": "https://api.moonshot.ai/v1",
"apiKey": "${MOONSHOT_API_KEY}",
"api": "openai-completions",
"models": [
{
"id": "kimi-k2.5",
"name": "Kimi K2.5",
"reasoning": false,
"input": ["text"],
"cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0},
"contextWindow": 256000,
"maxTokens": 8192
}
]
}
}
}
}
EOF
# Copy to volume
docker run --rm \
-v openclaw-app-ceptgi_openclaw-config:/data \
-v /tmp/openclaw-config.json:/config.json \
alpine:latest \
sh -c "cp /config.json /data/openclaw.json && chown -R 1000:1000 /data && chmod 755 /data"
2. Fixing Workspace Permissions
After deployment, fix workspace permissions:
# Fix permissions on both volumes
docker run --rm \
-v openclaw-app-ceptgi_openclaw-config:/data \
alpine:latest \
sh -c "chown -R 1000:1000 /data && chmod 755 /data"
docker run --rm \
-v openclaw-app-ceptgi_openclaw-workspace:/data \
alpine:latest \
sh -c "chown -R 1000:1000 /data && chmod 755 /data"
Pairing and Device Approval
Automatic Device Approval
When web UI shows "pairing required", approve devices:
# SSH into container
docker exec -it CONTAINER_NAME /bin/sh
# List pending devices
cat /home/node/.openclaw/devices/pending.json
# Manually approve by creating paired.json
cat > /home/node/.openclaw/devices/paired.json << 'EOF'
{
"DEVICE_ID": {
"deviceId": "DEVICE_ID",
"publicKey": "PUBLIC_KEY",
"platform": "MacIntel",
"clientId": "openclaw-control-ui",
"clientMode": "webchat",
"role": "operator",
"roles": ["operator"],
"scopes": ["operator.admin", "operator.approvals", "operator.pairing"],
"token": "approved-token",
"ts": TIMESTAMP,
"pairedAt": TIMESTAMP
}
}
EOF
# Clear pending
echo '{}' > /home/node/.openclaw/devices/pending.json
Using CLI to Approve
# From within container
export JAVA_HOME=/home/node/.local/jre
export PATH=$JAVA_HOME/bin:/home/node/.local/bin:$PATH
node /app/openclaw.mjs devices approve DEVICE_ID --token $OPENCLAW_GATEWAY_TOKEN
Deploying Updates
Method 1: Manual Rebuild and Deploy
# On registry node (tpi-n1)
cd ~/openclaw
# Build new image
docker build -t registry.lan/openclaw:latest .
# Push to registry (may fail on large layers)
docker push registry.lan/openclaw:latest
# Force service update to pull new image
docker service update --force openclaw-app-ceptgi_openclaw-gateway
Method 2: Via Dokploy Webhook
# Trigger dokploy deployment
curl -X POST http://dokploy.lan:3000/api/deploy/compose/JrrUnF7-O3tJDK02wUhUG
Note: Dokploy pulls the stack.yml from git. Make sure the image reference is updated in stack.yml before triggering.
Troubleshooting
Image Push Fails on Large Layers
Problem: Docker push fails on large layers.
Solution: Build directly on the registry node:
ssh ubuntu@192.168.2.130
cd ~/openclaw
docker build -t registry.lan/openclaw:latest .
# No push needed since registry is local
Container Can't Access Registry
Problem: "No such image: registry.lan/openclaw:latest"
Solution:
- Ensure registry.lan resolves on all nodes
- Ensure all nodes are logged into registry
- Check insecure registry settings in daemon.json
Pairing Required Error
Problem: Web UI shows "disconnected (1008): pairing required"
Solution:
- Use the built-in CLI approver (recommended):
# approve newest pending request
docker exec CONTAINER_NAME node dist/index.js devices approve --latest --token "$OPENCLAW_GATEWAY_TOKEN"
# inspect current state
docker exec CONTAINER_NAME node dist/index.js devices list --token "$OPENCLAW_GATEWAY_TOKEN"
- If using Tailscale Serve, ensure Gateway auth/proxy settings are correct:
{
"gateway": {
"bind": "loopback",
"tailscale": { "mode": "serve" },
"auth": { "allowTailscale": true },
"trustedProxies": ["127.0.0.1", "::1"]
}
}
- If error changes to
device token mismatch, the browser usually has stale local state.- Open the Control UI in an Incognito/Private window.
- Re-paste gateway token in settings, or open a tokenized URL from:
docker exec CONTAINER_NAME node dist/index.js dashboard --no-open
- If needed, only clear pending requests (not full config):
docker exec CONTAINER_NAME sh -c 'echo {} > /home/node/.openclaw/devices/pending.json'
Notes:
- Remote browsers (LAN/Tailscale) still require one-time device pairing.
- Localhost (
127.0.0.1) auto-approves. - Config edits trigger Gateway reload/restart automatically; container restart is usually unnecessary.
Permission Denied Errors
Problem: EACCES: permission denied, open '/home/node/.openclaw/workspace/...'
Solution: Fix volume permissions (see "Fixing Workspace Permissions" above)
Service Won't Start
Problem: Service stuck in "no suitable node" state
Solution:
# Check placement constraints
docker service inspect openclaw-app-ceptgi_openclaw-gateway | grep -A10 placement
# Verify node labels
docker node ls
docker node inspect tpi-n1 | grep -A10 Labels
# Remove constraint temporarily for debugging
docker service update --constraint-rm 'node.hostname == tpi-n1' openclaw-app-ceptgi_openclaw-gateway
Monitoring
Check Service Status
docker service ps openclaw-app-ceptgi_openclaw-gateway
docker service logs openclaw-app-ceptgi_openclaw-gateway --tail 50
Check Container Health
CONTAINER=$(docker ps --filter "name=openclaw-app-ceptgi" --format "{{.Names}}" | head -1)
docker logs $CONTAINER --tail 50
docker exec $CONTAINER ps aux
Test Gateway
# From any node
curl -s http://tpi-n1:18789/ | grep "<title>"
Additional Tools
Installing signal-cli (Temporary)
Note: Manual installations are lost on container restart. Add to Dockerfile for persistence.
# Download JRE
mkdir -p /home/node/.local/jre
cd /tmp
curl -L -o jre.tar.gz \
'https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.5%2B11/OpenJDK21U-jre_aarch64_linux_hotspot_21.0.5_11.tar.gz'
tar -xzf jre.tar.gz -C /home/node/.local/jre/ --strip-components=1
# Download signal-cli
mkdir -p /home/node/.local/bin /home/node/.local/share
curl -L -o signal-cli.tar.gz \
'https://github.com/AsamK/signal-cli/releases/download/v0.13.24/signal-cli-0.13.24.tar.gz'
tar -xzf signal-cli.tar.gz -C /home/node/.local/share/
ln -sf /home/node/.local/share/signal-cli-0.13.24/bin/signal-cli /home/node/.local/bin/signal-cli
# Set environment
export JAVA_HOME=/home/node/.local/jre
export PATH=$JAVA_HOME/bin:/home/node/.local/bin:$PATH
Using gogcli
# Configure credentials
gog auth credentials /home/node/.openclaw/client_secret.json
# Add account (requires browser for OAuth)
gog auth add user@gmail.com --services gmail,calendar,drive,contacts,docs,sheets
# List accounts
gog auth list
Security Notes
- Token Storage: Gateway token is stored in environment variables and config file
- Pairing: Devices must be explicitly approved before connecting
- Network: Gateway binds to
0.0.0.0(lan mode) - ensure firewall rules are in place - Registry: Use HTTPS for registry access in production
Maintenance
Updating Moonshot API Key
# Update config in volume
docker run --rm \
-v openclaw-app-ceptgi_openclaw-config:/data \
alpine:latest \
sh -c "sed -i 's/old-api-key/new-api-key/g' /data/openclaw.json"
# Restart service
docker service update --force openclaw-app-ceptgi_openclaw-gateway
Backup Volumes
# Backup config
docker run --rm \
-v openclaw-app-ceptgi_openclaw-config:/data \
-v /backup:/backup \
alpine:latest \
tar -czf /backup/openclaw-config-$(date +%Y%m%d).tar.gz -C /data .
# Backup workspace
docker run --rm \
-v openclaw-app-ceptgi_openclaw-workspace:/data \
-v /backup:/backup \
alpine:latest \
tar -czf /backup/openclaw-workspace-$(date +%Y%m%d).tar.gz -C /data .
Support
- OpenClaw Docs: https://docs.openclaw.ai
- Gateway Docs: https://docs.openclaw.ai/gateway
- Moonshot API: https://platform.moonshot.ai
- Signal-cli: https://github.com/AsamK/signal-cli
- gogcli: https://github.com/steipete/gogcli