openclaw-docker-setup
Install and configure a fully operational Dockerized OpenClaw instance on macOS from scratch. Includes browser pairing, Discord channel setup, and optional Gmail/Google Drive integration. Use when user asks to "install openclaw docker", "set up dockerized openclaw", "openclaw in docker", or "isolated openclaw instance".
安装 / 下载方式
TotalClaw CLI推荐
totalclaw install github:LeoYeAI~openclaw-master-skills~openclaw-docker-setupcURL直接下载,无需登录
curl -fsSL https://skills.taituai.com/api/skills/github%3ALeoYeAI~openclaw-master-skills~openclaw-docker-setup/file -o openclaw-docker-setup.md# openclaw-docker-setup
Install a fully isolated, production-ready OpenClaw instance inside Docker on macOS.
One session, zero to running. All common pitfalls are handled inline.
**Supports multiple instances on the same machine.** Each instance gets a unique name and port.
**What you end up with:**
- A named container running and auto-restarting
- Persistent data via named Docker volumes (survives container recreation)
- Dashboard accessible at http://127.0.0.1:YOUR_PORT/
- Discord channel configured and responding
- (Optional) Gmail working via Himalaya (supports attachments); Google Drive via gog
---
## Step 0: Pick Your Instance Name and Port
Run this auto-detect script. It scans existing OpenClaw containers, finds the next free port, and suggests a name. Confirm or override.
```bash
# Auto-detect existing instances and suggest next available name+port
python3 - << 'AUTODETECT'
import subprocess, re
# Find all running openclaw containers
result = subprocess.run(
["docker", "ps", "-a", "--format", "{{.Names}} {{.Ports}}"],
capture_output=True, text=True
)
existing = {}
for line in result.stdout.strip().splitlines():
parts = line.split(' ')
name = parts[0]
ports = parts[1] if len(parts) > 1 else ""
if 'openclaw' in name.lower() or '18789' in ports:
m = re.search(r'0\.0\.0\.0:(\d+)->18789', ports)
port = int(m.group(1)) if m else None
existing[name] = port
# Find next free port starting from 19002
used_ports = set(p for p in existing.values() if p)
port = 19002
while port in used_ports:
port += 1
# Suggest name
count = len(existing) + 1
names = ["openclaw-main", "openclaw-work", "openclaw-demo", "openclaw-test", "openclaw-lab"]
suggested_name = names[min(count - 1, len(names) - 1)]
print("\n=== Existing OpenClaw instances ===")
if existing:
for n, p in existing.items():
print(f" {n} → port {p}")
else:
print(" (none found)")
print(f"\n=== Suggested for new instance ===")
print(f" INSTANCE={suggested_name}")
print(f" HOST_PORT={port}")
print(f"\nTo accept, run:")
print(f" export INSTANCE={suggested_name}")
print(f" export HOST_PORT={port}")
print(f"\nTo override, replace the values and run the export commands with your chosen values.")
AUTODETECT
```
Review the output, then set your variables:
```bash
# Accept suggestion (paste the export lines from the output above)
export INSTANCE=openclaw-main
export HOST_PORT=19002
# Or override with your own values:
export INSTANCE=openclaw-demo
export HOST_PORT=19003
```
**All subsequent commands in this guide use `$INSTANCE` and `$HOST_PORT`.** Keep this terminal session open, or re-export the variables if you open a new one.
**Multiple instances example:**
| Instance | Host port | Purpose |
|----------|-----------|---------|
| `openclaw-main` | 19002 | Primary personal assistant |
| `openclaw-demo` | 19003 | Public demo / lecture |
| `openclaw-work` | 19004 | Work projects |
Each instance has its own volumes (`$INSTANCE-data`, `$INSTANCE-home`) — data is fully isolated.
---
## Prerequisites
- macOS (Darwin) — Intel or Apple Silicon
- Docker Desktop installed and running (or Docker Engine + CLI)
- Claude Code CLI installed on the host if using a Claude Max/Pro subscription (`claude` command available). Skip if using a raw API key.
Verify Docker is running:
```bash
docker --version
docker ps
```
Both commands must succeed before continuing.
---
## Step 1: Pull the Image
The official image is on GitHub Container Registry (GHCR), **not Docker Hub**.
```bash
docker pull ghcr.io/openclaw/openclaw:latest
```
> **Pitfall:** `openclaw/openclaw` on Docker Hub does not exist. Always use `ghcr.io/openclaw/openclaw:latest`.
**Success:** Pull completes without error and `docker images | grep openclaw` shows the image.
---
## Step 2: Generate a Claude Setup Token
**Skip this step if you have a raw Anthropic API key** — you will pass it via `-e ANTHROPIC_API_KEY=sk-ant-api03-...` in Step 3 instead.
If you have a Claude Max or Pro subscription, generate a setup token on the host:
```bash
claude setup-token
```
Copy the token (format: `sk-ant-oat01-...`). You will paste it into the container in Step 5.
> **Pitfall:** This is a **setup token**, not an API key. The two are different. A setup token lets the container authenticate using your subscription. An API key charges per token.
---
## Step 3: Launch the Container
Use the `$INSTANCE` and `$HOST_PORT` variables you set in Step 0. Do not reduce the memory — 512 MB and 1024 MB are insufficient and cause crash loops.
```bash
docker run -d \
--name $INSTANCE \
--restart unless-stopped \
-p $HOST_PORT:18789 \
-m 2048m \
--cpus=2 \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
--security-opt no-new-privileges \
-v ${INSTANCE}-data:/app/data \
-v ${INSTANCE}-home:/home/node \
-e NODE_OPTIONS="--max-old-space-size=1024" \
ghcr.io/openclaw/openclaw:latest
```
If using a raw API key instead of a setup token, add `-e ANTHROPIC_API_KEY=sk-ant-api03-...` before the image name.
Wait 10 seconds, then verify:
```bash
docker ps --filter name=$INSTANCE
```
**Success:** Status shows `Up X seconds` and the container is not restarting.
> **Pitfall — OOM crash loop:** If the container keeps restarting, check logs:
> `docker logs --tail 20 $INSTANCE`
> If you see `JavaScript heap out of memory`, the container needs `-m 2048m` AND `-e NODE_OPTIONS="--max-old-space-size=1024"`. Recreate with the full command above.
> **Pitfall — port conflict:** If the port is in use, you chose the wrong `HOST_PORT` in Step 0. Re-run the conflict check: `lsof -i :$HOST_PORT`. Pick a free port and relaunch.
---
## Step 4: Configure the Gateway
The gateway binds to `127.0.0.1` (loopback) inside the container by default. Docker port-forwarding sends traffic to the container's network interface, not its loopback. You must switch to LAN mode.
### 4a. Set bind to LAN
```bash
docker exec $INSTANCE node /app/openclaw.mjs config set gateway.bind lan
```
### 4b. Set allowed origins
Non-loopback bind requires explicitly allowed origins or the gateway refuses to start:
```bash
docker exec $INSTANCE node /app/openclaw.mjs config set \
gateway.controlUi.allowedOrigins '["http://127.0.0.1:$HOST_PORT"]' --json
```
### 4c. Restart to apply
```bash
docker restart $INSTANCE
```
Wait 10 seconds, then verify:
```bash
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:$HOST_PORT/
```
**Success:** Returns `200`.
> **Pitfall — crash after setting LAN bind:** If you set `gateway.bind lan` but forget the `allowedOrigins` step, the container crash-loops with `non-loopback Control UI requires gateway.controlUi.allowedOrigins`. Run step 4b, then restart.
---
## Step 5: Register Authentication
> **Important:** Do NOT paste your token directly in the command line — it would be stored in shell history. The command below prompts interactively.
```bash
docker exec -it $INSTANCE node /app/openclaw.mjs models auth paste-token --provider anthropic
```
When prompted, paste the setup token from Step 2 (or your API key if using one).
**Success:**
```
Updated ~/.openclaw/openclaw.json
Auth profile: anthropic:manual (anthropic/token)
```
> **Pitfall — `openclaw` command not found:** The CLI binary is NOT installed as a global command in this image. Always use `node /app/openclaw.mjs` inside the container:
> ```bash
> docker exec $INSTANCE node /app/openclaw.mjs <command>
> ```
> For interactive commands (pasting tokens, TTY prompts), add `-it`:
> ```bash
> docker exec -it $INSTANCE node /app/openclaw.mjs <command>
> ```
---
## Step 6: Verify Authentication and Gateway
```bash
# Check model auth
docker exec $INSTANCE node /app/openclaw.mjs models status
```
**Success:** Output includes `Providers w/ OAuth/tokens (1): anthropic (1)`
```bash
# Check gateway
docker exec $INSTANCE node /app/openclaw.mjs gateway status
```
**Success:** Output includes `RPC probe: ok`
---
## Step 7: