Docker Compose Setup

Containerized deployment with easy updates, backups, and multi-service orchestration

Platform: Docker
Cost: Free
Time: 30 minutes
Difficulty: Intermediate

Docker Compose Setup for OpenClaw

Deploy OpenClaw in a containerized environment with easy updates, backups, and multi-service orchestration. This guide walks through everything from a minimal single-container setup to a production-ready stack with Redis caching and HTTPS termination.

Difficulty: Intermediate | Cost: Free | Time: ~30 minutes


Prerequisites

Before you begin, make sure you have the following installed on your host machine:

  • Docker Engine 24.0+ -- verify with docker --version
  • Docker Compose v2 (ships with Docker Desktop; on Linux, install the docker-compose-plugin package) -- verify with docker compose version
  • Basic terminal knowledge -- you should be comfortable running commands, editing files, and reading log output
  • An API key from at least one supported LLM provider (Anthropic or OpenAI)

If you do not have Docker installed yet, follow the official instructions for your platform:

  • macOS / Windows: Install Docker Desktop
  • Ubuntu / Debian: sudo apt-get update && sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
  • Fedora: sudo dnf install docker-ce docker-ce-cli containerd.io docker-compose-plugin

After installation, confirm both tools are available:

docker --version
# Expected: Docker version 24.x or later

docker compose version
# Expected: Docker Compose version v2.x

Step 1: Project Setup

Create a dedicated directory for your OpenClaw deployment. Keeping everything in one directory makes backups and version control straightforward.

mkdir -p ~/openclaw-deploy && cd ~/openclaw-deploy

Inside this directory, create the following structure:

openclaw-deploy/
  docker-compose.yml
  .env
  config/
    soul.md          # Your SOUL.md persona configuration
  skills/            # Persistent skill data and custom skills
  logs/              # Container log output (optional)
  backups/           # Backup archives

Set up the directories:

mkdir -p config skills logs backups

If you already have a SOUL.md file, copy it into config/. Otherwise, create a starter file:

cat > config/soul.md << 'EOF'
# OpenClaw Agent Persona
You are a helpful AI assistant deployed via Docker.
Follow user instructions carefully and accurately.
EOF

Step 2: Docker Compose Configuration

Create docker-compose.yml at the root of your project directory. This file defines the OpenClaw service, its environment, storage, and resource constraints.

# docker-compose.yml
name: openclaw

services:
  openclaw:
    image: openclaw/openclaw:latest
    container_name: openclaw-agent
    restart: unless-stopped

    # Environment variables -- values are pulled from the .env file
    env_file:
      - .env

    environment:
      - OPENCLAW_CONFIG_DIR=/app/config
      - OPENCLAW_SKILLS_DIR=/app/skills
      - OPENCLAW_LOG_LEVEL=info

    # Persistent volumes
    volumes:
      - ./config:/app/config:ro       # Mount config as read-only
      - ./skills:/app/skills           # Skill data persists across restarts
      - ./logs:/app/logs               # Persist logs outside the container

    # Expose the agent API on localhost only
    ports:
      - "127.0.0.1:8080:8080"

    # Resource limits prevent runaway usage
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 2G
        reservations:
          cpus: "0.5"
          memory: 512M

    # Health check to detect a stuck or crashed process
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 15s

    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

Key decisions in this configuration:

  • restart: unless-stopped -- the container restarts automatically after a crash or host reboot, but stays stopped if you explicitly stop it.
  • 127.0.0.1:8080:8080 -- binds only to localhost. The agent API is not exposed to the public internet.
  • Resource limits -- caps at 2 CPU cores and 2 GB RAM. Adjust to fit your hardware.
  • Read-only config mount -- the :ro flag on the config volume prevents the container from modifying your configuration files.
  • Log rotation -- the json-file driver with max-size and max-file prevents logs from filling up your disk.

Step 3: Environment Variables

Create a .env file in the same directory as your docker-compose.yml. This file holds API keys and runtime configuration. Never commit this file to version control.

# .env

# --- LLM Provider API Keys ---
# At least one key is required.
ANTHROPIC_API_KEY=sk-ant-your-key-here
OPENAI_API_KEY=sk-your-openai-key-here

# --- Model Selection ---
# Choose the default model for the agent to use.
OPENCLAW_DEFAULT_MODEL=claude-sonnet-4-20250514
# Alternative options:
#   claude-sonnet-4-20250514
#   claude-opus-4-20250514
#   gpt-4o
#   gpt-4o-mini

# --- Skill Configuration ---
OPENCLAW_SKILLS_DIR=/app/skills
OPENCLAW_CONFIG_DIR=/app/config

# --- Runtime Settings ---
OPENCLAW_LOG_LEVEL=info
OPENCLAW_PORT=8080
OPENCLAW_MAX_CONCURRENT_TASKS=5

# --- Optional: Proxy / Networking ---
# HTTP_PROXY=http://proxy.example.com:8080
# HTTPS_PROXY=http://proxy.example.com:8080
# NO_PROXY=localhost,127.0.0.1

Protect the file so only your user can read it:

chmod 600 .env

Add .env to your .gitignore if this directory is under version control:

echo ".env" >> .gitignore

Step 4: Build and Run

Pull the latest image and start the container in detached mode:

docker compose pull
docker compose up -d

Verify the container is running:

docker compose ps

You should see output like:

NAME              IMAGE                     STATUS                   PORTS
openclaw-agent    openclaw/openclaw:latest   Up 12 seconds (healthy)  127.0.0.1:8080->8080/tcp

Check the startup logs:

docker compose logs -f openclaw

Press Ctrl+C to stop following the logs. If the container starts successfully, you will see initialization messages and a line indicating the agent is listening on port 8080.

Test connectivity:

curl http://localhost:8080/health

A healthy response returns {"status":"ok"} or similar.


Step 5: Managing the Container

Start / Stop / Restart

# Stop the container (keeps the container, just stops the process)
docker compose stop

# Start a stopped container
docker compose start

# Restart (stop + start)
docker compose restart

# Tear down everything (removes containers and default network)
docker compose down

# Tear down and also remove named volumes (DESTRUCTIVE -- deletes persistent data)
docker compose down -v

Viewing Logs

# Follow live logs
docker compose logs -f openclaw

# Show the last 100 lines
docker compose logs --tail 100 openclaw

# Show logs with timestamps
docker compose logs -t openclaw

Updating to the Latest Version

Pull the newest image and recreate the container. Your data in the mounted volumes is preserved.

docker compose pull
docker compose up -d

Docker Compose will detect the newer image, stop the old container, and start a new one automatically. To pin a specific version instead of latest, change the image tag in docker-compose.yml:

image: openclaw/openclaw:1.2.3

Inspecting the Container

# Open a shell inside the running container
docker compose exec openclaw /bin/sh

# Check resource usage
docker stats openclaw-agent

Step 6: Backups

What to Back Up

PathContentsPriority
.envAPI keys, configurationCritical
config/SOUL.md and agent persona filesCritical
skills/Installed skills and custom skill dataHigh
docker-compose.ymlService definitionsMedium (can be recreated)

Manual Backup

tar -czf backups/openclaw-backup-$(date +%Y%m%d-%H%M%S).tar.gz \
  .env \
  docker-compose.yml \
  config/ \
  skills/

Automated Backup Script

Save the following as backup.sh in your project directory:

#!/usr/bin/env bash
set -euo pipefail

BACKUP_DIR="./backups"
RETAIN_DAYS=30
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
ARCHIVE="${BACKUP_DIR}/openclaw-backup-${TIMESTAMP}.tar.gz"

mkdir -p "${BACKUP_DIR}"

# Create the backup archive
tar -czf "${ARCHIVE}" \
  .env \
  docker-compose.yml \
  config/ \
  skills/

echo "Backup created: ${ARCHIVE}"

