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.
CODEBLOCK0
Review the output, then set your variables:
CODEBLOCK1
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 |
|---|
| INLINECODE2 | 19002 | Primary personal assistant |
| INLINECODE3 |
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:
CODEBLOCK2
Both commands must succeed before continuing.
Step 1: Pull the Image
The official image is on GitHub Container Registry (GHCR), not Docker Hub.
CODEBLOCK3
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:
CODEBLOCK4
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.
CODEBLOCK5
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:
CODEBLOCK6
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
CODEBLOCK7
4b. Set allowed origins
Non-loopback bind requires explicitly allowed origins or the gateway refuses to start:
CODEBLOCK8
4c. Restart to apply
CODEBLOCK9
Wait 10 seconds, then verify:
CODEBLOCK10
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.
CODEBLOCK11
When prompted, paste the setup token from Step 2 (or your API key if using one).
Success:
CODEBLOCK12
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:
> docker exec $INSTANCE node /app/openclaw.mjs <command>
>
For interactive commands (pasting tokens, TTY prompts), add -it:
CODEBLOCK14
Step 6: Verify Authentication and Gateway
CODEBLOCK15
Success: Output includes INLINECODE31
CODEBLOCK16
Success: Output includes RPC probe: ok
Step 7: Access the Dashboard and Pair Your Browser
Get the gateway auth token
The CLI redacts secrets in output. Read the raw config file instead:
CODEBLOCK17
Find gateway.auth.token in the output. Copy it.
Open the dashboard
Open http://127.0.0.1:$HOST_PORT/ in your browser. Enter the gateway token to log in.
The browser will show "Pairing required." This is expected on first access.
Approve the pairing request
CODEBLOCK18
Find the pending request ID, then approve it:
CODEBLOCK19
Refresh the browser.
Success: Dashboard loads and shows the OpenClaw interface.
Step 8: Configure Discord
Create a Discord bot
- 1. Go to https://discord.com/developers/applications
- Click New Application — name it (e.g. "OpenClaw Isolated")
- Go to Bot → set a username → click Reset Token → copy the token
- Under Privileged Gateway Intents, enable:
-
Message Content Intent (required)
-
Server Members Intent (recommended)
- 5. Go to OAuth2 > URL Generator:
- Scopes:
bot AND
applications.commands (both required)
- Bot Permissions: View Channels, Send Messages, Read Message History, Embed Links, Attach Files
- 6. Copy the generated URL, open it in a browser, and add the bot to your server
Pitfall — slash commands say "not authorized": If you invite the bot without applications.commands, slash commands will not work. Re-authorize using this URL (replace BOT_CLIENT_ID):
CODEBLOCK20
Collect Discord IDs
Enable Developer Mode: Discord → User Settings → Advanced → Developer Mode.
- - Right-click your server icon → Copy Server ID
- Right-click your own avatar → Copy User ID
Configure Discord in the container
Replace YOUR_DISCORD_BOT_TOKEN, YOUR_SERVER_ID, and user IDs with real values.
CODEBLOCK21
Verify Discord is connected
CODEBLOCK22
Success: Output includes INLINECODE40
Confirm users resolved:
CODEBLOCK23
Look for: INLINECODE41
Pitfall — guilds config fails with "expected record, received array": Always set the full guilds object at once. Setting individual guild keys fails:
CODEBLOCK24
Pitfall — adding users later: config set guilds replaces the entire object. Always include ALL existing user IDs when adding new ones.
Optional: Gmail Integration
Skip this section if you do not need Gmail. The rest of the setup works without it.
See references/gmail-setup.md for the complete guide (uses Himalaya — the only reliable option for attachment download).
The key insight: OAuth browser auth does NOT work inside Docker because the callback URL points to localhost inside the container, which the host browser cannot reach. The solution is to authenticate on the host Mac first, then copy tokens into the container.
Optional: Google Drive Integration
Skip this section if you do not need Google Drive. Google Drive uses gog (gogcli) — independent of the Gmail/Himalaya setup. You can set up Drive without setting up Gmail.
See references/google-drive-setup.md for the complete guide.
Maintenance Reference
Daily operations
CODEBLOCK25
Update OpenClaw to a new version
CODEBLOCK26
Transfer files
CODEBLOCK27
Completely remove everything
CODEBLOCK28
Warning: This permanently deletes all config, auth, and workspace data. Backup first.
Troubleshooting
| Symptom | Cause | Fix |
|---|
| INLINECODE46 | Wrong image registry | Use INLINECODE47 |
| Container keeps restarting |
OOM crash | Use
-m 2048m +
-e NODE_OPTIONS="--max-old-space-size=1024" |
|
curl 127.0.0.1:$HOST_PORT returns 000 or connection refused | Gateway on loopback | Set
gateway.bind lan +
allowedOrigins, restart |
| Container crash-loops after setting LAN bind | Missing
allowedOrigins | Set
gateway.controlUi.allowedOrigins, restart |
|
exec: "openclaw": executable file not found | No global CLI binary | Use
node /app/openclaw.mjs |
| Dashboard shows "Pairing required" | Browser not approved |
devices list then
devices approve <ID> |
|
config get gateway.auth.token returns
__OPENCLAW_REDACTED__ | CLI redacts secrets |
cat /home/node/.openclaw/openclaw.json |
| Discord slash commands say "not authorized" | Missing
applications.commands scope or user not in allowlist | Re-authorize bot; check
guilds config |
| Data gone after container recreation | Data on ephemeral overlay | Mount
/home/node as named volume (the launch command above already does this) |
For full pitfall details, see references/pitfalls.md.
Container Reference
| Setting | Value |
|---|
| Container name | INLINECODE66 |
| Image |
ghcr.io/openclaw/openclaw:latest |
| Host port |
19002 |
| Container port |
18789 |
| Dashboard URL | http://127.0.0.1:$HOST_PORT/ |
| Memory limit | 2048 MB |
| CPU limit | 1 core |
| Node.js heap | 1024 MB |
| Restart policy |
unless-stopped |
| CLI prefix inside container |
node /app/openclaw.mjs |
| Volume | Mounted at | Contains |
|---|
| INLINECODE72 | INLINECODE73 | Config, auth, workspace |
| INLINECODE74 |
/app/data | App-level data |
Do You Need to Be at Your Mac?
Short answer: Yes for initial setup. No for ongoing use.
Why initial setup requires your Mac
Three steps require direct Mac access (physical or SSH with port forwarding):
| Step | Why Mac access is needed |
|---|
| Step 2: Claude setup token | INLINECODE76 opens a browser auth flow on localhost. Cannot be done remotely without a browser on the Mac. Skip if using a raw API key — that can be set from anywhere. |
| Step 4: Browser pairing |
The OpenClaw dashboard runs at
http://127.0.0.1:$HOST_PORT/ — only reachable from the Mac. You must open a browser there to pair. |
|
Optional Gmail/Drive OAuth | Google OAuth callback points to
localhost on the Mac. Must authenticate from a browser running on the Mac. |
Remote setup is possible via SSH port forwarding
If you SSH into your Mac from another machine, you can forward the container port to your local browser:
CODEBLOCK29
This lets you complete the browser pairing step remotely.
After setup is complete
Once the container is running and paired, you do not need to be at your Mac. OpenClaw runs as a background service (--restart unless-stopped). You interact with it entirely through your configured channel (Discord, Telegram, etc.) — from your phone, any browser, anywhere.
Configuration
No persistent configuration required. All settings are chosen interactively in Step 0 and set as shell variables ($INSTANCE, $HOST_PORT).
Optional integrations require additional setup:
| Integration | Guide | Requires |
|---|
| Gmail (email + attachments) | INLINECODE83 | Gmail App Password |
| Google Drive / Docs / Sheets / Calendar |
references/google-drive-setup.md | Google Cloud OAuth credentials |
System dependencies:
| Dependency | Purpose | Check |
|---|
| Docker Desktop | Container runtime | INLINECODE85 |
| Python 3 |
Auto-detect script in Step 0 |
python3 --version |
| Claude Code CLI (
claude) | Generate setup token (Claude Max/Pro only) |
claude --version |
openclaw-docker-setup
在 macOS 上的 Docker 中安装一个完全隔离、可用于生产环境的 OpenClaw 实例。
一次会话,从零到运行。所有常见陷阱均已内联处理。
支持在同一台机器上运行多个实例。 每个实例都有唯一的名称和端口。
最终你将获得:
- - 一个命名容器,正在运行并自动重启
- 通过命名 Docker 卷持久化数据(容器重建后数据依然保留)
- 可在 http://127.0.0.1:YOUR_PORT/ 访问的控制面板
- 已配置并可响应的 Discord 频道
- (可选)通过 Himalaya 使用 Gmail(支持附件);通过 gog 使用 Google Drive
第 0 步:选择实例名称和端口
运行此自动检测脚本。它会扫描现有的 OpenClaw 容器,找到下一个可用端口,并建议一个名称。确认或覆盖。
bash
自动检测现有实例并建议下一个可用名称+端口
python3 - << AUTODETECT
import subprocess, re
查找所有正在运行的 openclaw 容器
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(r0\.0\.0\.0:(\d+)->18789, ports)
port = int(m.group(1)) if m else None
existing[name] = port
从 19002 开始查找下一个可用端口
used_ports = set(p for p in existing.values() if p)
port = 19002
while port in used_ports:
port += 1
建议名称
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=== 现有 OpenClaw 实例 ===)
if existing:
for n, p in existing.items():
print(f {n} → 端口 {p})
else:
print( (未找到))
print(f\n=== 新实例建议 ===)
print(f INSTANCE={suggested_name})
print(f HOST_PORT={port})
print(f\n要接受,请运行:)
print(f export INSTANCE={suggested_name})
print(f export HOST_PORT={port})
print(f\n要覆盖,请替换值并使用您选择的值运行 export 命令。)
AUTODETECT
查看输出,然后设置您的变量:
bash
接受建议(粘贴上面输出中的 export 行)
export INSTANCE=openclaw-main
export HOST_PORT=19002
或使用您自己的值覆盖:
export INSTANCE=openclaw-demo
export HOST_PORT=19003
本指南中的所有后续命令都使用 $INSTANCE 和 $HOST_PORT。 保持此终端会话打开,如果打开新会话,请重新导出变量。
多实例示例:
| 实例 | 主机端口 | 用途 |
|---|
| openclaw-main | 19002 | 主要个人助手 |
| openclaw-demo |
19003 | 公开演示/讲座 |
| openclaw-work | 19004 | 工作项目 |
每个实例都有自己的卷($INSTANCE-data、$INSTANCE-home)——数据完全隔离。
前提条件
- - macOS (Darwin) — Intel 或 Apple Silicon
- 已安装并运行 Docker Desktop(或 Docker Engine + CLI)
- 如果使用 Claude Max/Pro 订阅,主机上已安装 Claude Code CLI(claude 命令可用)。如果使用原始 API 密钥,则跳过。
验证 Docker 正在运行:
bash
docker --version
docker ps
两个命令都必须成功才能继续。
第 1 步:拉取镜像
官方镜像位于 GitHub 容器注册表 (GHCR),而非 Docker Hub。
bash
docker pull ghcr.io/openclaw/openclaw:latest
陷阱: Docker Hub 上的 openclaw/openclaw 不存在。始终使用 ghcr.io/openclaw/openclaw:latest。
成功: 拉取完成且无错误,docker images | grep openclaw 显示该镜像。
第 2 步:生成 Claude 设置令牌
如果您有原始 Anthropic API 密钥,请跳过此步骤 — 您将在第 3 步中通过 -e ANTHROPICAPIKEY=sk-ant-api03-... 传递它。
如果您有 Claude Max 或 Pro 订阅,请在主机上生成一个设置令牌:
bash
claude setup-token
复制令牌(格式:sk-ant-oat01-...)。您将在第 5 步中将其粘贴到容器中。
陷阱: 这是一个设置令牌,而不是 API 密钥。两者不同。设置令牌允许容器使用您的订阅进行身份验证。API 密钥按令牌计费。
第 3 步:启动容器
使用您在第 0 步中设置的 $INSTANCE 和 $HOST_PORT 变量。不要减少内存 — 512 MB 和 1024 MB 不足,会导致崩溃循环。
bash
docker run -d \
--name $INSTANCE \
--restart unless-stopped \
-p $HOST_PORT:18789 \
-m 2048m \
--cpus=2 \
--cap-drop=ALL \
--cap-add=NETBINDSERVICE \
--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
如果使用原始 API 密钥而不是设置令牌,请在镜像名称前添加 -e ANTHROPICAPIKEY=sk-ant-api03-...。
等待 10 秒,然后验证:
bash
docker ps --filter name=$INSTANCE
成功: 状态显示 Up X seconds,并且容器没有重启。
陷阱 — OOM 崩溃循环: 如果容器不断重启,请检查日志:
docker logs --tail 20 $INSTANCE
如果看到 JavaScript heap out of memory,容器需要 -m 2048m 和 -e NODE_OPTIONS=--max-old-space-size=1024。使用上面的完整命令重新创建。
陷阱 — 端口冲突: 如果端口已被占用,说明您在第 0 步中选择了错误的 HOSTPORT。重新运行冲突检查:lsof -i :$HOSTPORT。选择一个空闲端口并重新启动。
第 4 步:配置网关
默认情况下,网关绑定到容器内的 127.0.0.1(回环地址)。Docker 端口转发将流量发送到容器的网络接口,而不是其回环地址。您必须切换到 LAN 模式。
4a. 设置绑定到 LAN
bash
docker exec $INSTANCE node /app/openclaw.mjs config set gateway.bind lan
4b. 设置允许的来源
非回环绑定需要显式允许的来源,否则网关拒绝启动:
bash
docker exec $INSTANCE node /app/openclaw.mjs config set \
gateway.controlUi.allowedOrigins [http://127.0.0.1:$HOST_PORT] --json
4c. 重启以应用
bash
docker restart $INSTANCE
等待 10 秒,然后验证:
bash
curl -s -o /dev/null -w %{httpcode} http://127.0.0.1:$HOSTPORT/
成功: 返回 200。
陷阱 — 设置 LAN 绑定后崩溃: 如果您设置了 gateway.bind lan 但忘记了 allowedOrigins 步骤,容器会崩溃循环,并显示 non-loopback Control UI requires gateway.controlUi.allowedOrigins。运行第 4b 步,然后重启。
第 5 步:注册身份验证
重要: 不要直接将令牌粘贴到命令行中 — 它会被存储在 shell 历史记录中。下面的命令会交互式提示。
bash
docker exec -it $INST