All Articles OpenClaw

OpenClaw Docker Compose Deep Dive: Gateway, Workspace, and Persistent Storage

So you've got OpenClaw running with a simple docker run command. It works. Your agent is responding.

So you’ve got OpenClaw running with a simple docker run command. It works. Your agent is responding. But you’re thinking about the future: what if you want to add Ollama for local LLM support? What if you want to run multiple instances? What if you need to version control your configuration?

That’s where Docker Compose comes in. It’s basically a way to say “here’s my entire application setup in a file” instead of typing a monster command every time. And once you understand what’s happening in that file, you’ll see that it’s not magical—it’s just organized.

This guide is going to walk you through three complete, production-ready docker-compose.yaml files: a minimal setup for beginners, a standard setup with all the bells and whistles, and a production setup that includes Ollama for running local LLMs. We’ll break down every line, explain the Gateway architecture, show you how to manage persistent storage, and give you the exact commands to update, restart, and troubleshoot your setup.

Understanding the Architecture: Gateway and Workspace

Before we dive into the YAML, let’s talk about what OpenClaw actually is under the hood.

OpenClaw has three main components:

The Gateway (Node.js daemon): This is the central hub. It runs on your machine and coordinates between your messaging platforms (Telegram, Discord, Slack, etc.) and your LLM provider (Claude, OpenAI, Ollama, etc.). Think of it as a switchboard operator. Every message that comes in gets routed through the Gateway, which decides what to do with it, calls the appropriate LLM, and sends the response back out. The Gateway handles rate limiting, request queuing, retry logic, and conversation context management. It’s essentially a translation layer—it takes a Telegram message, converts it to the format your chosen LLM expects, gets back a response, converts that back to Telegram format, and sends it out. Without this Gateway, you’d be manually translating between six different platform APIs and three different LLM APIs. That’s what it saves you from.

The Dashboard (web interface): This is the thing at https://automateanddeploy.com:18789 where you configure platforms, view logs, and manage your agent’s personality and skills. It’s a Node.js web server running inside the same container as the Gateway. The Dashboard is your control panel—you don’t configure OpenClaw by editing yaml files or environment variables. You use the Dashboard to enable/disable platforms, change your agent’s personality (SOUL.md), add new skills, review conversation logs, and adjust rate limits and model settings. Most of the configuration you’ll do happens through the Dashboard, not through Docker Compose environment variables. Docker Compose sets up the environment (which LLM to use, the API key, basic settings), but the Dashboard is where you actually customize your agent.

The Workspace (your data): This is the ~/.openclaw/ directory on your machine. It contains your SOUL.md (personality), SKILL.md (custom behaviors), MEMORY.md (long-term memory), conversation logs, and platform configurations. This is what lives outside Docker and persists between container restarts. The workspace is sacred—it’s your agent’s entire identity and history. Lose it and you’d need to rebuild your agent from scratch.

When you run OpenClaw in Docker:

  • The container runs the Gateway and Dashboard (the application code)
  • Your workspace lives on your machine in ~/.openclaw/ (the data)
  • Docker mounts them together so the container can read/write your workspace
  • Every time you restart the container, it loads the same workspace, so your agent picks up exactly where it left off

This separation is crucial. Your data is decoupled from the application. You can upgrade OpenClaw to a new version, stop the container, start a new one with the new image, and your workspace is still there—untouched. The new version of OpenClaw immediately has access to all your custom skills, personality settings, and conversation history. Your agent’s identity transcends any particular Docker container. This is why Docker is so powerful for applications like OpenClaw—you get the sandboxing and reproducibility of containers, but you don’t lose your data when the container gets replaced.

Docker Compose Fundamentals: What You’re Actually Writing

A docker-compose.yaml file is basically a recipe for Docker. It says “here’s what services I want, here’s how to configure them, here’s how they talk to each other.” It’s declarative—you describe the end state you want, and Docker Compose figures out how to make it happen. This is a huge shift from imperative scripts where you tell Docker exactly what to do step-by-step. Declarative means “if I stop my containers and then run docker compose up again, I get the exact same result without any manual steps in between.”

The basic structure looks like this:

version: "3.8"

services:
  service-name:
    image: some/image:tag
    container_name: friendly-name
    restart: unless-stopped
    ports:
      - "host-port:container-port"
    volumes:
      - /host/path:/container/path
    environment:
      VARIABLE_NAME: value
    networks:
      - network-name

networks:
  network-name:
    driver: bridge

Each services: block is a container you want to run. You can have one service (just OpenClaw) or many (OpenClaw + Ollama + a database, etc.). The networks section defines how containers talk to each other. The version matters too—it tells Docker which features you’re using. Version 3.8 has been stable for years and supports everything you’ll likely need.

Here’s the key insight about Docker Compose: it’s not actually running your containers in a special way. It’s generating Docker commands and running them in the right order. When you run docker compose up, it’s essentially running something like docker network create openclaw-net, then docker run --name openclaw --network openclaw-net ... for each service. It’s just automating the tedious parts and giving you a human-readable file format for your configuration.

