Chapter 27: Self-Hosting with Docker
Running OpenClaw in Docker gives you a production-grade deployment with proper isolation, easy updates, and straightforward management. This chapter covers the official Docker image, Docker Compose setups, networking, persistent storage, Nginx reverse proxy, SSL, monitoring, backups, and troubleshooting.
Why Docker?
Running OpenClaw directly on a host works fine for local development. But for a production server that runs 24/7, Docker gives you:
- Isolation โ OpenClaw and its dependencies are contained; nothing conflicts with other software on the host
- Reproducibility โ The exact same image runs on any machine
- Easy updates โ Pull a new image and restart; no dependency management
- Rollback โ Pin to a previous image tag if an update causes problems
- Resource limits โ Cap CPU and RAM so one runaway process can't take down the whole server
The Official Docker Image
OpenClaw publishes an official multi-arch Docker image:
docker pull openclaw/openclaw:latest
Available tags:
| Tag | Description |
|---|---|
latest | Latest stable release |
1.4 | Major.minor pinned |
1.4.2 | Exact version pinned (recommended for production) |
nightly | Latest build from main branch (unstable) |
The image is built for linux/amd64 and linux/arm64 (including Raspberry Pi 4/5 and Apple Silicon in Linux VMs).
Check the current digest before deploying:
docker pull openclaw/openclaw:1.4.2
docker inspect openclaw/openclaw:1.4.2 --format '{{.Id}}'
Quick Start
docker run -d \
--name openclaw \
--restart unless-stopped \
-p 3000:3000 \
-v ~/.openclaw:/app/data \
-e ANTHROPIC_API_KEY=sk-ant-... \
-e TELEGRAM_BOT_TOKEN=1234567890:ABC... \
openclaw/openclaw:1.4.2
This mounts your ~/.openclaw directory into the container so your config and data persist across container restarts. The --restart unless-stopped flag means Docker automatically restarts the container after a crash or reboot.
Docker Compose (Recommended)
For a complete production setup with Redis for session storage:
# docker-compose.yml
version: '3.8'
services:
openclaw:
image: openclaw/openclaw:1.4.2
container_name: openclaw
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- ./config:/app/config:ro
- openclaw-data:/app/data
environment:
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
- SLACK_BOT_TOKEN=${SLACK_BOT_TOKEN}
- REDIS_URL=redis://redis:6379
- OPENCLAW_CONFIG=/app/config/openclaw.json
depends_on:
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/v1/health"]
interval: 30s
timeout: 10s
retries: 3
redis:
image: redis:7-alpine
container_name: openclaw-redis
restart: unless-stopped
volumes:
- redis-data:/data
command: redis-server --appendonly yes
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
openclaw-data:
redis-data:
Common commands:
# Start all services
docker compose up -d
# Stop all services
docker compose down
# View logs (follow)
docker compose logs -f openclaw
# View Redis logs
docker compose logs -f redis
# Restart only OpenClaw
docker compose restart openclaw
# Check status
docker compose ps
Configuration File Mount
Separate your secrets from your config. Place openclaw.json in ./config/ and keep secrets in .env:
project/
โโโ docker-compose.yml
โโโ .env โ secrets (never commit to git)
โโโ config/
โโโ openclaw.json โ config (safe to commit without secrets)
.env file:
ANTHROPIC_API_KEY=sk-ant-...
TELEGRAM_BOT_TOKEN=1234567890:ABC...
SLACK_BOT_TOKEN=xoxb-...
REDIS_PASSWORD=your-secure-redis-password
openclaw.json references env vars with ${}:
{
"agents": {
"default": {
"provider": "anthropic",
"apiKey": "${ANTHROPIC_API_KEY}"
}
},
"channels": {
"telegram": {
"enabled": true,
"token": "${TELEGRAM_BOT_TOKEN}"
}
}
}
Reverse Proxy with Nginx
For HTTPS and a custom domain, put Nginx in front of OpenClaw:
# /etc/nginx/sites-available/openclaw
server {
listen 80;
server_name bot.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name bot.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/bot.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/bot.yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# Increase timeout for long AI responses
proxy_read_timeout 120s;
proxy_send_timeout 120s;
location / {
proxy_pass http://localhost:3000;
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_cache_bypass $http_upgrade;
}
}
Enable the site and get a free SSL certificate:
sudo ln -s /etc/nginx/sites-available/openclaw /etc/nginx/sites-enabled/
sudo certbot --nginx -d bot.yourdomain.com
sudo nginx -t && sudo systemctl reload nginx
Certbot will auto-renew your certificate every 90 days.
Persistent Storage
The container writes data to /app/data. Always mount a named volume:
volumes:
- openclaw-data:/app/data
What lives in /app/data:
| Path | Content |
|---|---|
data/sessions/ | Session files (when using file storage) |
data/uploads/ | User-uploaded files and attachments |
data/canvas/ | Saved canvas sessions |
data/memory/ | Memory skill data |
data/logs/ | Application audit logs |
data/skills/ | Installed custom skills |
Never delete this volume unless you intend to reset all data. Back it up regularly.
Backups
Back up your volume nightly with a simple cron job:
# /etc/cron.d/openclaw-backup
0 3 * * * root docker run --rm \
-v openclaw-data:/data:ro \
-v /backups/openclaw:/backup \
alpine tar czf /backup/openclaw-$(date +%Y%m%d).tar.gz -C /data .
This creates a compressed archive of all data every night at 3 AM. Keep 30 days of backups:
# Clean up old backups
find /backups/openclaw -name "*.tar.gz" -mtime +30 -delete
Restore from backup:
docker run --rm \
-v openclaw-data:/data \
-v /backups/openclaw:/backup \
alpine tar xzf /backup/openclaw-20260430.tar.gz -C /data
Updates
# Pull the new image
docker compose pull
# Recreate containers (brief restart)
docker compose up -d --remove-orphans
# Verify the new version
docker compose exec openclaw openclaw --version
Automatic Updates with Watchtower
services:
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --interval 86400 --cleanup openclaw
restart: unless-stopped
Watchtower checks for new images every 24 hours and updates automatically. Use --interval 86400 (daily) rather than more frequent checks to avoid unnecessary pulls.
Warning: Automatic updates can break things. For production, consider disabling Watchtower and doing manual updates so you can read the changelog first.
Health Monitoring
The container exposes a health endpoint at /api/v1/health:
curl http://localhost:3000/api/v1/health
{
"status": "ok",
"version": "1.4.2",
"uptime": 86400,
"channels": {
"telegram": "connected",
"slack": "connected"
},
"redis": "connected",
"memoryMB": 128
}
Uptime Kuma (Recommended)
Add a new monitor in Uptime Kuma:
- Type: HTTP(S)
- URL:
http://localhost:3000/api/v1/health - Interval: 60 seconds
- Expected status code: 200
Prometheus
OpenClaw exposes Prometheus metrics at /metrics:
# prometheus.yml
scrape_configs:
- job_name: 'openclaw'
static_configs:
- targets: ['openclaw:3000']
scrape_interval: 15s
Key metrics:
| Metric | Description |
|---|---|
openclaw_messages_total | Total messages processed |
openclaw_tokens_total | Total tokens used (in/out) |
openclaw_active_sessions | Current active sessions |
openclaw_response_latency_seconds | Response time histogram |
openclaw_errors_total | Total errors by type |
Resource Limits
Set resource limits to prevent runaway containers from affecting the host:
services:
openclaw:
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
redis:
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
Typical resource usage:
- Idle: ~50 MB RAM, <1% CPU
- Active (few users): ~100-200 MB RAM, 10-30% CPU
- Under load: ~300-400 MB RAM, 50-80% CPU
Firewall Configuration
Only expose the ports you need:
# Allow SSH
ufw allow 22/tcp
# Allow HTTPS (Nginx handles this, not OpenClaw directly)
ufw allow 443/tcp
ufw allow 80/tcp
# Block direct access to OpenClaw port from outside
# (Nginx proxies it internally)
ufw deny 3000/tcp
ufw enable
Troubleshooting
Container keeps restarting:
docker compose logs --tail 50 openclaw
Look for Error: lines. Common causes: missing API key, wrong config path, port already in use.
Can't connect to Telegram/Slack:
docker compose exec openclaw openclaw channels status
Check that env vars are set correctly inside the container:
docker compose exec openclaw env | grep TOKEN
Redis connection refused:
docker compose exec redis redis-cli ping
If it returns PONG, Redis is running. Check REDIS_URL in your .env.
Out of memory:
docker stats openclaw
Increase the memory limit in docker-compose.yml or reduce maxContextTokens in your agent config.
Next: Chapter 28 โ Multi-Workspace Deployments โ Advanced patterns for organizations running many workspaces, teams, and use cases on a single gateway.