← Blog
Guide

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.

·6 min read

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.1 only — 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_timeout to 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.