OpenClaw is an open-source AI agent platform (github.com/openclaw/openclaw, 373k+ GitHub stars). It is NOT the same as Hermes WebUI (nesquena/hermes-webui) — they are completely different products. OpenClaw uses Docker, Node.js/TypeScript, and its own skill system (ClawHub).
github.com/openclaw/openclaw) — AI agent platform with Docker gateway, browser automation, multi-provider LLM support, ClawHub skills marketplacegithub.com/nesquena/hermes-webui) — Web frontend for Hermes Agent, Python-based, uses bootstrap.pyBoth can coexist on the same server but serve different purposes. Do NOT confuse them.
docker pull ghcr.io/openclaw/openclaw:latest
mkdir -p /root/.openclaw/workspace /root/.openclaw-auth-profile-secrets
chmod -R 777 /root/.openclaw /root/.openclaw-auth-profile-secrets
Container runs as UID 1000 (node user) — needs write access
docker run --rm \
-v /root/.openclaw:/home/node/.openclaw \
-v /root/.openclaw/workspace:/home/node/.openclaw/workspace \
ghcr.io/openclaw/openclaw:latest \
node dist/index.js setup --non-interactive --accept-risk
This creates /root/.openclaw/openclaw.json with default configuration.
Edit /root/.openclaw/openclaw.json — add env block with API keys:
{
"defaultProvider": "chutes",
"defaultModel": "zai-org/GLM-5-Turbo",
"env": {
"OPENCLAW_API_KEY_CHUTES": "sk-your-chutes-key",
"OPENCLAW_API_KEY_GROQ": "gsk_your-groq-key",
"OPENCLAW_API_KEY_OPENROUTER": "sk-or-your-key"
},
"gateway": {
"controlUi": {
"allowedOrigins": ["https://your-domain.example.com"]
}
}
}
Provider name mapping — OpenClaw uses lowercase provider names as env var suffixes:
OPENCLAW_API_KEY_CHUTES → Chutes providerOPENCLAW_API_KEY_GROQ → GroqOPENCLAW_API_KEY_OPENROUTER → OpenRouterImportant: Use a working free provider (e.g., Chutes) as the default. Avoid providers that require special env vars (like opencode-go which needs a separate API key env var) unless that env var is also set.
# /opt/openclaw/docker-compose.yml
version: "3.8"
services:
gateway:
image: ghcr.io/openclaw/openclaw:latest
ports:
- "18789:8080"
volumes:
- /root/.openclaw:/home/node/.openclaw
- /root/.openclaw/workspace:/home/node/.openclaw/workspace
- /root/.openclaw-auth-profile-secrets:/home/node/.openclaw-auth-profile-secrets
- /etc/localtime:/etc/localtime:ro
environment:
- OPENCLAW_AUTH_PASSWORD=munaf
restart: unless-stopped
cd /opt/openclaw && docker compose up -d
curl -s http://localhost:18789/healthz
{"ok":true,"status":"live"}
docker logs openclaw-openclaw-gateway-1 --tail 10
Should show: [gateway] ready, [browser/server] Browser control listening
# List available skills
docker exec openclaw-openclaw-gateway-1 node dist/index.js skills list
Search for skills
docker exec openclaw-openclaw-gateway-1 node dist/index.js skills search "browser"
Install skills
docker exec openclaw-openclaw-gateway-1 node dist/index.js skills install bsession
docker exec openclaw-openclaw-gateway-1 node dist/index.js skills install github
docker exec openclaw-openclaw-gateway-1 node dist/index.js skills install research-agent
docker exec openclaw-openclaw-gateway-1 node dist/index.js skills install web-search
... etc
Skills install to /home/node/.openclaw/workspace/skills/ (mapped to /root/.openclaw/workspace/skills/ on host).
cd /opt/openclaw && docker compose restart
# /etc/nginx/sites-available/openclaw
server {
server_name oc.example.com;
location / {
proxy_pass http://127.0.0.1:18789;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
proxy_send_timeout 86400;
proxy_buffering off;
}
}
ln -sf /etc/nginx/sites-available/openclaw /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
certbot --nginx -d oc.example.com
Do NOT add nginx auth_basic — OpenClaw has built-in password auth (OPENCLAW_AUTH_PASSWORD env var). Adding nginx auth creates a double-login.
OpenClaw auto-detects a running CamoFox browser server. If CamoFox is on localhost:9377, the gateway log will show:
[browser/server] Browser control listening on http://127.0.0.1:18791/ (auth=password)
And the browser plugin appears in the gateway startup line.
To point OpenClaw at a remote CamoFox fleet, add browser config to openclaw.json:
{
"browser": {
"endpoints": [
{"url": "http://localhost:9377", "label": "US"},
{"url": "http://103.171.122.216:9377", "label": "PK"},
{"url": "http://103.171.122.217:9377", "label": "PK-2"}
]
}
}
Set OPENCLAW_AUTH_PASSWORD in the Docker Compose environment:
environment:
- OPENCLAW_AUTH_PASSWORD=munaf
When migrating API keys from a Hermes Agent config:
config.yaml → openclaw.json: Extract api_key fields from each provider and create OPENCLAW_API_KEY_{PROVIDER} env varsskills install.openclaw.json's systemPrompt field or use the web UI settingsImportant: Hermes .env values like OPENCODE_GO_API_KEY=* are MASKED — the real keys are in config.yaml under each provider's api_key: field. Always copy from config.yaml, not .env.
Also important: PyYAML's dump() mangles config files. Never round-trip a YAML config through yaml.safe_load() → yaml.dump(). Use scp for raw file transfer, then sed or Python string replacement for targeted edits.
/root/.openclaw/
├── openclaw.json # Main config (providers, model, system prompt)
├── workspace/
│ └── skills/ # Installed ClawHub skills
│ ├── bsession/
│ ├── github/
│ ├── research-agent/
│ └── ...
└── auth-profile-secrets/
/opt/openclaw/
├── docker-compose.yml
└── .env # OPENCLAW_API_KEY_* vars (alternative to openclaw.json env)
/root/.openclaw needs chmod 777 or the container can't write to it. Without write access, openclaw setup and skill installs fail silently.openclaw onboard fails in Docker — requires TTY. Use setup --non-interactive --accept-risk instead.openai/gpt-5.5 — this will fail without an OpenAI API key. Change defaultModel and defaultProvider in openclaw.json to a working free provider (e.g., chutes / zai-org/GLM-5-Turbo).docker compose restart needed for changes to openclaw.json or .env to take effect.skills install). Hermes skill files (SKILL.md) are NOT compatible with OpenClaw skills.skills install 404s for some names — not all skill names from skills list exist on ClawHub. Use skills search to find the correct installable name.apt-get install -y libgtk-3-0 libdbus-glib-1-2 libxt6 libasound2t64 libatk1.0-0 libatk-bridge2.0-0 libcups2 libxdamage1 libxrandr2 libxcomposite1 libxfixes3 libx11-xcb1 libxcb1 libxcursor1 libxi6 libpango-1.0-0 libpangocairo-1.0-0 libcairo2 libcairo-gobject2 libgdk-pixbuf-2.0-0 libfreetype6 libfontconfig1 libatk-adaptor libatspi2.0-0 xvfb. The /health endpoint works without these, but tab creation returns 500.sessionKey required for tab creation — POST /tabs returns {"error":"userId and sessionKey required"} if sessionKey is missing. Always include both: {"userId":"user1","sessionKey":"default"}.auth_basic in nginx if OpenClaw already has built-in auth (OPENCLAW_AUTH_PASSWORD). Use one or the other.proxy_set_header Upgrade and proxy_read_timeout 86400 are essential for the OpenClaw gateway's real-time features to work through nginx.openclaw.json (JSON), not YAML like Hermes Agent. Edit with jq or Python json module, not yaml.openclaw devices approve . Run this inside the container: docker exec openclaw-openclaw-gateway-1 node dist/index.js devices approve . Without this, the browser sees "Device pairing required" and cannot connect. See references/device-pairing.md for full workflow.bind must be lan for external/nginx access — if set to loopback (the default from setup), only localhost connections work. Set "bind": "lan" in openclaw.json gateway section for public access behind nginx.controlUi.allowedOrigins must include the external domain — After setup, the gateway only allows localhost origins. Add your domain to gateway.controlUi.allowedOrigins or the dashboard JS will be blocked: "allowedOrigins": ["https://oc.example.com", "http://localhost:18789"].openclaw.json "auth": {"mode": "password", "password": "munaf"} persists across container recreates. The OPENCLAW_AUTH_PASSWORD env var also works but is only in docker-compose.yml. Prefer the JSON config approach for persistence.setup command worked — docker run --rm -v ... ghcr.io/openclaw/openclaw:latest node dist/index.js setup --non-interactive --accept-risk generates the config. The onboard command requires TTY and fails in Docker even without -it./root/.openclaw/openclaw.json — Main config/opt/openclaw/docker-compose.yml — Docker Compose for gateway/opt/openclaw/.env — Environment vars (API keys)/root/.openclaw/workspace/skills/ — Installed ClawHub skillsAdd a healthcheck to your Compose service so Docker can detect gateway hangs and auto-restart:
services:
gateway:
# ... existing config ...
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8080/healthz"]
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
restart: unless-stopped
Without a healthcheck, Docker only restarts on process exit — not on deadlocks or silent hangs. The /healthz endpoint returns {"ok":true} when the gateway is live.
Use Docker Compose Watch to hot-reload openclaw.json changes without restarting the container:
services:
gateway:
# ... existing config ...
develop:
watch:
- path: /root/.openclaw/openclaw.json
action: sync
target: /home/node/.openclaw/openclaw.json
This is especially useful for iterative provider/model tuning — changes propagate in seconds instead of requiring docker compose restart.
The container runs as UID 1000 (node). chmod 777 works but is sloppy. Prefer targeted ownership:
# Instead of: chmod -R 777 /root/.openclaw
chown -R 1000:1000 /root/.openclaw /root/.openclaw-auth-profile-secrets
This grants the node user write access without making directories world-writable.
Since openclaw.json is JSON (not YAML), jq is the right tool for scripted edits:
# Change default provider
jq '.defaultProvider = "chutes"' /root/.openclaw/openclaw.json > /tmp/oc.json && mv /tmp/oc.json /root/.openclaw/openclaw.json
Add an API key
jq '.env.OPENCLAW_API_KEY_CHUTES = "sk-xxx"' /root/.openclaw/openclaw.json > /tmp/oc.json && mv /tmp/oc.json /root/.openclaw/openclaw.json
Set gateway bind to lan
jq '.gateway.bind = "lan"' /root/.openclaw/openclaw.json > /tmp/oc.json && mv /tmp/oc.json /root/.openclaw/openclaw.json
setup and skills install will fail silently — no error, just no output. Always verify ownership with ls -la /root/.openclaw/.environment: entries override env_file: entries when the same variable name appears in both. If you set OPENCLAW_AUTH_PASSWORD in both environment: and an .env file referenced by env_file:, the environment: value wins. This can be surprising when debugging auth issues — check which source actually provides the value.ClawHub has expanded from ~52 built-in skills to 200+ community and official skills. New categories include data-pipeline, devops-automate, ai-redteam, and browser-agent clusters. Search and install:
# Browse by category
docker exec openclaw-openclaw-gateway-1 node dist/index.js skills search "category:data-pipeline"
Install popular new skills
docker exec openclaw-openclaw-gateway-1 node dist/index.js skills install browser-agent
docker exec openclaw-openclaw-gateway-1 node dist/index.js skills install ai-redteam
docker exec openclaw-openclaw-gateway-1 node dist/index.js skills install devops-automate
The marketplace now supports skill dependencies — installing a skill auto-installs its prerequisites. Check for skill updates with skills update .
CamoFox integration improvements in 2025:
POST /tabs now supports sessionKey isolation per tab, enabling parallel browser sessions without state leakage"fingerprintRotation": true in browser config/health endpoint now returns browser engine version and active tab count ({"ok":true,"engine":"122.0","tabs":3})sessionPersistence: true is set in browser config{
"browser": {
"endpoints": [
{"url": "http://localhost:9377", "label": "local", "fingerprintRotation": true}
],
"sessionPersistence": true
}
}
The develop.watch section and healthchecks documented earlier are now the recommended deployment pattern. One addition: use depends_on with health conditions for startup ordering:
services:
gateway:
# ... existing config ...
depends_on:
redis:
condition: service_healthy
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
retries: 5
This ensures the gateway doesn't start accepting connections until Redis is ready.
references/device-pairing.md — Full workflow for approving new browsers that connect to the OpenClaw dashboard (required on first connection)templates/docker-compose.yml — Ready-to-use Docker Compose with correct volume mounts and env varsscripts/openclaw-healthcheck.sh — One-command deployment health check (container status, gateway health, model, skills, errors, pending device pairings)