This matters because it means everything in a Compose file, you could do manually with docker run commands. But why would you? The Compose file is cleaner, version-controllable, and repeatable.

Let’s build your first complete file.

Template 1: Minimal Setup (Just OpenClaw + Claude)

This is the simplest production-ready setup. One container, Claude as the LLM, persistent storage.

version: "3.8"

# Top-level comment: This is the minimal OpenClaw setup
# One container (OpenClaw Gateway + Dashboard)
# Uses Claude as the LLM provider
# Everything persists to ~/.openclaw on your machine

services:
  openclaw:
    # image: The Docker image to use. openclaw/openclaw is on Docker Hub.
    # latest means "the newest version available"
    image: openclaw/openclaw:latest

    # container_name: Friendly name for this container
    # You'll use this in commands like "docker logs openclaw"
    container_name: openclaw

    # restart: unless-stopped means:
    # - If the container crashes, Docker automatically restarts it
    # - If you manually stop it (docker stop), it won't auto-restart
    # This is better than "always" because it respects manual stops
    restart: unless-stopped

    # ports: Maps container ports to your machine ports
    # Format: "host-port:container-port"
    # 18789 on container → 18789 on your machine
    # So you visit https://automateanddeploy.com:18789 to access the dashboard
    ports:
      - "18789:18789"

    # volumes: Persistent storage
    # Everything OpenClaw writes to /root/.openclaw in the container
    # Actually gets saved to ~/.openclaw on your machine
    # The ~ expands to your home directory
    volumes:
      - ~/.openclaw:/root/.openclaw

    # environment: Configuration variables
    # These tell OpenClaw how to behave
    # OPENCLAW_LLM_PROVIDER: which AI service to use (claude, openai, deepseek, ollama)
    # OPENCLAW_API_KEY: your API key for that service
    # Replace sk-ant-xxx with your actual Claude API key from console.anthropic.com
    environment:
      OPENCLAW_LLM_PROVIDER: claude
      OPENCLAW_API_KEY: sk-ant-your-actual-key-here

      # Optional: other useful variables
      # Log level (debug, info, warn, error)
      LOG_LEVEL: info

      # If you want to enable specific debug features
      # OPENCLAW_DEBUG: "false"

    # networks: Which network this container connects to
    # Containers on the same network can talk to each other by hostname
    networks:
      - openclaw-net

    # healthcheck: Optionally, Docker can monitor container health
    # This isn't required but it's nice for production
    # Every 30 seconds, make a request to the dashboard
    # If it fails 3 times in a row, mark the container unhealthy
    healthcheck:
      test:
        [
          "CMD",
          "curl",
          "-f",
          "https://automateanddeploy.com:18789/health",
          "||",
          "exit",
          "1",
        ]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

# networks: Define the network(s) your services use
networks:
  openclaw-net:
    # driver: bridge is the default, safe, and works everywhere
    # It creates an isolated network where containers can talk to each other
    driver: bridge

That’s it. To run this:

# Save the above to docker-compose.yaml in some folder
# cd into that folder, then:
docker compose up -d

# Check the logs
docker compose logs -f openclaw

# Once you see "listening on 18789" or similar, open https://automateanddeploy.com:18789

The -d flag means “detached mode”—run in the background. If you want to see logs in real-time while it starts, use docker compose up without -d.

On first run, Docker will pull the OpenClaw image from Docker Hub. This might take a minute or two depending on your internet speed (the image is maybe 200-300MB). Subsequent runs are instant because the image is cached locally. You’ll see a lot of console output—that’s Docker describing what it’s doing (creating the network, pulling the image, starting the container). At the end, you’ll see something like “openclaw-net is up” or similar, which means everything succeeded. Then check the logs with docker compose logs -f openclaw and you should see startup messages from the Node.js application. Look for “listening on 18789” or “Dashboard available at” or similar. That’s your signal that it’s ready. Open https://automateanddeploy.com:18789 in a browser and you should see the OpenClaw Dashboard.

Breaking Down Docker Compose Directives

Let me expand on what each directive is actually doing, because understanding this makes troubleshooting way easier when things go sideways.

image: This tells Docker what to pull from Docker Hub or your registry. The format is registry/repository:tag. When you leave it as openclaw/openclaw:latest, Docker automatically handles pulling the image if it doesn’t exist locally. Using latest is convenient for development, but in production you’ll want to pin a specific version like openclaw/openclaw:1.2.3 so updates don’t surprise you. Here’s why pinning matters: if you use latest and a new version breaks something, your deployment breaks automatically the next time you pull. If you pin a version, you control when upgrades happen. Test them first, then update your yaml when you’re ready. This is why big companies always pin versions—they’re protecting themselves from surprise breaking changes.

container_name: This is just a friendly identifier. Without it, Docker generates random names like hungry_turing_47 or fervent_mclaren_82. Give it a human-readable name because you’ll reference it constantly in commands. You’ll type docker logs openclaw dozens of times. Without a friendly name, you’d have to look up the container ID each time or use a substring match. This name lives only on your machine—it’s not shared across systems. If you deploy the same yaml on three different machines, each will have its own container with the same name, and they won’t conflict.

restart: This is your safety net. When you set unless-stopped, Docker automatically restarts the container if it crashes or if your machine reboots. Here’s the behavior: if OpenClaw crashes, Docker will restart it automatically. If you manually stop it (docker stop openclaw), Docker respects that and won’t restart it until you tell it to (docker start openclaw). The alternatives are always (restart no matter what, even if you stopped it manually), on-failure (restart only if it exits with non-zero status), and no (never restart). Choose unless-stopped for production unless you have a specific reason not to.

ports: This creates a bridge between your machine and the container. The format "18789:18789" means “listen on my machine’s port 18789 and forward to the container’s port 18789.” You can use different numbers: "9000:18789" would mean “my machine’s 9000 → container’s 18789.” This is how external traffic reaches your container. Without it, the container is invisible from outside—it’s completely isolated. That’s actually why we use expose instead of ports when we put a reverse proxy in front (like Nginx). The reverse proxy is on the bridge network and talks to the container internally, and only Nginx listens on the external port.

volumes: This is where data persistence happens. The format ~/path:/container/path means “my machine’s directory ~/path is the same as /container/path inside the container.” Any writes to /container/path actually hit your disk. The ~ expands to your home directory automatically—on Linux/Mac it’s /home/username, on Windows it’s C:\Users\username\. Why does this matter? Because when your container stops, everything inside it disappears—except mounted volumes. Your ~/.openclaw/ workspace survives container restarts, upgrades, complete deletion of the container, even migrating to a different machine. You can delete the container, pull a new image, start a new container with the same volume mount, and your data is instantly there. That’s the power of decoupling data from application.

environment: These are configuration variables. Docker injects them into the container’s environment, and OpenClaw reads them on startup. This is how you tell the app “use Claude, not OpenAI” or “run in debug mode” or “listen on port 9000 instead of 18789.” No need to rebuild the image—just change the yaml and restart. This is why yaml-based configuration is so powerful. You can version control your settings, see exactly what changed between deployments, and debug issues by looking at the yaml.

networks: Containers can’t talk to each other by default. Adding them to the same network creates an isolated LAN where they can reach each other by hostname. This is critical when you’re running multiple services (like OpenClaw + Ollama). The Docker daemon runs a DNS server that translates container names to internal IPs automatically. So when OpenClaw tries to connect to http://ollama:11434, Docker’s internal DNS resolves ollama to the Ollama container’s internal IP (something like 172.20.0.3), and the request goes through. This is why service names become hostnames—it’s built into Docker’s networking layer.

healthcheck: This is Docker’s way of verifying a service is actually alive. Every 30 seconds, it runs the command (in this case, curl to the health endpoint). If it fails 3 times in a row, Docker marks the container unhealthy (though it won’t auto-restart unless you also set restart: on-failure). The start_period is important—it’s how long Docker waits before it starts checking health. An app might need 40 seconds to boot, so we tell Docker “don’t check health for the first 40 seconds, then start checking.” This prevents false positives on startup.

deploy.resources.limits and deploy.resources.reservations These control memory and CPU allocation. A limit is the maximum the container can use—Docker will kill it if it exceeds this. A reservation is the minimum Docker guarantees to this container—if there’s contention, this container gets its reservation first. You usually want reservation < limit to give headroom. For Ollama, which is memory-hungry, you might set reservation: 8g, limit: 16g so it has 8GB guaranteed but can burst to 16GB if other containers aren’t using their resources.

logging.driver and logging.options These control how Docker stores logs. By default, logs grow unbounded and can consume your entire disk. The json-file driver with max-size: "10m" and max-file: "3" means “keep at most 3 log files, each up to 10MB, then rotate.” This prevents logs from consuming all your disk space while keeping recent history accessible. If you need logs older than this, you’ll need to collect them elsewhere (like ELK stack or CloudWatch).

Volume Mount Mapping: Why ~/.openclaw Matters

Let’s drill deeper into volumes because this is where persistence actually happens and it’s the difference between “my agent’s data survived” and “I just lost everything.”

When you write volumes: - ~/.openclaw:/root/.openclaw, here’s what Docker does:

  1. On startup, it checks if ~/.openclaw exists on your machine. If not, it creates it.
  2. It binds your machine’s ~/.openclaw to the container’s /root/.openclaw. They’re now the same directory—changes on either side are reflected immediately.
  3. When the container writes to /root/.openclaw/MEMORY.md, it’s actually writing to your machine’s ~/.openclaw/MEMORY.md.
  4. When the container stops, the volume remains on your machine. The data is completely safe.

Why use ~ instead of an absolute path? Because ~ expands to your home directory, which is different on every machine. If you use /home/alice/.openclaw/ and then deploy to a server, it’ll try to use /home/alice/ there too (and probably fail). Using ~ makes your config portable.

What if you want multiple workspaces? You can! Just mount different directories:

