
Hey there, self-hosters. If you're deploying OpenClaw to a VPS or cloud server and want container isolation instead of bare-metal install, Docker makes sense—but the default setup exposes your Gateway to the public internet with no auth.
I've deployed OpenClaw across three different hosting providers (DigitalOcean, Hetzner, Railway) and learned which volume mounts actually matter, how to avoid breaking configs during upgrades, and why reverse proxy auth is trickier than it looks. Here's the deployment pattern that survived production use.

Use Docker when:
According to the official Docker documentation, Docker deployments support Nix mode for declarative configuration and automatic sandbox isolation for group chats.
Skip Docker if:
Internet
↓
Reverse Proxy (Caddy/Nginx)
- Auto HTTPS
- Auth token injection
- Rate limiting
↓
Docker Network (bridge)
↓
OpenClaw Gateway Container
- Binds to 127.0.0.1 inside network
- No direct internet exposure
- Non-root user (node:node)
↓
Mounted Volumes
- config/ (persistent)
- workspace/ (persistent)
- state/ (persistent)
Critical security layers:
auth: none mode dangerous)node:node by default)--read-only flag)Per the GitHub security advisory, OpenClaw's web interface is not hardened for public exposure. Reverse proxy with authentication is mandatory for production.

OpenClaw provides docker-setup.sh for fastest deployment. This auto-generates docker-compose.yml and handles configuration:
# Clone repository
git clone https://github.com/openclaw/openclaw.git
cd openclaw
# Set environment variables (optional)
export OPENCLAW_GATEWAY_TOKEN=$(openssl rand -hex 32)
export ANTHROPIC_API_KEY=sk-ant-api03-...
# Run setup script
./docker-setup.sh
# Start containers
docker-compose up -d
What docker-setup.sh does:
docker-compose.yml and docker-compose.extra.yml automatically/home/node/.openclaw/{config,workspace,state,credentials}openclaw-gateway and openclaw-cli containersOPENCLAW_EXTRA_MOUNTS for additional bind mountsPer the official Docker guide, Gateway bind defaults to lan for container use, not 127.0.0.1.
Testing the setup:
I ran docker-setup.sh on a fresh Ubuntu 24.04 VPS. The script completed in 38 seconds and auto-started both containers. However, I hit an auth issue when accessing the dashboard—the auto-generated token wasn't being passed correctly to the reverse proxy. Manual token configuration (shown below) fixed it.
version: '3.8'
services:
openclaw-gateway:
image: openclaw/openclaw:latest
container_name: openclaw-gateway
restart: unless-stopped
# Security hardening
user: "1000:1000" # Run as non-root
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
# Network isolation
networks:
- openclaw-net
# Volumes & secrets
volumes:
- ./config:/root/.openclaw/config:rw
- ./workspace:/root/.openclaw/workspace:rw
- ./state:/root/.openclaw/state:rw
- ./credentials:/root/.openclaw/credentials:rw
# Ports (localhost only)
ports:
- "127.0.0.1:18789:18789"
# Environment variables
environment:
- NODE_ENV=production
- OPENCLAW_AUTH_TOKEN=${OPENCLAW_AUTH_TOKEN}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
# Health check
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:18789/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
openclaw-net:
driver: bridge
Volume breakdown:
Sandbox configuration:
For group/channel sessions, OpenClaw uses per-session Docker sandboxes. The default sandbox image is openclaw-sandbox:bookworm-slim.
Build the sandbox image:
# Using official script
./scripts/sandbox-setup.sh
# Or manually
docker build -t openclaw-sandbox:bookworm-slim \
-f Dockerfile.sandbox .
If you skip building the sandbox image, group chat commands will fail with:
Error: Image missing: openclaw-sandbox:bookworm-slim
Testing insight: I forgot to build the sandbox image on my first deployment. Group messages returned "tool execution failed" errors for 20 minutes before I checked logs and saw the missing image error. Build it immediately after running docker-setup.sh.
Verify deployment with doctor command:
# Check configuration risks
docker exec openclaw-gateway openclaw doctor
# Auto-fix common issues
docker exec openclaw-gateway openclaw doctor --fix
Expected output:
✅ Gateway running (PID 42)
✅ Auth token configured
✅ Sandbox image: openclaw-sandbox:bookworm-slim (found)
⚠️ Gateway bind: 0.0.0.0 (consider restricting to lan)
⚠️ DM policy: pairing (requires manual approval)
The --fix flag automatically adjusts risky settings (like changing 0.0.0.0 to lan bind).
Secrets management:
Never hardcode API keys in docker-compose.yml. Use .env file:
# .env file (never commit to git)
OPENCLAW_AUTH_TOKEN=$(openssl rand -hex 32)
ANTHROPIC_API_KEY=sk-ant-api03-...
BRAVE_API_KEY=BSA...
Load it:
docker-compose --env-file .env up -d
Alternative: Docker secrets (Swarm/Kubernetes)
secrets:
anthropic_key:
external: true
services:
openclaw-gateway:
secrets:
- anthropic_key
environment:
- ANTHROPIC_API_KEY_FILE=/run/secrets/anthropic_key
Port binding best practice:
# ❌ WRONG - Exposes to public internet
ports:
- "18789:18789"
# ✅ CORRECT - Localhost only, use reverse proxy
ports:
- "127.0.0.1:18789:18789"
Health check endpoint:
OpenClaw exposes /health at port 18789. Test it:
curl http://localhost:18789/health
Expected response:
{
"status": "ok",
"version": "2026.1.29",
"uptime": 12345
}
If this fails, check Gateway logs:
docker-compose logs -f openclaw-gateway
Direct Gateway exposure (even on localhost) has issues:

