Every OpenClaw Docker tutorial tells you to run docker-setup.sh. That script works – until it doesn’t. When it fails, you’re stuck debugging a black box.
The script hides what’s happening. It builds an image, runs onboarding, writes config, starts Compose – all in one pass. If step 3 breaks, you don’t know which layer failed.
The manual approach forces you to understand each piece. You write the Compose file yourself. You run onboarding separately. You see exactly where things go wrong. It takes 10 more minutes upfront. Saves you hours when production breaks at 2am.
Two Paths: Script vs Manual
The official OpenClaw Docker docs push the script. Clone the repo, run ./scripts/docker/setup.sh, answer prompts, done. Fast, opaque.
Script-based deploy packages everything: image build, onboarding wizard, token generation, Compose startup. When the wizard hangs (common with certain Google model versions as of early 2026), you’re guessing what broke.
Manual deploy? You define your docker-compose.yml with explicit resource limits, health checks, restart policies. Run onboarding as a separate container invocation. Control when the gateway starts. Debugging becomes: “which step failed?” not “what did the script do?”
Resource Limits: The OOMKill You Didn’t See Coming
OpenClaw’s gateway compiles a WebAssembly sandbox on first boot. Peaks at roughly 1.3GB RAM. Container has less than 2GB allocated? Linux OOM killer terminates it. Exit code 137 in docker logs. No error message. Gone.
The official Docker installation guide (as of April 2026) states 2GB RAM minimum for image builds. Community troubleshooting docs confirm the 1.3GB peak during WASM compilation. Most tutorials skip this.
Production docker-compose.yml that prevents it:
version: '3.8'
services:
openclaw-gateway:
image: ghcr.io/openclaw/openclaw:latest
container_name: openclaw-gateway
restart: unless-stopped
ports:
- "18789:18789"
volumes:
- ~/.openclaw:/home/node/.openclaw
- ~/openclaw/workspace:/home/node/.openclaw/workspace
environment:
- NODE_ENV=production
- OPENCLAW_GATEWAY_TOKEN=${OPENCLAW_GATEWAY_TOKEN}
mem_limit: 2g
mem_reservation: 1g
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 30s
timeout: 5s
retries: 5
start_period: 20s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
mem_limit: 2g is the hard cap. mem_reservation: 1g guarantees that minimum. Without these? Docker defaults to no limit. Your gateway can consume all host RAM, or get killed when the host runs out.
The Pre-Start vs Post-Start Container Trap
The official Compose file defines two services: openclaw-gateway and openclaw-cli. Most guides show CLI commands like docker compose run --rm openclaw-cli channels login without explaining when you can run them.
The catch: openclaw-cli uses network_mode: "service:openclaw-gateway". It shares the gateway’s network namespace. Gateway container must exist first. Try to run openclaw-cli before docker compose up -d openclaw-gateway? Fails.
The official docs (as of April 2026) state this: “openclaw-cli is a post-start tool.” Pre-start commands – onboarding, initial config – must run through openclaw-gateway directly with --no-deps --entrypoint node.
Correct pre-start onboarding (before docker compose up):
docker compose run --rm --no-deps --entrypoint node openclaw-gateway
dist/index.js onboard --mode local --no-install-daemon
Post-start commands (after gateway is running):
docker compose run --rm openclaw-cli channels login
docker compose run --rm openclaw-cli dashboard --no-open
Miss this and you’ll spend 20 minutes wondering why openclaw-cli can’t reach the gateway.
Production Compose File with Security Hardening
The basic Compose file works. For internet-facing deployments, add these:
version: '3.8'
services:
openclaw-gateway:
image: ghcr.io/openclaw/openclaw:2026.3.7 # Pin version
container_name: openclaw-gateway
restart: unless-stopped
ports:
- "127.0.0.1:18789:18789" # Bind to localhost only
volumes:
- openclaw-config:/home/node/.openclaw
- openclaw-workspace:/home/node/.openclaw/workspace
environment:
- NODE_ENV=production
- OPENCLAW_GATEWAY_TOKEN=${OPENCLAW_GATEWAY_TOKEN}
mem_limit: 2g
mem_reservation: 1g
cpus: 2.0
read_only: true # Filesystem read-only except volumes
tmpfs:
- /tmp
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 30s
timeout: 5s
retries: 5
start_period: 20s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
volumes:
openclaw-config:
openclaw-workspace:
read_only: true prevents writes outside mounted volumes. cap_drop: ALL removes all Linux capabilities; cap_add: NET_BIND_SERVICE adds back only what’s needed. ports: "127.0.0.1:18789:18789" binds to localhost – external access requires a reverse proxy.
Pro tip: Pin the image version (
2026.3.7instead oflatest). As of March 2026, versions2026.3.2and2026.2.26shipped broken in Docker. The alpine/openclaw Docker Hub page documents this explicitly. Pinning prevents auto-updates from breaking production.
The 3-7 Minute First-Boot Silence
You start the gateway. Logs say “Starting…” Port 18789 isn’t open. You wait 30 seconds. Nothing. You restart the container. Still nothing.
Normal.
On first boot, OpenClaw compiles its WebAssembly sandbox from scratch. Depending on CPU, this takes 3 to 7 minutes (as of April 2026, per community troubleshooting guides). Gateway doesn’t open port 18789 until compilation finishes. No progress bar. No status updates. Just silence.
Check progress:
docker logs openclaw-gateway --follow
See active log output plus CPU usage (via docker stats)? It’s compiling. Wait. Logs stopped and CPU is idle? Config error – check for unrecognized keys or missing API credentials.
Real Deploy: Compose File to Running Gateway
Fresh VPS. Docker installed. No OpenClaw repo cloned.
Create directories:
mkdir -p ~/openclaw
cd ~/openclaw
mkdir -p config workspace
Write docker-compose.yml: Use the production file from earlier. Save it as ~/openclaw/docker-compose.yml.
Create .env file:
OPENCLAW_GATEWAY_TOKEN=$(openssl rand -hex 32)
Paste that into ~/openclaw/.env. Add your API key:
ANTHROPIC_API_KEY=sk-ant-your-key-here
Run onboarding (pre-start):
docker compose run --rm --no-deps --entrypoint node openclaw-gateway
dist/index.js onboard --mode local --no-install-daemon
Answer prompts. Choose your model provider. Skip Tailscale unless you need it.
Start the gateway:
docker compose up -d openclaw-gateway
3-7 minutes on first boot. Check logs:
docker logs openclaw-gateway --follow
See “Gateway listening on port 18789”? Ready.
Get the dashboard URL:
docker compose run --rm openclaw-cli dashboard --no-open
Copy the URL with ?token=... and open it.
When the CLI Container “Can’t Reach Gateway”
You run docker compose run --rm openclaw-cli devices list. Connection error. Two common causes:
Gateway isn’t running. Check docker ps | grep openclaw-gateway. Not listed? Container exited. Check logs for the error.
You ran CLI before docker compose up. The CLI container can’t start without the gateway’s network namespace. Run docker compose up -d openclaw-gateway first, then retry.
If openclaw-cli commands fail even with the gateway running, use the direct invocation from Simon Willison’s TIL:
docker compose exec openclaw-gateway node dist/index.js devices list
Bypasses the CLI container entirely. Runs the command inside the gateway.
Version Pinning: Which Tag to Use
Official docs show image: ghcr.io/openclaw/openclaw:latest. Moving target. As of March 2026, the Docker Hub alpine/openclaw maintainer reports that versions 2026.3.2 and 2026.2.26 shipped broken. Version 2026.3.7 is stable.
In production, pin to a known-good version:
image: ghcr.io/openclaw/openclaw:2026.3.7
Want to upgrade? Test the new version on a staging container first. Change the tag, docker compose pull, docker compose up -d, verify it works, then promote. Your config and data persist in volumes, so upgrades are non-destructive – but broken versions can still crash your gateway. One debugging session with a bad version burned through 4 hours last month.
Check the OpenClaw GitHub releases page for changelogs. As of April 2026, the latest release is version 2026.4.11.
What to Do When Port 18789 Won’t Open
You’ve waited 10 minutes. Logs are silent. Port 18789 still isn’t responding.
Is another process using the port?
lsof -i :18789 # macOS/Linux
netstat -ano | findstr :18789 # Windows
Something else owns it? Change the port mapping in docker-compose.yml to "18790:18789".
Is the container running?
docker ps -a | grep openclaw-gateway
Status shows “Exited”? Check logs:
docker logs openclaw-gateway --tail 50
Common exits: missing API key (“ANTHROPIC_API_KEY not set”), OOMKilled (exit 137), unrecognized config key.
Is WASM compilation still running?
docker stats openclaw-gateway --no-stream
CPU above 50% and memory climbing? Compilation in progress. Wait.
Did you bind to the wrong interface?ports set to "127.0.0.1:18789:18789" and you’re trying to access from a different machine? Won’t work. For remote access, use "0.0.0.0:18789:18789" (then secure it with a reverse proxy).
Add Your First Channel
Gateway runs. Dashboard loads. Connect a messaging app.
Telegram is fastest. Create a bot with @BotFather, copy the token:
docker compose run --rm openclaw-cli channels add --channel telegram --token "YOUR_BOT_TOKEN"
Message your bot. You’ll get a pairing code. Approve it:
docker compose run --rm openclaw-cli pairing approve telegram YOUR_CODE
Send “hello” to your bot on Telegram. It replies? Your OpenClaw gateway is live.
Why isn’t the dashboard accessible after docker compose up?
First boot: 3-7 minutes for WASM compilation. Port 18789 only opens after. Check docker logs openclaw-gateway --follow. Logs stopped and container idle? Config error – “unrecognized key” or missing API credentials.
How much RAM does OpenClaw need in Docker?
2GB minimum. Gateway compiles a WebAssembly sandbox on startup, peaking at ~1.3GB RAM (as of April 2026). Less than 2GB? OOMKill (exit 137). Set mem_limit: 2g in docker-compose.yml. Multi-agent setups? 4GB+. One user reported running 3 concurrent agents on 6GB without issues, but 4GB struggled during simultaneous model switches.
Can I run openclaw-cli commands before starting the gateway?
No. openclaw-cli shares the gateway’s network namespace. For pre-start commands (onboarding, config), use docker compose run --rm --no-deps --entrypoint node openclaw-gateway dist/index.js onboard. Only run openclaw-cli after docker compose up -d openclaw-gateway succeeds.