volumes:
  - ~/.openclaw-prod:/root/.openclaw
  # or
  - /data/openclaw-backup:/root/.openclaw

Each workspace is completely independent. You could even run two OpenClaw instances with different personalities by mounting different paths.

Gateway Environment Variables: Complete Reference

The environment: section is where OpenClaw’s behavior gets configured. Here’s what each variable actually does:

LLM Provider Selection:

  • OPENCLAW_LLM_PROVIDER: claude – Use Anthropic’s Claude API
  • OPENCLAW_LLM_PROVIDER: openai – Use OpenAI’s GPT models
  • OPENCLAW_LLM_PROVIDER: deepseek – Use DeepSeek (cheaper alternative)
  • OPENCLAW_LLM_PROVIDER: ollama – Use local LLM (requires Ollama container)

API Key:

  • OPENCLAW_API_KEY – Your authentication token for the chosen provider. Never commit this to version control. Use .env files instead.

Model Selection (optional, provider-dependent):

  • CLAUDE_MODEL: claude-3-5-sonnet-20241022 – Specific Claude version
  • OPENAI_MODEL: gpt-4-turbo – Specific OpenAI model

Ollama-Specific (if using Ollama):

  • OLLAMA_MODEL: llama2 – Which local model to use
  • OLLAMA_BASE_URL: http://ollama:11434 – Where Ollama is running (hostname resolved through Docker network)

Logging and Debugging:

  • LOG_LEVEL: info – How chatty logs are (debug, info, warn, error, fatal)
  • NODE_ENV: production – Environment (production or development affects performance)
  • OPENCLAW_DEBUG: "false" – Enable verbose debugging output

Memory and Performance:

  • MEMORY_CONTEXT_LIMIT: 10 – How many conversation exchanges to keep in immediate context
  • RATE_LIMIT_PER_MINUTE: 60 – API call rate limiting to prevent abuse

Dashboard Security (optional):

  • DASHBOARD_PASSWORD: your-secret-here – Lock down the web dashboard with a password

Workspace Location (usually don’t change):

  • OPENCLAW_WORKSPACE: /root/.openclaw/workspace – Where OpenClaw stores data inside the container

These variables are read once when the container starts. If you change them, you need to restart the container for the changes to take effect. That’s why having them in your yaml file is so valuable—you can see your entire configuration at a glance.

Template 2: Standard Setup (OpenClaw + Options)

Now let’s add some real-world features. This setup includes:

  • Support for multiple LLM providers (Claude by default, but easy to switch)
  • Custom environment overrides
  • A dedicated data volume for better isolation
  • Exposed logs directory so you can read them from your machine
version: "3.8"

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

    # ports: Can bind to different ports if 18789 is taken
    # Change the first number to use a different port on your machine
    # E.g., "9000:18789" would be https://automateanddeploy.com:9000
    ports:
      - "18789:18789"
      # Optional: expose the internal API port for advanced use cases
      # - "18790:18790"

    volumes:
      # Main workspace: where OpenClaw stores everything
      # This directory persists between container restarts
      - ~/.openclaw:/root/.openclaw

      # Optional: mount a backup directory
      # So you can easily backup your workspace
      # - /path/to/backups/openclaw:/root/.openclaw/backups

      # Optional: mount a custom skills directory
      # - /path/to/my/skills:/root/.openclaw/workspace/skills
      # This lets you version control your skills separately

    environment:
      # LLM Configuration
      # Change these to use different providers

      # Option A: Claude (Anthropic)
      OPENCLAW_LLM_PROVIDER: claude
      OPENCLAW_API_KEY: sk-ant-your-actual-key-here
      # Optional: specify Claude model version
      # CLAUDE_MODEL: claude-3-5-sonnet-20241022

      # Option B: To use OpenAI (ChatGPT) instead, change to:
      # OPENCLAW_LLM_PROVIDER: openai
      # OPENCLAW_API_KEY: sk-your-openai-key-here
      # OPENAI_MODEL: gpt-4

      # Option C: To use DeepSeek (cheaper), change to:
      # OPENCLAW_LLM_PROVIDER: deepseek
      # OPENCLAW_API_KEY: sk-your-deepseek-key-here

      # Option D: To use Ollama (local, no API key needed):
      # OPENCLAW_LLM_PROVIDER: ollama
      # OLLAMA_MODEL: llama2
      # OLLAMA_BASE_URL: http://ollama:11434
      # (Note: change "http://ollama" if Ollama is on a different host/port)

      # General Settings
      LOG_LEVEL: info
      NODE_ENV: production

      # Security: Set a dashboard password (optional)
      # DASHBOARD_PASSWORD: your-secret-password-here

      # Memory settings: how many conversation messages to keep in context
      # MEMORY_CONTEXT_LIMIT: 10

      # Workspace path inside container
      # Usually don't change this
      OPENCLAW_WORKSPACE: /root/.openclaw/workspace

      # API Rate limiting (optional)
      # Rate limit for API calls to prevent abuse
      # RATE_LIMIT_PER_MINUTE: 60

    # networks: Connect to the network
    networks:
      - openclaw-net

    # healthcheck: Monitor container health
    # If you want Docker to automatically restart unhealthy containers
    healthcheck:
      test:
        [
          "CMD",
          "curl",
          "-f",
          "https://automateanddeploy.com:18789/health",
          "||",
          "exit",
          "1",
        ]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

    # logging: Control how much log data Docker stores
    # This prevents logs from consuming all your disk space
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

