Docker Compose accounts for roughly 65% of all Openclaw deployments, and there is a good reason for it: you get process isolation, reproducible builds, and one-command updates without touching your host system. But the default docker-setup.sh script produces a minimal configuration that skips resource limits, log rotation, and proper restart policies. In production, that catches up with you fast.
This guide gives you a complete, copy-paste-ready Docker Compose setup for Openclaw with health checks, volume persistence, security hardening, and auto-restart baked in. The configuration below reflects production best practices rather than what the quickstart docs suggest.
Prerequisites
Before starting, confirm you have the following installed on your host machine:
- Docker Engine 24+ or Docker Desktop
- Docker Compose v2 (the
docker composeplugin, not the legacydocker-composebinary) - Git for cloning the Openclaw repository
- Minimum 2 GB RAM available for the container (the build process OOM-kills on 1 GB hosts with exit code 137)
- An API key from your LLM provider (OpenAI, Anthropic, or OpenRouter)
Verify your Docker installation:
docker --version
docker compose version
If you need Docker installed from scratch on Ubuntu, the full process takes about two minutes:
sudo apt update && sudo apt install -y ca-certificates curl gnupg
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
If you have not set up Openclaw at all yet, start with our Openclaw setup guide to understand the workspace files, memory system, and model selection before containerizing.
Clone and Run the Setup Script
Start by cloning the Openclaw repository and running the automated setup:
git clone https://github.com/openclaw/openclaw.git
cd openclaw
./scripts/docker/setup.sh
The script walks you through several prompts: gateway mode (choose local), LLM provider selection, and API key entry. It generates a .env file with your gateway token and fires up the containers via Docker Compose.
To use a pre-built image instead of building locally (faster, skips the 2 GB RAM build requirement):
export OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:latest"
./scripts/docker/setup.sh
Pre-built images are tagged at GitHub Container Registry as latest, main, or version-specific like 2026.2.26.
The Production Docker Compose File
The default docker-compose.yml from the repository works, but it lacks resource constraints, health check intervals, restart policies, and log management. Here is a production-hardened version you can use as a drop-in replacement:
version: "3.8"
services:
openclaw-gateway:
image: ghcr.io/openclaw/openclaw:latest
container_name: openclaw-gateway
env_file: .env
ports:
- "18789:18789"
volumes:
- ${OPENCLAW_CONFIG_DIR:-~/.openclaw}:/home/node/.openclaw
- ${OPENCLAW_WORKSPACE_DIR:-~/openclaw/workspace}:/home/node/.openclaw/workspace
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:18789/healthz"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
deploy:
resources:
limits:
memory: 4G
cpus: "2.0"
reservations:
memory: 2G
cpus: "1.0"
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "5"
security_opt:
- no-new-privileges:true
What each addition does and why it matters:
restart: unless-stoppedkeeps the container running through host reboots and crash loops. The only way it stays down is if you explicitly stop it.healthcheckpings the/healthzliveness endpoint every 30 seconds. After 3 consecutive failures, Docker marks the container as unhealthy. Orchestrators like Watchtower or Docker Swarm can auto-restart unhealthy containers.deploy.resourcescaps the container at 4 GB RAM and 2 CPU cores while reserving a floor of 2 GB and 1 core. This prevents Openclaw from starving other services on shared hosts.loggingrotates JSON log files at 50 MB with 5 files max. Without this, Openclaw logs grow unbounded and will fill your disk within weeks on an active instance.security_opt: no-new-privilegesprevents the container process from gaining additional privileges through setuid binaries. A baseline security measure for any production container.
Environment Variables
Create a .env file in the same directory as your docker-compose.yml. The setup script generates most of these, but here is the complete reference:
# Required
OPENCLAW_GATEWAY_TOKEN=your_generated_token_here
LLM_PROVIDER=openai
OPENAI_API_KEY=sk-your-api-key-here
# Optional: use Anthropic instead
# LLM_PROVIDER=anthropic
# ANTHROPIC_API_KEY=sk-ant-your-key-here
# Volume paths (defaults shown)
OPENCLAW_CONFIG_DIR=~/.openclaw
OPENCLAW_WORKSPACE_DIR=~/openclaw/workspace
# Network binding
OPENCLAW_GATEWAY_BIND=lan
# Optional: pre-built image tag
OPENCLAW_IMAGE=ghcr.io/openclaw/openclaw:latest
# Optional: extra apt packages for the container
# OPENCLAW_DOCKER_APT_PACKAGES=ripgrep jq
# Optional: enable sandboxed tool execution
# OPENCLAW_SANDBOX=true
Lock down the file permissions immediately:
chmod 600 .env
Never commit .env to version control. Add it to your .gitignore if you are tracking your deployment configuration in Git.
Volume Mounts and Persistence
Docker Compose bind-mounts two host directories into the container:
| Host Path | Container Path | Contents |
|---|---|---|
~/.openclaw | /home/node/.openclaw | Configuration, memory files, API keys, channel configs |
~/openclaw/workspace | /home/node/.openclaw/workspace | Workspace files (agents.md, soul.md, heartbeat.md, skills) |
These directories survive container replacement. When you pull a new image and recreate the container, all your configuration, memory, and workspace files persist.
The uid 1000 Gotcha
The Openclaw container runs as user node with uid 1000. If your host directories are owned by a different user, the container cannot write to them. This is the single most common cause of “permission denied” errors after a fresh Docker deployment.
Fix it before you start:
sudo chown -R 1000:1000 ~/.openclaw ~/openclaw/workspace
Watch for Disk Growth
Active Openclaw instances generate files that accumulate over time:
media/stores downloaded files and browser screenshots- Session JSONL files log every conversation turn
cron/runs/logs scheduled task executions/tmp/openclaw/holds temporary processing files
On a moderately active instance, expect 500 MB to 1 GB of growth per month. Set a calendar reminder to check disk usage monthly, or add a monitoring alert at 80% capacity.
Health Checks and Monitoring
Openclaw exposes two unauthenticated probe endpoints:
# Liveness: is the process running?
curl -fsS http://127.0.0.1:18789/healthz
# Readiness: is the gateway accepting requests?
curl -fsS http://127.0.0.1:18789/readyz
For deeper inspection (requires your gateway token):
docker compose exec openclaw-gateway \
node dist/index.js health --token "$OPENCLAW_GATEWAY_TOKEN"
The healthcheck directive in the docker-compose.yml above uses the liveness endpoint. If you are running behind a reverse proxy like Nginx or Traefik, point your upstream health check at /readyz instead, since readiness confirms the gateway is ready to process requests, not just alive.
Docker vs Bare Metal vs systemd
Not every deployment needs Docker. Here is when each approach makes sense:
| Factor | Docker Compose | Bare Metal (npm) | systemd Service |
|---|---|---|---|
| Setup complexity | Medium | Low | Medium |
| Process isolation | Strong (container boundary) | None | Process-level only |
| Resource limits | Built-in (cgroups) | Manual (ulimit) | Manual (cgroups via unit file) |
| Update process | docker compose pull && up -d | git pull && npm install | git pull && systemctl restart |
| Rollback | Instant (pin image tag) | Manual (git revert) | Manual (git revert) |
| Multi-instance | Trivial (one compose file per instance) | Separate directories | Separate unit files |
| Log management | Built-in rotation | Manual | journald (built-in) |
| Host contamination | None | Node.js + deps on host | Node.js + deps on host |
| RAM overhead | ~100-200 MB extra | Baseline | Baseline |
Use Docker when you want isolation, run multiple Openclaw instances on one host, or need reproducible deployments across machines. Docker is the right choice for most DevOps teams.
Use bare metal when you are on a resource-constrained VPS with exactly 2 GB RAM and cannot spare the container overhead. Install Openclaw directly with npm and manage it with pm2 or a systemd unit.
Use systemd when you want a single persistent instance on a dedicated Linux server without the Docker layer. Systemd handles restarts natively and integrates with journald for logging. The enterprise deployment guide covers the systemd approach for small teams.
Docker Compose is the best default for anything beyond a quick personal test. The isolation alone prevents the “it works on my machine” problem when you move from local development to a VPS.
Connecting Messaging Channels
After the gateway is running, add your messaging integrations:
# Telegram (most common)
docker compose run --rm openclaw-cli channels add \
--channel telegram --token "YOUR_BOT_TOKEN"
# WhatsApp (QR code flow)
docker compose run --rm openclaw-cli channels login
# Discord
docker compose run --rm openclaw-cli channels add \
--channel discord --token "YOUR_DISCORD_TOKEN"
# Slack
docker compose run --rm openclaw-cli channels add \
--channel slack --token "YOUR_SLACK_TOKEN"
Create your Telegram bot via @BotFather first. If you need Slack integration specifically, our Slack to Openclaw guide covers the full OAuth setup.
Updating Your Deployment
Pull the latest image and recreate the container in one sequence:
docker compose pull
docker compose up -d
docker image prune -f
Your volumes are bind-mounted, so configuration and workspace files are untouched. The prune command cleans up old image layers to reclaim disk space.
To pin a specific version instead of tracking latest:
image: ghcr.io/openclaw/openclaw:2026.3.15
Version pinning is worth the minor inconvenience if you run Openclaw in production. A breaking change in latest at 2 AM is not how you want to discover an incompatibility.
Securing Your Docker Deployment
Beyond no-new-privileges in the compose file, apply these hardening steps for any internet-facing deployment:
Firewall rules to restrict access:
sudo ufw allow 22/tcp
sudo ufw allow 18789/tcp
sudo ufw enable
Better yet, restrict port 18789 to your IP or VPN only:
sudo ufw allow from YOUR_IP to any port 18789
Reverse proxy with TLS using Nginx, Traefik, or Caddy. Never expose the gateway port directly over HTTP in production. A minimal Caddy config:
openclaw.yourdomain.com {
reverse_proxy localhost:18789
}
Caddy handles automatic HTTPS via Let’s Encrypt, which is one less thing to manage.
Drop all capabilities if you do not need Openclaw to execute shell commands on the host:
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
This is the most restrictive configuration. If Openclaw needs to run shell tools, you will need to selectively add capabilities back.
Troubleshooting Docker-Specific Issues
Container exits immediately with code 137
This is an out-of-memory kill. Your host has less than 2 GB RAM available for the container. Either upgrade the host or use a pre-built image (which skips the memory-intensive build step):
export OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:latest"
Permission denied on mounted volumes
The container runs as uid 1000. Fix ownership:
sudo chown -R 1000:1000 ~/.openclaw ~/openclaw/workspace
Port 18789 already in use
Another process is using the port. Find and stop it, or remap Openclaw to a different host port:
ports:
- "18790:18789"
Container is unhealthy but running
The health check is failing. Check the gateway logs:
docker compose logs --tail 50 openclaw-gateway
Common causes: the gateway has not finished starting (increase start_period in the health check), or the LLM provider API key is invalid.
Pairing requests not appearing
If Telegram or other channels show “pairing required,” list and approve pending devices:
docker compose run --rm openclaw-cli devices list
docker compose run --rm openclaw-cli devices approve REQUEST_ID
Frequently Asked Questions
Does Openclaw support Docker on ARM (Apple Silicon, Raspberry Pi)?
The official images are built for amd64. On ARM hosts, Docker builds from source using emulation, which is slow but works. For Apple Silicon Macs, Docker Desktop handles the translation transparently. Raspberry Pi 4 with 4 GB RAM can run Openclaw, but expect slower response times due to emulation overhead.
How much RAM does a running Openclaw container use?
A baseline idle instance uses around 400-600 MB. Active sessions with browser automation push that to 1-2 GB. The 4 GB limit in the production compose file above accounts for memory spikes during tool execution, which are unpredictable. If you are running multiple instances on one host, budget 2 GB per instance minimum.
Can I run multiple Openclaw instances on one machine?
Yes. Duplicate the docker-compose.yml, change the container name and host port, and point the volume mounts to separate directories. Each instance gets isolated config, workspace, and memory. The enterprise deployment guide covers multi-instance architecture in detail.
Should I use Docker volumes or bind mounts?
Bind mounts (what we use in this guide) are simpler for Openclaw because you can directly access the workspace files from your host for editing and backup. Named Docker volumes provide better isolation but make it harder to inspect and back up your configuration files. For most teams, bind mounts are the right choice.
How do I back up my Openclaw Docker deployment?
Back up the two bind-mounted directories: ~/.openclaw (config, memory, keys) and ~/openclaw/workspace (workspace files, skills). A cron job running tar works for simple setups:
tar -czf /backups/openclaw-$(date +%F).tar.gz ~/.openclaw ~/openclaw/workspace
For automated backups, point a tool like restic or borgbackup at those directories.
What happens to my data when I update the container?
Nothing. Your configuration and workspace live in bind-mounted host directories, not inside the container. Pulling a new image and recreating the container leaves all your data intact. This is one of the key advantages of the volume mount approach.
Key Takeaways
- Use the production docker-compose.yml from this guide instead of the default. It adds health checks, resource limits, log rotation, and restart policies that the quickstart skips.
- Fix uid 1000 ownership on your volume directories before first start. This is the number one cause of Docker-specific Openclaw failures.
- Pin your image tag in production rather than tracking
latest. Breaking changes at unexpected times are not worth the convenience. - Set up log rotation from day one. An active Openclaw instance generates substantial log volume, and Docker’s default is to keep everything forever.
- Docker Compose is the right deployment method for most teams. Only drop to bare metal if you are genuinely RAM-constrained, and only move to Kubernetes if you need to manage dozens of instances across regions.
SFAI Labs