# Remove backups older than the retention period
find "${BACKUP_DIR}" -name "openclaw-backup-*.tar.gz" -mtime +${RETAIN_DAYS} -delete
echo "Cleaned up backups older than ${RETAIN_DAYS} days."

Make it executable and schedule it with cron:

chmod +x backup.sh

# Run daily at 2:00 AM
crontab -e
# Add the following line (adjust the path to match your setup):
# 0 2 * * * cd /home/youruser/openclaw-deploy && ./backup.sh >> logs/backup.log 2>&1

Step 7: Multi-Service Setup

For production workloads, you may want to add Redis for caching and a reverse proxy for HTTPS. Extend your docker-compose.yml with additional services.

Adding Redis for Caching

# docker-compose.yml (extended)
name: openclaw

services:
  openclaw:
    image: openclaw/openclaw:latest
    container_name: openclaw-agent
    restart: unless-stopped
    env_file:
      - .env
    environment:
      - OPENCLAW_CONFIG_DIR=/app/config
      - OPENCLAW_SKILLS_DIR=/app/skills
      - OPENCLAW_LOG_LEVEL=info
      - REDIS_URL=redis://redis:6379
    volumes:
      - ./config:/app/config:ro
      - ./skills:/app/skills
      - ./logs:/app/logs
    ports:
      - "127.0.0.1:8080:8080"
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 2G
    depends_on:
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 15s

  redis:
    image: redis:7-alpine
    container_name: openclaw-redis
    restart: unless-stopped
    volumes:
      - redis-data:/data
    command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3
    deploy:
      resources:
        limits:
          cpus: "0.5"
          memory: 512M

volumes:
  redis-data:

Redis is configured with a 256 MB memory cap and an LRU eviction policy, so it acts purely as a cache. The depends_on condition ensures OpenClaw waits for Redis to be healthy before starting.

Adding a Reverse Proxy with Caddy (Automatic HTTPS)

Caddy automatically provisions and renews TLS certificates via Let's Encrypt. Add it as a service and point it at the OpenClaw container.

Create Caddyfile in your project directory:

yourdomain.com {
    reverse_proxy openclaw:8080
    encode gzip
    log {
        output file /data/access.log
    }
}

Then add Caddy to docker-compose.yml:

  caddy:
    image: caddy:2-alpine
    container_name: openclaw-caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"    # HTTP/3 (QUIC)
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy-data:/data
      - caddy-config:/config
    depends_on:
      - openclaw
    deploy:
      resources:
        limits:
          cpus: "0.5"
          memory: 256M

Add the Caddy volumes to the top-level volumes block:

volumes:
  redis-data:
  caddy-data:
  caddy-config:

When using Caddy as the public-facing proxy, remove the ports mapping from the OpenClaw service entirely. Traffic flows: Internet -> Caddy (ports 80/443) -> OpenClaw (internal port 8080). The OpenClaw port no longer needs to be published.

If you prefer nginx instead of Caddy, replace the Caddy service with:

  nginx:
    image: nginx:1.27-alpine
    container_name: openclaw-nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - openclaw

With nginx you must manage TLS certificates yourself (for example, with certbot or an ACME sidecar container). For most deployments, Caddy is the simpler choice.