# Define the network
networks:
  openclaw-net:
    driver: bridge

With this setup, you can easily switch LLM providers by just changing environment variables. You can add new volumes to mount custom skills. You can set memory limits, rate limits, and other advanced options.

To use this:

# Start it
docker compose up -d

# View logs (follow mode, shows new lines in real-time)
docker compose logs -f openclaw

# View last 50 lines
docker compose logs --tail 50 openclaw

# Stop it (data persists)
docker compose stop

# Start it again
docker compose start

# Check status
docker compose ps

Understanding Gateway Environment Variables in Detail

When you’re configuring your Gateway, each environment variable affects how OpenClaw behaves in specific ways. Let’s talk about some of the more subtle ones.

LOG_LEVEL is crucial for troubleshooting. Set it to debug during development—you’ll see everything the Gateway is doing. In production, use info to reduce noise. If something breaks, switch to debug temporarily to see what went wrong.

MEMORY_CONTEXT_LIMIT controls how much recent conversation history OpenClaw keeps in memory. This is a trade-off: keep too much history and you burn through your token budget; keep too little and the agent forgets context. Start with 10 and adjust based on your workload. For most use cases, 5-15 is reasonable.

NODE_ENV affects performance tuning. production mode disables some debugging features and optimizes for throughput. development mode keeps everything verbose and adds extra safety checks. Never run development in production—it’s much slower.

Dashboard security: If your OpenClaw instance is exposed to the internet (which you shouldn’t do without HTTPS), set a DASHBOARD_PASSWORD. Without it, anyone can access your agent’s configuration.

Template 3: Production Setup with Ollama (Local LLM)

This is for when you want the full experience: local LLM running on your machine, no API calls, complete privacy, no costs. Ollama is an open-source tool that runs LLMs locally.

version: "3.8"

services:
  # OpenClaw: The main agent
  openclaw:
    image: openclaw/openclaw:latest
    container_name: openclaw
    restart: unless-stopped

    # Port mapping: standard setup
    ports:
      - "18789:18789"

    volumes:
      - ~/.openclaw:/root/.openclaw

    environment:
      # Use Ollama for LLM (running on same machine)
      # http://ollama:11434 is how containers reach Ollama from the Docker network
      OPENCLAW_LLM_PROVIDER: ollama
      OLLAMA_MODEL: llama2
      OLLAMA_BASE_URL: http://ollama:11434

      # These are overridden when using Ollama, but keep them for clarity
      LOG_LEVEL: info
      NODE_ENV: production

    # Service dependency: wait for Ollama to be healthy before starting OpenClaw
    # This prevents startup errors if OpenClaw tries to reach Ollama before it's ready
    depends_on:
      ollama:
        condition: service_healthy

    networks:
      - openclaw-net

    healthcheck:
      test:
        [
          "CMD",
          "curl",
          "-f",
          "https://automateanddeploy.com:18789/health",
          "||",
          "exit",
          "1",
        ]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

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

  # Ollama: Local LLM provider running in a separate container
  ollama:
    # Official Ollama image from Docker Hub
    image: ollama/ollama:latest
    container_name: ollama

    # Restart policy: auto-restart on crash or system reboot
    restart: unless-stopped

    # Port mapping: Ollama API listens on 11434
    # We don't expose this to the host (no "ports:" section)
    # because only OpenClaw in Docker needs to reach it
    # But if you wanted to use Ollama from your machine directly:
    # ports:
    #   - "11434:11434"

    # GPU support: If you have an NVIDIA GPU, uncomment the runtime below
    # This lets Ollama use your GPU for much faster inference
    # runtime: nvidia

    # Alternatively, for AMD GPU (ROCm):
    # runtime: rocm

    volumes:
      # Ollama models are big. Store them in a named volume so they persist
      # and don't get deleted if the container restarts
      - ollama_models:/root/.ollama

      # Optional: mount a local directory for model data
      # - ~/.ollama:/root/.ollama

    # Environment variables
    environment:
      # Ollama logs (optional)
      OLLAMA_HOST: 0.0.0.0:11434

      # If using GPU, set these:
      # OLLAMA_CUDA_COMPUTE_CAP: 86  # for RTX 3090, 4080, etc.
      # OLLAMA_NUM_PARALLEL: 3       # how many requests in parallel
      # OLLAMA_NUM_THREAD: 8         # CPU threads for non-GPU operations

    networks:
      - openclaw-net

    # Health check: periodically ask if Ollama is responding
    healthcheck:
      test:
        [
          "CMD",
          "curl",
          "-f",
          "https://automateanddeploy.com:11434/api/tags",
          "||",
          "exit",
          "1",
        ]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

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

    # Resource limits: prevent Ollama from consuming all your memory
    # Adjust these based on your machine
    deploy:
      resources:
        limits:
          memory: 16g
        reservations:
          memory: 8g