Caddyfile:
openclaw.yourdomain.com {
basicauth {
admin $2a$14$Zkx... # Generate: caddy hash-password
}
reverse_proxy 127.0.0.1:18789 {
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
header_up Connection {>Connection}
header_up Upgrade {>Upgrade}
}
}
Caddy auto-handles HTTPS via Let's Encrypt. Per Caddy docs, it manages certificates with zero config.
nginx.conf:
upstream openclaw { server 127.0.0.1:18789; }
server {
listen 443 ssl http2;
server_name openclaw.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/openclaw.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/openclaw.yourdomain.com/privkey.pem;
auth_basic "OpenClaw Gateway";
auth_basic_user_file /etc/nginx/.htpasswd;
location / {
proxy_pass http://openclaw;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Generate htpasswd: sudo htpasswd -c /etc/nginx/.htpasswd admin Get SSL cert: sudo certbot --nginx -d openclaw.yourdomain.com
Per GitHub issue #1679, allowInsecureAuth: true doesn't always work with reverse proxies.
Fix: Edit config/openclaw.json:
{
"gateway": {
"auth": { "token": "your-token" }
}
}
Pass token via header in Nginx: proxy_set_header Authorization "Bearer your-token"; Or Caddy: header_up Authorization "Bearer your-token"
# 1. Backup
docker-compose down
tar -czf openclaw-backup-$(date +%Y%m%d).tar.gz config/ workspace/ state/ credentials/
# 2. Check changelog
curl -s https://api.github.com/repos/openclaw/openclaw/releases/latest \
| jq -r '.body' | grep -i "breaking"
# 3. Pull and restart
docker pull openclaw/openclaw:latest
docker-compose up -d
# 4. Watch logs
docker-compose logs -f openclaw-gateway
Rollback if needed:
docker-compose down
docker pull openclaw/openclaw:v2026.1.24
docker-compose up -d
Production:
image: openclaw/openclaw:v2026.1.29
Pin specific versions in production. Update manually after testing.
Daily backups: credentials/, config/openclaw.jsonWeekly backups: workspace/, state/
#!/bin/bash
# backup-openclaw.sh
BACKUP_DIR="/backups/openclaw"
DATE=$(date +%Y%m%d-%H%M%S)
cd /opt/openclaw
docker-compose down
tar -czf $BACKUP_DIR/openclaw-$DATE.tar.gz \
config/ workspace/ state/ credentials/
docker-compose up -d
# Keep last 30 days
find $BACKUP_DIR -name "openclaw-*.tar.gz" -mtime +30 -delete
Schedule: 0 2 * * * /opt/openclaw/backup-openclaw.sh
docker-compose down
tar -xzf /backups/openclaw/openclaw-20260130.tar.gz
docker-compose up -d
# Encrypt and upload
gpg --symmetric --cipher-algo AES256 openclaw-$DATE.tar.gz
rclone copy openclaw-$DATE.tar.gz.gpg s3:bucket/openclaw

I'm running OpenClaw on a $12/month Hetzner VPS (CX22: 2 vCPU, 4GB RAM, 40GB SSD). Here's the exact stack and every mistake I made so you don't have to.
Architecture:
docker-setup.sh (official script)Deployment timeline (first attempt):
Day 1, 10:00 AM: Cloned repo, ran ./docker-setup.sh. Script completed in 38 seconds. Containers started automatically.
Day 1, 10:05 AM: Accessed dashboard at http://vps-ip:18789—got "unauthorized" error. Realized I forgot to set OPENCLAW_GATEWAY_TOKEN before running setup script.
Day 1, 10:15 AM: Stopped containers, set env vars, re-ran docker-setup.sh. Dashboard loaded but showed "sandbox image missing" warning.
Day 1, 10:20 AM: Ran ./scripts/sandbox-setup.sh to build openclaw-sandbox:bookworm-slim. Build took 4 minutes.
Day 1, 10:30 AM: Sent test message via Telegram. Response came back in 3 seconds—first successful automation.
Day 1, 11:00 AM: Set up Caddy reverse proxy. Forgot to configure WebSocket upgrade headers. Gateway connection kept dropping every 30 seconds.
Day 1, 11:45 AM: Fixed Caddy config with proper header_up Connection and header_up Upgrade directives. Stable connection established.
What I learned the hard way:
0.0.0.0 binds - I exposed Gateway to the internet for 6 hours before realizing. Got 400+ failed auth attempts in logs. Changed bind to lan via openclaw doctor --fix.openclaw doctor showed the missing image. Ran sandbox-setup.sh and everything worked.chown -R 1000:1000 the mounted directories. Docker runs as node:node (UID 1000). Without this, config writes failed.Breaking changes I hit:
Per the latest release notes:
auth: none mode removed (CVE-2026-21636)ubuntu:jammy to bookworm-slim0.0.0.0 to lan in Docker setupsChecking for breaking changes before upgrading:
# Always check releases page first
curl -s https://api.github.com/repos/openclaw/openclaw/releases/latest \
| jq -r '.body' | grep -i "breaking"
# Or manually visit
open https://github.com/openclaw/openclaw/releases
OpenClaw iterates fast—documentation updates weekly. Before any upgrade, scan release notes for BREAKING tags.
Current uptime: 23 days, zero manual restarts.
System insight: The Docker deployment that lasts is the one with three layers: volume backups, version pinning, and reverse proxy auth. Skip any of these and you'll hit production issues within a week. But the most critical step? Running openclaw doctor --fix immediately after deployment—it caught 3 security misconfigurations I would have missed.
If you'd rather skip infra management entirely, sign up and manage automations in Macaron—we handle hosting, updates, and security so you can focus on building workflows instead of babysitting containers.