docker openclaw self-hosted deployment containers tutorial

Running OpenClaw in Docker — Complete Guide

How to run OpenClaw in Docker with docker-compose. Covers Dockerfile, persistent volumes, headless browser, network config, environment variables, and production deployment.

Running OpenClaw in Docker — Complete Guide

TL;DR: Clone the repo, copy the example docker-compose file, set your environment variables, run docker compose up -d. Your AI assistant is running in a container with persistent data, isolated from your host system.

Estimated time: 30-60 minutes
Difficulty: Advanced (assumes Docker knowledge)


Why Docker?

Running OpenClaw in Docker gives you:

  • Isolation: OpenClaw can't mess with your host system
  • Reproducibility: Same setup works on any machine
  • Easy updates: Pull new image, restart container
  • Clean teardown: Remove everything without leaving traces
  • Multi-instance: Run separate assistants for different purposes

If you're already using Docker for other services, adding OpenClaw to your stack is natural. If you've never used Docker, this guide might not be the place to start — try our Hetzner bare-metal guide instead.


Prerequisites

  • Docker Engine 24+ installed (docs.docker.com/engine/install)
  • Docker Compose v2 (docker compose — no hyphen)
  • A server or machine with at least 1 GB RAM (2 GB+ recommended)
  • Your AI provider API key (Anthropic, OpenAI, etc.)
  • Channel tokens (Telegram bot token, Discord bot token, etc.)

Step 1: Project Structure

Create a directory for your OpenClaw deployment:

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

The final structure will look like:

openclaw-docker/
├── docker-compose.yml
├── Dockerfile              # If building custom image
├── .env                    # Environment variables (secrets)
├── config/
│   └── openclaw.json       # OpenClaw configuration
└── data/                   # Persistent data (auto-created)
    ├── memory/
    ├── sessions/
    └── workspace/

Step 2: The Dockerfile

If you're building from source (recommended for latest features):

FROM node:22-slim

# Install system dependencies
RUN apt-get update && apt-get install -y \
    git \
    curl \
    jq \
    python3 \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

# Create app user (don't run as root)
RUN useradd -m -s /bin/bash openclaw

# Clone OpenClaw
WORKDIR /home/openclaw
RUN git clone https://github.com/clawdbot/openclaw.git app

WORKDIR /home/openclaw/app

# Install dependencies
RUN npm install --production

# Create data directories
RUN mkdir -p /home/openclaw/data/memory \
             /home/openclaw/data/sessions \
             /home/openclaw/data/workspace

# Set ownership
RUN chown -R openclaw:openclaw /home/openclaw

USER openclaw

# Gateway port
EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

CMD ["node", "src/index.js"]

Build it:

docker build -t openclaw:latest .

Step 3: docker-compose.yml

This is the main file. It defines all services:

version: "3.8"

services:
  openclaw:
    build: .
    # Or use a pre-built image:
    # image: ghcr.io/clawdbot/openclaw:latest
    container_name: openclaw
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:3000"   # Gateway — localhost only!
    env_file:
      - .env
    volumes:
      # Persistent data
      - ./data:/home/openclaw/data
      # Configuration
      - ./config/openclaw.json:/home/openclaw/app/config.json:ro
    networks:
      - openclaw-net
    depends_on:
      browser:
        condition: service_healthy
        required: false

  # Optional: Headless browser for web browsing capability
  browser:
    image: browserless/chromium:latest
    container_name: openclaw-browser
    restart: unless-stopped
    environment:
      - MAX_CONCURRENT_SESSIONS=2
      - CONNECTION_TIMEOUT=60000
      - TOKEN=${BROWSER_TOKEN:-openclaw-browser}
    ports:
      - "127.0.0.1:3001:3000"   # Browser API — localhost only!
    networks:
      - openclaw-net
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/"]
      interval: 30s
      timeout: 10s
      retries: 3
    # Resource limits
    deploy:
      resources:
        limits:
          memory: 2G
          cpus: "2.0"

networks:
  openclaw-net:
    driver: bridge

Key Points

  • Ports bound to 127.0.0.1: The gateway should NOT be exposed to the public internet directly. Use a reverse proxy with auth if you need external access
  • Volumes: Data persists across container restarts and rebuilds
  • Config is read-only (:ro): The container can read but not modify your config file
  • Browser is optional: Only needed if you want your AI to browse the web
  • Resource limits on browser: Chromium is memory-hungry. Limiting it prevents your server from going OOM

Step 4: Environment Variables

Create the .env file with your secrets:

# .env — NEVER commit this file to git

# AI Provider (pick one or more)
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxxxxxxxxxx
OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxxxxxxxxxxxxxxxxx

# Messaging Channels
TELEGRAM_BOT_TOKEN=7123456789:AAHxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
DISCORD_BOT_TOKEN=MTIzNDU2Nzg5MDEyMzQ1Njc4OQ.xxxxxx.xxxxxxxxxxxxxxxxx
DISCORD_APPLICATION_ID=1234567890123456789

# Gateway
GATEWAY_PORT=3000
GATEWAY_AUTH_TOKEN=your-secret-gateway-token-here

# Browser (if using browserless container)
BROWSER_TOKEN=openclaw-browser
BROWSER_URL=ws://browser:3000

# Optional
NODE_ENV=production
LOG_LEVEL=info
TZ=UTC

⚠️ Security: Add .env to your .gitignore:

echo ".env" >> .gitignore

Step 5: OpenClaw Configuration