Security Notes

  1. Bind ports to localhost only. The default configuration uses 127.0.0.1:8080:8080. Never bind to 0.0.0.0 unless the port is behind a reverse proxy or firewall.

  2. Use Docker secrets for API keys in production. Instead of storing keys in .env, define them as secrets:

    # docker-compose.yml
    services:
      openclaw:
        secrets:
          - anthropic_api_key
          - openai_api_key
        environment:
          - ANTHROPIC_API_KEY_FILE=/run/secrets/anthropic_api_key
          - OPENAI_API_KEY_FILE=/run/secrets/openai_api_key
    
    secrets:
      anthropic_api_key:
        file: ./secrets/anthropic_api_key.txt
      openai_api_key:
        file: ./secrets/openai_api_key.txt
    

    Create the secrets directory and files:

    mkdir -p secrets
    echo "sk-ant-your-key-here" > secrets/anthropic_api_key.txt
    echo "sk-your-openai-key-here" > secrets/openai_api_key.txt
    chmod 600 secrets/*.txt
    

    Secrets are mounted as files inside the container at /run/secrets/ and never appear in docker inspect output or environment variable listings.

  3. Use read-only filesystems where possible. Add read_only: true to the service definition and explicitly mount writable paths with tmpfs or named volumes:

    services:
      openclaw:
        read_only: true
        tmpfs:
          - /tmp
        volumes:
          - ./config:/app/config:ro
          - ./skills:/app/skills
          - ./logs:/app/logs
    
  4. Drop unnecessary Linux capabilities. Reduce the container's attack surface:

    services:
      openclaw:
        cap_drop:
          - ALL
        cap_add:
          - NET_BIND_SERVICE
    
  5. Run as a non-root user. If the image supports it, set the user explicitly:

    services:
      openclaw:
        user: "1000:1000"
    
  6. Keep images updated. Regularly pull new images to get security patches: docker compose pull && docker compose up -d.


Troubleshooting

Port Conflict

Symptom: Error: bind: address already in use

Another process is using port 8080. Find it and either stop it or change the OpenClaw port.

# Find what is using port 8080
lsof -i :8080
# or
ss -tlnp | grep 8080

To change the OpenClaw port, edit the ports mapping in docker-compose.yml:

ports:
  - "127.0.0.1:9090:8080"   # Host port 9090 -> container port 8080

Permission Denied on Volumes

Symptom: The container logs show Permission denied when reading config or writing to skills/logs.

This happens when the container runs as a different UID than the host directory owner. Fix it by matching ownership:

# Check the UID the container runs as (common: 1000 or 65534)
docker compose exec openclaw id

# Set ownership on the host to match
sudo chown -R 1000:1000 config/ skills/ logs/

Container Keeps Restarting

Symptom: docker compose ps shows status Restarting in a loop.

Check the logs for the root cause:

docker compose logs --tail 50 openclaw

Common causes:

  • Missing or invalid API key -- verify your .env file has a valid ANTHROPIC_API_KEY or OPENAI_API_KEY.
  • Configuration syntax error -- check config/soul.md for malformed content.
  • Insufficient memory -- if the container is being OOM-killed, increase the memory limit in deploy.resources.limits.memory.

Container Starts but Health Check Fails

Symptom: Status shows (health: starting) and then (unhealthy).

The health check endpoint may not be ready yet. Increase the start_period:

healthcheck:
  start_period: 30s   # Give the app more time to initialize

If /health is not the correct endpoint for your version, check the OpenClaw documentation and update the test command accordingly.

Cannot Connect to Redis

Symptom: OpenClaw logs show Error: connect ECONNREFUSED 127.0.0.1:6379.

The REDIS_URL should reference the Docker Compose service name, not localhost:

# Wrong
REDIS_URL=redis://localhost:6379

# Correct
REDIS_URL=redis://redis:6379

Inside the Docker network, services communicate by service name, not by localhost.

Disk Space Running Out

Symptom: Container crashes or Docker commands fail with no space left on device.

Prune unused Docker resources:

# Remove stopped containers, dangling images, and build cache
docker system prune -f

# Also remove unused volumes (careful -- this deletes data in unnamed volumes)
docker system prune --volumes -f

Check your backup directory as well and remove old archives if retention is not configured.


Quick Reference

TaskCommand
Start servicesdocker compose up -d
Stop servicesdocker compose stop
Restart servicesdocker compose restart
View live logsdocker compose logs -f openclaw
Update to latestdocker compose pull && docker compose up -d
Check statusdocker compose ps
Open a shelldocker compose exec openclaw /bin/sh
Resource usagedocker stats openclaw-agent
Full teardowndocker compose down
Manual backuptar -czf backup.tar.gz .env docker-compose.yml config/ skills/

Guide maintained by the OpenClaw community. Last updated: February 2026.