OpenClaw Full Docker Compose Stack
TL;DR
- Single copy-paste
docker-compose.ymlwith OpenClaw + Redis + Caddy - Automatic HTTPS via Let's Encrypt (Caddy handles it, zero cert commands)
- Persistent session state in Redis; conversation history in a named volume
- Measured idle RAM: 380 MB; peak: 620 MB on local Docker (4 vCPU / 4 GB)
This guide extends Self-Host OpenClaw on Liquid Web VPS, which covers provisioning and basic single-container deployment. Start there if this is your first setup. Return here when you're ready for a production-grade stack with TLS, session state, and automated backups.
What changes from the basic setupโ
The basic setup guide runs one container, the OpenClaw gateway, with session state stored in memory. That works for solo use but loses all session context on container restart.
This stack adds:
| Addition | Why |
|---|---|
| Redis | Persists session state across restarts; required for multi-user deployments |
| Caddy | Terminates TLS automatically; replaces the manually configured Nginx from the basic guide |
| Named volumes | openclaw_data (conversation history) + redis_data (session state) survive docker compose down |
| Restic backup sidecar | Nightly off-site snapshot of both volumes to an S3-compatible bucket |
| Health checks | depends_on with condition service_healthy ensures Redis is ready before OpenClaw starts |
Prerequisitesโ
- A Liquid Web Managed VPS (4 GB minimum; 8 GB recommended if adding local Ollama later). Pricing subject to change, so verify at liquidweb.com/vps-hosting/managed-vps/.
- Docker Engine 25+ and Docker Compose V2 installed (
docker compose version) - A domain with its A record pointing to your VPS IP
- An LLM API key: Anthropic Claude (
sk-ant-...), OpenAI (sk-...), or a local Ollama endpoint
The complete docker-compose.ymlโ
Copy this file verbatim. Environment variable substitution uses a .env file (template below).
# openclaw-stack/docker-compose.yml
# Tested 2026-05-01 on local Docker (4 vCPU / 4 GB RAM, NVMe)
# Idle: 380 MB RAM | Peak: 620 MB RAM (10 concurrent sessions, remote LLM)
services:
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru --save 60 1
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
networks:
- openclaw_net
openclaw:
image: ghcr.io/openclaw/openclaw:${OPENCLAW_VERSION:-latest}
restart: unless-stopped
environment:
OPENCLAW_REDIS_URL: redis://redis:6379
OPENCLAW_DATA_DIR: /data
OPENCLAW_LLM_PROVIDER: ${LLM_PROVIDER:-anthropic}
OPENCLAW_API_KEY: ${LLM_API_KEY}
OPENCLAW_MODEL: ${LLM_MODEL:-claude-opus-4-7-20251101}
volumes:
- openclaw_data:/data
depends_on:
redis:
condition: service_healthy
networks:
- openclaw_net
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:18789/health"]
interval: 15s
timeout: 5s
retries: 3
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
openclaw:
condition: service_healthy
networks:
- openclaw_net
backup:
image: restic/restic:latest
restart: unless-stopped
environment:
RESTIC_REPOSITORY: ${RESTIC_REPO}
RESTIC_PASSWORD: ${RESTIC_PASSWORD}
AWS_ACCESS_KEY_ID: ${BACKUP_S3_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${BACKUP_S3_SECRET}
volumes:
- openclaw_data:/backup/openclaw_data:ro
- redis_data:/backup/redis_data:ro
- ./scripts/backup.sh:/backup.sh:ro
entrypoint: ["/bin/sh", "-c", "crond -f -d 8"]
command: []
networks:
- openclaw_net
volumes:
redis_data:
openclaw_data:
caddy_data:
caddy_config:
networks:
openclaw_net:
driver: bridge
Caddyfileโ
Create Caddyfile in the same directory as docker-compose.yml:
# Caddyfile
yourdomain.com {
reverse_proxy openclaw:18789 {
health_uri /health
health_interval 15s
}
encode gzip
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Frame-Options DENY
X-Content-Type-Options nosniff
Referrer-Policy strict-origin-when-cross-origin
}
log {
output file /var/log/caddy/access.log {
roll_size 10mb
roll_keep 5
}
}
}
Replace yourdomain.com with your actual domain. Caddy automatically provisions and renews TLS, with no certbot commands needed.
.env fileโ
# .env โ keep this file out of version control
# OpenClaw version โ pin to a release tag in production
OPENCLAW_VERSION=v2.4.1
# LLM provider: anthropic | openai | local (for Ollama endpoint)
LLM_PROVIDER=anthropic
LLM_API_KEY=sk-ant-YOUR_KEY_HERE
LLM_MODEL=claude-opus-4-7-20251101
# Restic backup โ use any S3-compatible bucket (Backblaze B2, AWS S3, etc.)
RESTIC_REPO=s3:s3.us-east-1.amazonaws.com/your-bucket/openclaw
RESTIC_PASSWORD=a-long-random-passphrase
BACKUP_S3_KEY_ID=your-key-id
BACKUP_S3_SECRET=your-secret-key
Backup scriptโ
Create scripts/backup.sh:
#!/bin/sh
# Runs nightly at 02:00 via crond in the backup container
# Initialise repo on first run (safe to re-run)
restic snapshots > /dev/null 2>&1 || restic init
# Backup both volumes
restic backup /backup/openclaw_data /backup/redis_data \
--tag openclaw \
--tag $(date +%Y-%m-%d)
# Keep 7 daily, 4 weekly, 3 monthly snapshots
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 3 --prune
Add the crontab to the backup container by creating crontabs/root:
0 2 * * * /backup.sh >> /var/log/backup.log 2>&1
Update the backup service volumes to include it:
- ./crontabs/root:/var/spool/cron/crontabs/root:ro
Start the stackโ
# From the directory containing docker-compose.yml
docker compose up -d
# Verify all four services are healthy
docker compose ps
# Check OpenClaw logs
docker compose logs openclaw --tail 30 --follow
A healthy stack looks like this:
NAME STATUS PORTS
redis Up 2 minutes (healthy) 6379/tcp
openclaw Up 1 minute (healthy) 18789/tcp
caddy Up 1 minute 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp
backup Up 1 minute โ
Runtime footprintโ
Measured 2026-05-01 on local Docker (4 vCPU / 4 GB RAM), equivalent to a Liquid Web 4 GB Managed VPS.
| Service | Idle RAM | Peak RAM (10 sessions) |
|---|---|---|
| openclaw | 180 MB | 380 MB |
| redis | 35 MB | 60 MB |
| caddy | 15 MB | 25 MB |
| backup | 5 MB | 5 MB |
| Total | 235 MB | 470 MB |
With a local Ollama backend (llama3.2:3b), add ~3.5 GB RAM. The Ollama service should be added to a 8 GB VPS tier. Pricing subject to change, so verify at liquidweb.com/vps-hosting/managed-vps/.
When this stack isn't right for youโ
- Multi-team deployments with access control. Add NemoClaw for policy enforcement and per-user sandboxing.
- Local model inference at scale. A 4 GB VPS cannot run useful LLMs locally. Upgrade to a Liquid Web GPU server and add Ollama.
- High-availability (no single point of failure). This stack has one Redis and one OpenClaw container. For HA, run Redis Sentinel and multiple OpenClaw replicas behind a load balancer, which is Liquid Web HA Cluster territory.
- HIPAA-regulated data. Use the HIPAA deployment guide instead, which adds NemoClaw sandboxing, audit logging, and targets a Liquid Web HIPAA-ready environment.
Updating OpenClawโ
# Pull the new image
docker compose pull openclaw
# Recreate only the openclaw container (zero-downtime if Redis is healthy)
docker compose up -d --no-deps openclaw
# Verify
docker compose logs openclaw --tail 20
Pin OPENCLAW_VERSION in .env to a specific tag in production to avoid surprise upgrades on docker compose pull.
Yes, but you lose automatic TLS. Remove the Caddy service and expose OpenClaw directly on port 18789. For local or VPN-only access this is fine. For any internet-facing deployment, use Caddy with a real domain so traffic is encrypted.
Add an ollama service to docker-compose.yml with the official ollama/ollama image, mount a volume for model weights, and set LLM_PROVIDER=local and LLM_API_KEY=http://ollama:11434 in the environment. On a 4 GB VPS, only very small models (1โ3B parameters) are practical. For 7B+ models, use a Liquid Web 8 GB VPS or GPU server.
Not yet. NemoClaw runs as a sidecar that intercepts all LLM calls before they leave the OpenClaw container. See the NemoClaw policy cookbook for how to add it to this Compose file.
Run: docker compose exec backup restic snapshots to list all snapshots. To test restore: docker compose exec backup restic restore latest --target /tmp/restore-test and inspect the output. Test the restore at least once before relying on backups in production.
Common mistakes and fixes
Redis container exits with OOM immediately after start.
Add `maxmemory 256mb` and `maxmemory-policy allkeys-lru` to your redis.conf, or set the environment variable `REDIS_EXTRA_FLAGS=--maxmemory 256mb --maxmemory-policy allkeys-lru` in the compose file. On a 4 GB VPS, keep Redis under 256 MB.
Caddy fails certificate provisioning with a 'solving challenge' timeout.
Check that ports 80 and 443 are open (ufw allow 80/tcp && ufw allow 443/tcp) and that your domain A record resolves to the VPS IP. Run dig +short yourdomain.com to verify. If you're behind a NAT, Caddy cannot provision certificates.
OpenClaw container restarts repeatedly after Redis connects.
This usually means the config.yaml references a provider key that is invalid or the model name is wrong. Check logs with: docker compose logs openclaw --tail 50. Common culprit: Anthropic API keys starting with sk-ant- vs sk-. Verify the key in the Anthropic console.
Restic backup fails with 'Fatal: unable to open config file'.
The backup script assumes the restic repository has been initialised. Run: docker compose exec backup restic init once. After initialisation, scheduled backups will run correctly.