# Named volumes: persistent storage that Docker manages
volumes:
  # This volume stores Ollama's models (/root/.ollama)
  # Models are huge, so using a named volume prevents re-downloading them
  ollama_models:
    driver: local

# Networks: how containers communicate
networks:
  openclaw-net:
    driver: bridge

This setup is powerful. Ollama runs in its own container and handles all the LLM inference. OpenClaw reaches it through the internal Docker network. Models are stored in a named volume so they persist between restarts.

Important: Before you run this, make sure you have Ollama configured locally or adjust the image name. The first time you run it, Ollama will download the llama2 model (about 4GB). This takes a few minutes.

To use this:

# Start both containers
docker compose up -d

# Watch both come up
docker compose logs -f

# Once both are healthy, open https://automateanddeploy.com:18789
# The dashboard will show you're connected to Ollama

# If you want to pull a different model (e.g., mistral instead of llama2):
docker compose exec ollama ollama pull mistral

# Then update your docker-compose.yaml to change OLLAMA_MODEL to mistral
# And restart:
docker compose down
docker compose up -d

Managing Your Setup: Common Commands

Once you’ve picked a template and saved it to docker-compose.yaml, here are the commands you’ll use constantly.

Starting everything:

# First-time startup (pulls images, creates containers)
docker compose up -d

# Subsequent startups (just starts existing containers)
docker compose start

Viewing logs:

# Follow real-time logs for all services
docker compose logs -f

# Follow just OpenClaw
docker compose logs -f openclaw

# Follow Ollama (if you're using the production setup)
docker compose logs -f ollama

# View last 100 lines
docker compose logs --tail 100

# View logs with timestamps
docker compose logs --timestamps

Stopping and restarting:

# Stop all services (doesn't delete anything, just stops them)
docker compose stop

# Restart all services
docker compose restart

# Restart just OpenClaw (useful if it becomes unresponsive)
docker compose restart openclaw

# Stop and remove containers (but NOT volumes—your data persists)
docker compose down

# Stop, remove containers, AND delete volumes (careful!)
docker compose down -v

Updating to a new version:

# Pull the latest images from Docker Hub
docker compose pull

# Restart services with the new images
docker compose up -d

# View logs to see if anything broke
docker compose logs -f

Running one-off commands:

# Execute a command inside OpenClaw container
docker compose exec openclaw /bin/bash

# From inside, you can check files:
ls ~/.openclaw/workspace/
cat ~/.openclaw/config.yaml

# Exit with: exit

# Or run a command directly without entering a shell:
docker compose exec openclaw cat /root/.openclaw/config.yaml

Checking status:

# See what containers are running
docker compose ps

# See resource usage
docker compose stats

# See detailed configuration of a service
docker compose config

Advanced: Updating Your Configuration

Let’s say you’re running the standard setup with Claude, but you want to switch to Ollama (local LLM).

Step 1: Edit the file

# Open your docker-compose.yaml in a text editor
nano docker-compose.yaml  # or vim, VS Code, whatever you use

Change:

OPENCLAW_LLM_PROVIDER: claude
OPENCLAW_API_KEY: sk-ant-your-key

To:

OPENCLAW_LLM_PROVIDER: ollama
OLLAMA_MODEL: llama2
OLLAMA_BASE_URL: http://ollama:11434

And add the Ollama service (copy from Template 3).

Step 2: Restart

# Pull new images (if needed)
docker compose pull

# Restart with new configuration
docker compose down
docker compose up -d

# Watch startup
docker compose logs -f

That’s it. Your workspace (~/.openclaw/) is untouched. Your settings persist. OpenClaw will now use Ollama instead of Claude.

Persistent Storage Breakdown: What Actually Persists

Here’s what gets saved where:

~/.openclaw/ (on your machine):

  • config.yaml – global configuration
  • workspace/SOUL.md – your agent’s personality
  • workspace/SKILL.md – custom behaviors
  • workspace/MEMORY.md – long-term memory
  • workspace/skills/ – custom skill definitions
  • workspace/logs/ – every conversation
  • platforms/ – messaging platform credentials and configs
  • Everything here persists forever (until you delete it)

Docker volumes (managed by Docker):

  • In the production setup, ollama_models stores downloaded LLM models
  • These also persist forever
  • Located at /var/lib/docker/volumes/ on your machine (you don’t usually touch this)

Inside the container (disappears when container stops):

  • Temporary files, caches, logs that the application writes but doesn’t need to persist
  • These are fine to lose

The key insight: your workspace and data live outside Docker, so you’re never locked in. You can:

  • Backup ~/.openclaw/ like any folder
  • Run OpenClaw on a different machine (copy your workspace over)
  • Upgrade OpenClaw versions without losing anything
  • Downgrade if needed
  • Even run OpenClaw without Docker and it’ll use the same workspace

