OpenClaw Docker: Complete Self-Hosted Setup Guide
Run OpenClaw in Docker with docker-compose, configure environment variables, expose the right ports, put it behind nginx, and keep containers updated.
Running OpenClaw in Docker gives you a clean, reproducible installation that survives OS updates, is easy to back up, and can be moved between servers without reinstalling. This guide covers everything from the initial Docker install through a production-grade setup behind nginx with SSL.
If you want to run OpenClaw directly via npm instead, the process is simpler but less portable. Docker is the right choice for VPS deployments, homelab setups, or anywhere you want process isolation.
Prerequisites
- A Linux VPS or server (Ubuntu 22.04/24.04 recommended)
- Docker Engine 24+ and docker-compose v2
- A domain name pointed at your server (for SSL setup)
- At least 2GB RAM, 20GB disk
Installing Docker
If Docker isn't installed:
# Ubuntu/Debian
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
# Verify
docker --version
docker compose version
Docker 27+ comes with compose built in (docker compose). Older installs use the standalone docker-compose binary. This guide uses docker compose (v2 syntax).
Project Structure
Create a directory for your OpenClaw deployment:
mkdir -p /opt/openclaw
cd /opt/openclaw
You'll end up with this structure:
/opt/openclaw/
├── docker-compose.yml
├── .env
├── workspace/ # Agent workspace (persistent)
├── data/ # Database and session data
└── nginx/
├── openclaw.conf
└── certs/ # SSL certs (if managing manually)
docker-compose.yml
version: '3.8'
services:
openclaw:
image: openclaw/openclaw:latest
container_name: openclaw
restart: unless-stopped
environment:
- NODE_ENV=production
- OPENCLAW_PORT=${OPENCLAW_PORT:-3080}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
- OPENCLAW_GATEWAY_TOKEN=${OPENCLAW_GATEWAY_TOKEN}
- OPENCLAW_WORKSPACE=/workspace
- OPENCLAW_DATA_DIR=/data
- TZ=${TZ:-UTC}
volumes:
- ./workspace:/workspace
- ./data:/data
- /var/run/docker.sock:/var/run/docker.sock:ro # Optional: for spawning sibling containers
ports:
- "127.0.0.1:3080:3080" # Bind to localhost only — nginx handles external
networks:
- openclaw-net
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
openclaw-net:
driver: bridge
Key decisions in this config:
- Port binds to
127.0.0.1only — nginx sits in front for SSL termination - Workspace and data directories are mounted as volumes — they survive container restarts and updates
- Docker socket mount is optional; needed only if your agents spawn sibling containers
Environment Variables
Create your .env file:
cat > /opt/openclaw/.env << 'EOF'
# Required
ANTHROPIC_API_KEY=sk-ant-your-key-here
OPENCLAW_GATEWAY_TOKEN=change-this-to-a-strong-random-string
# Optional — add if you use OpenAI models
OPENAI_API_KEY=sk-your-openai-key
# Port (default 3080)
OPENCLAW_PORT=3080
# Timezone
TZ=America/New_York
EOF
# Lock down permissions — this file has secrets
chmod 600 /opt/openclaw/.env
Generate a strong gateway token:
openssl rand -hex 32
Never commit .env to git. Add it to .gitignore:
echo ".env" >> /opt/openclaw/.gitignore
Environment Variable Reference
| Variable | Required | Description |
|---|---|---|
ANTHROPIC_API_KEY |
Yes (if using Claude) | Anthropic API key |
OPENAI_API_KEY |
No | OpenAI API key for GPT models |
OPENCLAW_GATEWAY_TOKEN |
Yes | Shared secret for gateway auth |
OPENCLAW_PORT |
No | Internal port (default 3080) |
OPENCLAW_WORKSPACE |
No | Workspace path inside container |
OPENCLAW_DATA_DIR |
No | Data directory inside container |
TZ |
No | Timezone for cron and logs |
NODE_ENV |
No | Set to production for prod deployments |
Starting the Container
cd /opt/openclaw
docker compose up -d
# Watch logs
docker compose logs -f openclaw
Wait for the health check to pass. You should see:
openclaw | OpenClaw gateway started on port 3080
openclaw | Workspace initialized at /workspace
Check container status:
docker compose ps
Nginx Configuration
Install Nginx
sudo apt install nginx -y
Create the OpenClaw Virtual Host
cat > /etc/nginx/sites-available/openclaw.conf << 'EOF'
server {
listen 80;
server_name openclaw.yourdomain.com;
# Redirect HTTP to HTTPS
return 301 https://$host$request_uri;
}
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;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# Security headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
location / {
proxy_pass http://127.0.0.1:3080;
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;
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
}
}
EOF
ln -s /etc/nginx/sites-available/openclaw.conf /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
Get SSL Certificate
sudo apt install certbot python3-certbot-nginx -y
# Make sure DNS is pointing to your server before running this
certbot --nginx -d openclaw.yourdomain.com
Certbot auto-renews. Check the timer:
systemctl status certbot.timer
WebSocket Support
OpenClaw uses WebSockets for real-time session updates. The nginx config above includes the WebSocket upgrade headers (Upgrade, Connection). If you're getting connection drops in the dashboard, verify these headers are present:
curl -i -H "Upgrade: websocket" -H "Connection: Upgrade" \
https://openclaw.yourdomain.com/
Firewall Setup
Open only the ports you need:
ufw allow 22/tcp # SSH
ufw allow 80/tcp # HTTP (for certbot and redirects)
ufw allow 443/tcp # HTTPS
ufw enable
ufw status
Port 3080 stays closed externally — nginx handles it internally.
Persistent Data and Backups
Everything important lives in two directories:
/opt/openclaw/workspace/— agent workspace (memory, projects, config)/opt/openclaw/data/— session data, logs
Back these up regularly:
# Simple daily backup script
cat > /root/backup-openclaw.sh << 'EOF'
#!/bin/bash
DATE=$(date +%Y%m%d)
BACKUP_DIR=/root/backups/openclaw
mkdir -p $BACKUP_DIR
tar -czf $BACKUP_DIR/openclaw-workspace-$DATE.tar.gz /opt/openclaw/workspace/
tar -czf $BACKUP_DIR/openclaw-data-$DATE.tar.gz /opt/openclaw/data/
# Keep 14 days
find $BACKUP_DIR -name "*.tar.gz" -mtime +14 -delete
echo "Backup complete: $DATE"
EOF
chmod +x /root/backup-openclaw.sh
# Add to crontab (runs daily at 2am)
echo "0 2 * * * /root/backup-openclaw.sh >> /var/log/openclaw-backup.log 2>&1" | crontab -
Updating Containers
To update to the latest OpenClaw image:
cd /opt/openclaw
docker compose pull
docker compose up -d
# Clean up old images
docker image prune -f
This pulls the latest image, recreates the container (with the new image), and mounts the same workspace/data volumes — your agent context and memory are preserved across updates.
To pin to a specific version instead of latest:
# In docker-compose.yml
image: openclaw/openclaw:1.4.2
Check the OpenClaw releases page for version history and changelogs before updating production.
Monitoring and Logs
Check container health:
docker compose ps
docker stats openclaw
Follow logs in real time:
docker compose logs -f --tail=100 openclaw
Check disk usage:
du -sh /opt/openclaw/workspace/ /opt/openclaw/data/
For production deployments, consider piping logs to a logging service or setting up log rotation:
# docker-compose.yml logging config
services:
openclaw:
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "5"
Troubleshooting
Container exits immediately:
docker compose logs openclaw
# Look for "missing required environment variable" errors
# Usually means ANTHROPIC_API_KEY or OPENCLAW_GATEWAY_TOKEN not set
Gateway not reachable from browser:
# Check nginx is running
systemctl status nginx
# Check container is listening
curl -s http://127.0.0.1:3080/health
# Check firewall
ufw status
WebSocket disconnects frequently:
- Increase nginx
proxy_read_timeoutto 600s - Check if a load balancer or CDN is in front (they often kill long-lived connections)
- Cloudflare free plan has a 100-second proxy timeout — either use Cloudflare Tunnels or DNS-only for the OpenClaw subdomain
High memory usage:
- Check for zombie agent processes:
docker exec openclaw ps aux | grep agent - Restart the container if needed:
docker compose restart openclaw - The workspace grows over time — archive old memory files periodically
For connecting your Docker-hosted instance to GitHub repos, see OpenClaw GitHub. For managing everything from the web interface, see OpenClaw Dashboard.
Running your own infrastructure? Document the workflow, monitor it, and make every deployment verifiable.