Create your config file:

mkdir -p config
{
  "providers": {
    "anthropic": {
      "type": "anthropic",
      "apiKey": "${ANTHROPIC_API_KEY}"
    }
  },
  "channels": {
    "telegram": {
      "enabled": true,
      "token": "${TELEGRAM_BOT_TOKEN}"
    }
  },
  "defaultModel": "anthropic/claude-sonnet-4-20250514",
  "gateway": {
    "port": 3000,
    "authToken": "${GATEWAY_AUTH_TOKEN}"
  },
  "browser": {
    "endpoint": "${BROWSER_URL}"
  },
  "session": {
    "compaction": {
      "enabled": true,
      "threshold": 80000
    }
  }
}

Save as config/openclaw.json.


Step 6: Launch

# Start all services
docker compose up -d

# Check status
docker compose ps

# Watch logs
docker compose logs -f openclaw

# Check just the browser container
docker compose logs browser

You should see:

openclaw    | [gateway] Starting on port 3000...
openclaw    | [telegram] Bot connected: @your_bot_name
openclaw    | [gateway] Ready

Persistent Volumes Explained

The ./data directory persists your AI's state across restarts:

data/
├── memory/                 # AI memory files (MEMORY.md, daily notes)
│   ├── MEMORY.md
│   ├── 2025-07-14.md
│   └── ...
├── sessions/               # Conversation session state
│   ├── telegram_dm_123456.json
│   └── ...
└── workspace/              # Files the AI creates/uses
    ├── SOUL.md
    ├── AGENTS.md
    └── ...

Backup Strategy

Back up the data/ directory regularly:

# Simple backup
tar -czf backup-$(date +%Y%m%d).tar.gz data/

# Rsync to remote
rsync -avz data/ backup-server:/backups/openclaw/

# Automated daily backup (add to crontab)
0 3 * * * cd ~/openclaw-docker && tar -czf /backups/openclaw-$(date +\%Y\%m\%d).tar.gz data/

Network Configuration

Container-to-Container Communication

Services communicate over the openclaw-net bridge network. The OpenClaw container reaches the browser at ws://browser:3000 using the service name as hostname.

Connecting to Host Services

If you're running Ollama or another service on the host machine:

{
  "providers": {
    "ollama": {
      "type": "openai",
      "baseUrl": "http://host.docker.internal:11434/v1",
      "apiKey": "ollama"
    }
  }
}

host.docker.internal resolves to the host machine's IP from inside the container. On Linux, you may need to add this to your docker-compose:

services:
  openclaw:
    extra_hosts:
      - "host.docker.internal:host-gateway"

Node Connections (Mobile Devices)

If you're using OpenClaw nodes (mobile pairing), the gateway needs to be accessible from your local network:

ports:
  - "0.0.0.0:3000:3000"   # Open to local network

Only do this on a trusted local network. For remote access, use a VPN or SSH tunnel instead.

Reverse Proxy (Nginx/Caddy)

If you want HTTPS or external access, put a reverse proxy in front:

# Add to docker-compose.yml
  caddy:
    image: caddy:latest
    container_name: openclaw-caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - openclaw-net

volumes:
  caddy_data:
  caddy_config:
# Caddyfile
your-domain.com {
    reverse_proxy openclaw:3000
    # Add authentication!
    basicauth * {
        admin $2a$14$your-bcrypt-hash-here
    }
}

Never expose the gateway to the internet without authentication. See our security hardening guide.


Updating OpenClaw

If Building from Source

# Pull latest changes
docker compose build --no-cache openclaw

# Restart with new image
docker compose up -d openclaw

If Using Pre-built Images

# Pull latest image
docker compose pull openclaw

# Restart with new image
docker compose up -d openclaw

Your data persists in the ./data volume, so updates are non-destructive.


Useful Docker Commands

# View running containers
docker compose ps

# Follow logs in real time
docker compose logs -f

# Restart a specific service
docker compose restart openclaw

# Enter the container for debugging
docker compose exec openclaw bash

# Stop everything
docker compose down

# Stop and remove volumes (⚠️ deletes all data!)
docker compose down -v

# Check resource usage
docker stats openclaw openclaw-browser

Common Issues

Container exits immediately

Check logs:

docker compose logs openclaw

Common causes:

  • Missing or invalid environment variables
  • Config file not found (volume mount path wrong)
  • Port already in use on the host

"Cannot connect to browser"

  • Make sure the browser container is running: docker compose ps
  • Check the browser service health: docker compose logs browser
  • Verify the BROWSER_URL uses the service name (ws://browser:3000), not localhost

Permission errors on data directory

If the container runs as a non-root user but the host directory is owned by root:

# Fix ownership (use the UID from your Dockerfile)
sudo chown -R 1000:1000 ./data

High memory usage

The browser container is usually the culprit. Set stricter limits:

deploy:
  resources:
    limits:
      memory: 1G

Or disable the browser container if you don't need web browsing.


The Easy Way

Docker is powerful but adds complexity — Dockerfiles, compose files, networking, volume management, updates. It's worth it if you're already in the Docker ecosystem, but it's a lot of moving parts just to run an AI assistant.

Don't want to manage infrastructure? lobsterfarm provides managed OpenClaw hosting — deployment, updates, and support handled for you.

Get started with lobsterfarm →

Skip the setup. Start using your AI assistant today.

lobsterfarm gives you a fully managed OpenClaw instance — one click, your own server, running 24/7.