Troubleshooting Docker Compose Setups

“Service ‘X’ failed to start”

# Check the logs
docker compose logs servicename

# Common fixes:
# - Is the image available? (docker pull imagename)
# - Is a port already in use? (change the port mapping)
# - Does the service have all the environment variables it needs?

“Cannot reach [service] from another container”

This usually means you forgot to add both services to the same network. Check:

services:
  service1:
    networks:
      - mynetwork
  service2:
    networks:
      - mynetwork

networks:
  mynetwork:
    driver: bridge

“Volume mount permission denied”

# Make sure the directory exists and Docker can access it
mkdir -p ~/.openclaw
chmod 777 ~/.openclaw

# Or use full paths instead of ~

Ollama not responding / models not downloading

# Check Ollama logs
docker compose logs ollama

# Make sure it's healthy
docker compose ps

# Try pulling a model manually
docker compose exec ollama ollama pull llama2

# Check available disk space (models are big)
df -h

Gateway Communication: How Services Talk to Each Other

When you’re running multiple services (OpenClaw and Ollama, for instance), Docker Compose handles the networking automatically. This is one of its secret superpowers.

Here’s what happens under the hood:

  1. You define a network (openclaw-net in our examples)
  2. Both services attach to that network
  3. Docker creates a bridge network with DNS resolution
  4. Services can reach each other by hostname

So when OpenClaw tries to reach Ollama at http://ollama:11434, Docker’s internal DNS translates ollama to the container’s internal IP address. Magic.

This also means:

  • Services can’t reach each other if they’re not on the same network (good for security)
  • You can add multiple networks for complex setups (one service on network A, another on network B)
  • Service names become hostnames automatically (much cleaner than hardcoding IPs)

If you want to expose a service to your machine (not just other containers), use the ports: section. If you only want internal communication, skip ports: entirely.

Performance Tuning: Getting the Most Out of Your Setup

If your OpenClaw setup feels sluggish, a few tweaks can help.

Memory allocation:

services:
  openclaw:
    # ... other config ...
    deploy:
      resources:
        limits:
          memory: 2g
        reservations:
          memory: 1g

This tells Docker to never let OpenClaw consume more than 2GB of RAM, and to reserve at least 1GB for it (so other processes don’t steal it).

CPU allocation:

services:
  openclaw:
    deploy:
      resources:
        limits:
          cpus: "2"
        reservations:
          cpus: "1"

Limits OpenClaw to 2 CPU cores maximum, reserves 1 core minimum.

For Ollama specifically:

If you’re running a local LLM and it’s slow, you probably need more resources:

services:
  ollama:
    deploy:
      resources:
        limits:
          memory: 16g
        reservations:
          memory: 8g

Large language models are memory-hungry. A 7B parameter model needs about 8-10GB. A 70B model needs 40-50GB. Match your resource limits to your model and machine.

Network optimization:

If services are communicating slowly, check if you’re using the right network driver. The bridge driver is fine for most setups, but for maximum performance on Linux, you can use host:

networks:
  openclaw-net:
    driver: host # Linux only; services share host network

Be careful with host—services can’t isolate their network namespaces, which has security implications. Use bridge unless you have a specific reason not to.

Scaling Beyond One Machine: Remote Deployments

Once your local setup is working, you might want to deploy to a remote server (AWS, DigitalOcean, Linode, etc.). Docker Compose is portable—the same yaml file works everywhere.

To deploy to a remote machine:

Step 1: Copy your docker-compose.yaml to the server

scp docker-compose.yaml [email protected]:~/openclaw/
scp ~/.openclaw/ [email protected]:~/openclaw/.openclaw/  # Copy workspace too

Step 2: SSH in and start it

ssh [email protected]
cd ~/openclaw
docker compose up -d

Step 3: Set up reverse proxy (recommended)

If you want to access the dashboard from the internet, don’t expose port 18789 directly. Use a reverse proxy with HTTPS:

services:
  openclaw:
    # ... other config ...
    # Don't use ports: in production; let nginx handle external access
    expose:
      - 18789

  nginx:
    image: nginx:latest
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    networks:
      - openclaw-net

networks:
  openclaw-net:
    driver: bridge

This exposes the OpenClaw port only internally (via expose), and Nginx handles external traffic with HTTPS.

(Nginx config is beyond this article, but the pattern is: Nginx on 443 → forwards to OpenClaw on 18789 internally.)

Versioning Your Configuration

Your docker-compose.yaml is code. Treat it like code—version it.

# Initialize a git repo for your OpenClaw configuration
cd ~/openclaw-setup
git init

# Add your files
git add docker-compose.yaml
echo "OPENCLAW_API_KEY=sk-secret" > .env
git add .env

# But don't commit secrets!
echo ".env" > .gitignore
git add .gitignore

# Commit
git commit -m "Initial OpenClaw setup with Claude backend"

# Later, if you upgrade:
git add docker-compose.yaml
git commit -m "Upgrade to Ollama backend, add GPU support"

# Now you can see history and roll back if needed
git log --oneline

This is especially useful if something breaks—you can see exactly what changed and revert it.

The Reality: What Docker Compose Actually Buys You

Here’s what this setup gives you:

  1. Reproducibility: Your entire setup is in one file. You can share it (minus secrets), version it, run it on another machine. Six months from now, you’ll look at this yaml and instantly understand your entire infrastructure. That’s gold.

  2. Easy scaling: Want to run multiple copies of OpenClaw? Add another service block. Each instance gets its own workspace, its own memory, but they’re all managed together. This is how you go from “hobby project” to “production deployment.”

  3. Composition: Want to add a database? A caching layer? A monitoring service? A Redis instance for session management? Just add another service to the yaml. Docker Compose handles the networking, the dependencies, the health checks—all automatically.

  4. Clean shutdown/restart: docker compose down and docker compose up -d is cleaner than managing processes manually. No zombie processes. No forgotten background services. Everything starts together, stops together, in the right order.

  5. Organized logging: All container logs in one place, easy to tail and filter. You can see what every service is doing without ssh-ing into different machines or digging through system logs.

  6. Portable: The same yaml works on your laptop, a VPS, a Docker Swarm cluster—anywhere Docker runs. Deploy locally during development, move to production on DigitalOcean or AWS or your own server, and the yaml stays exactly the same.

  7. Environment flexibility: Change a single environment variable and your entire setup adapts. Switch from Claude to Ollama. Add a password to your dashboard. Increase memory limits. All without touching code or rebuilding images.

  8. Dependency management: Services can wait for other services to be healthy before starting. Your OpenClaw won’t crash because Ollama wasn’t ready yet—Docker handles the orchestration.

What it doesn’t do:

  • It doesn’t make OpenClaw more powerful—the agent’s capabilities come from SOUL.md, SKILL.md, and your LLM choice
  • It doesn’t change how your agent behaves—that’s entirely in your personality and memory files
  • It doesn’t move your data to the cloud—everything stays local unless you explicitly push it to S3 or similar
  • It’s not required—you can use docker run if you prefer, or run without Docker at all
  • It doesn’t provide automatic backups—you still need to manually backup ~/.openclaw/ or automate it separately

But once you’ve invested five minutes in setting up a docker-compose.yaml, you’ll never go back to typing that monster command line again. It’s just cleaner, more manageable, more professional. And six months from now, when you need to remember exactly how you had things configured? You can just cat the file. No guessing. No hunting through bash history.


Real-World Maintenance Patterns

Let’s talk about what actually happens after you get Docker Compose running. Because deployment is just the beginning.

Weekly Checks

Every week, run these quick checks:

# See what's running
docker compose ps

# Check resource usage
docker compose stats

# Look for errors in recent logs
docker compose logs --tail 50 | grep -i error

If something looks off, pull the full logs for that service and see what’s happening. Most problems show up in logs if you know where to look.

Monthly Updates

Once a month (or when you hear about a security patch), update your images:

# See what's available
docker compose images

# Pull latest versions
docker compose pull

# Restart with new versions
docker compose down
docker compose up -d

# Verify everything came back up
docker compose ps

This is why pinning image versions matters. If you use openclaw/openclaw:latest, you’re taking whatever Docker Hub gives you. If you use openclaw/openclaw:1.2.3, you control when upgrades happen.

Quarterly Deep Dives

Every three months, look at your memory usage:

# How big is your workspace?
du -sh ~/.openclaw/

# Any problem files?
ls -lh ~/.openclaw/memory/ | sort -k5 -h

# Disk space overall
df -h /

If memory is growing too fast, it’s time to clean it up (see the memory management article). If disk space is running low, you might need a bigger drive.

Disaster Recovery

Keep backups. Seriously.

# Daily backup
0 2 * * * tar -czf /backups/openclaw-$(date +\%Y-\%m-\%d).tar.gz ~/.openclaw/

# Keep 30 days
find /backups -name "openclaw-*.tar.gz" -mtime +30 -delete

If something breaks catastrophically:

# Stop the container
docker compose stop

# Restore from backup
tar -xzf /backups/openclaw-2026-03-15.tar.gz -C ~/

# Start again
docker compose start

# Check logs
docker compose logs -f

Why This Matters: The Bigger Picture

Using Docker Compose isn’t just about convenience (though it is convenient). It’s about taking ownership of your infrastructure. Instead of hoping OpenClaw “just works,” you understand exactly what’s happening: which services are running, what they’re talking to, how they’re configured, where your data lives.

When things break (and they will), you have the yaml right there. You can show someone “here’s exactly what I’m running.” You can version it in git. You can diff old versions to see what changed. You can reason about your setup instead of guessing.

That’s professional-grade DevOps. And you just did it with a 50-line yaml file.


Free Discovery Call

Start With a Conversation, Not a Commitment

Every engagement begins with a free 30-minute discovery call. We'll map what's slowing your business down and tell you exactly what we'd fix first – no pitch deck, no obligation.