Skip to content

OpenClaw Docker Deploy: 2026 Manual Setup Guide (No Script)

Deploy OpenClaw in Docker manually (no docker-setup.sh). Includes production compose files, memory limits, 3 edge cases competitors miss, and the CLI/gateway trap.

8 min readIntermediate

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.7 instead of latest). As of March 2026, versions 2026.3.2 and 2026.2.26 shipped 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.