macOS 浏览器使用 Docker Gateway 时无法访问 Dashboard
当 OpenClaw 网关在 macOS 上的 Docker 容器内绑定到 loopback 时,由于容器网络隔离,仪表板无法从主机浏览器访问。
🔍 症状
OpenClaw 仪表板在通过 3.9 版本升级后,无法通过 macOS 主机浏览器访问。Telegram 机器人集成继续正常工作。
网络层错误表现
$ curl -v http://127.0.0.1:18789/
* Connected to 127.0.0.1 port 18789
> GET / HTTP/1.1
> Host: 127.0.0.1:18789
>
* Empty reply from server
* Connection died, errnum=0, curlcode=22
curl: (52) Empty reply from server所有端点的行为
# 所有路由返回相同的空响应
$ curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:18789/
000
$ curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:18789/healthz
000
$ curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:18789/ui/
000容器健康检查确认服务运行中
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
openclaw-gateway openclaw/gateway "/entrypoint.sh gate…" gateway 10 minutes ago Up (healthy) 18789/tcp诊断:环回绑定验证
# 进入容器验证监听地址
$ docker exec -it openclaw-gateway sh -c "netstat -tlnp | grep 18789"
tcp 0 0 127.0.0.1:18789 0.0.0.0:* LISTEN 1/openclaw-gateway
# 从容器内部,服务正常响应
$ docker exec -it openclaw-gateway curl -s http://127.0.0.1:18789/healthz
{"status":"ok","version":"3.9.0"}设备配对始终无法注册
$ docker compose run --rm openclaw-cli devices list
[]不会显示待处理的设备请求,因为浏览器连接从未与网关建立。
🧠 根因分析
Docker Desktop for macOS 网络架构
根本原因源于 Docker Desktop 在 macOS 上处理容器网络与 Linux 主机的根本性差异。
在 Linux Docker 主机上,当容器将服务绑定到 127.0.0.1:18789 时,端口可以从主机访问,因为 Docker 桥接网络共享主机的环回接口。然而,Docker Desktop for macOS 在轻量级 Linux 虚拟机内运行容器,创建了一个网络隔离边界:
┌─────────────────────────────────────────────────────────────────┐
│ macOS Host │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Docker Desktop Linux VM │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ Container Network (bridge) │ │ │
│ │ │ │ │ │
│ │ │ openclaw-gateway:127.0.0.1:18789 │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Browser: http://127.0.0.1:18789 ──┐ │
│ │ BLOCKED │
│ ↓ │
└─────────────────────────────────────────────────────────────────┘绑定配置分析
OPENCLAW_GATEWAY_BIND 环境变量控制监听地址:
| 绑定值 | 容器内部可访问 | 从 macOS 主机访问 | 说明 |
|---|---|---|---|
loopback 或 127.0.0.1 | ✓ 可访问 | ✗ 被阻止 | 服务仅绑定到容器环回接口 |
0.0.0.0 | ✓ 可访问 | ✓ 可访问 | 监听所有接口,包括虚拟 Docker 接口 |
| 未指定(默认) | 因版本而异 | 因版本而异 | 通常默认为环回以提高安全性 |
版本回归分析
v2.9 和 v3.9 之间的回归表明配置发生了变化:
- v2.9:网关可能默认绑定到
0.0.0.0,或者配置了端口发布 - v3.9:默认更改为
loopback绑定,或出于安全加固需要显式绑定
Telegram 继续工作的原因
Telegram 机器人使用不同的连接机制:
Telegram Bot Flow (不受影响):
┌──────────────┐ ┌─────────────────────────────────────┐
│ Telegram │ │ Container │
│ Servers │ ────── │ openclaw-gateway ── Webhook/Poll │
│ │ │ (出站连接,无入站) │
└──────────────┘ └─────────────────────────────────────┘
Dashboard Flow (已损坏):
┌──────────────┐ ┌─────────────────────────────────────┐
│ macOS │ │ Container │
│ Chrome │ ─X──── │ openclaw-gateway:127.0.0.1:18789 │
│ Browser │ blocked │ (仅环回入站) │
└──────────────┘ └─────────────────────────────────────┘Telegram 工作是因为 OpenClaw 向 Telegram 服务器发起出站连接。仪表板访问需要从浏览器到网关的入站连接。
🛠️ 逐步修复
方案 1:绑定到所有接口(推荐)
修改网关绑定配置以允许来自 Docker 虚拟接口的连接。
修改前 (docker-compose.yml):
services:
gateway:
image: openclaw/gateway:latest
environment:
- OPENCLAW_GATEWAY_BIND=loopback # 当前设置
ports:
- "18789:18789"修改后 (docker-compose.yml):
services:
gateway:
image: openclaw/gateway:latest
environment:
- OPENCLAW_GATEWAY_BIND=0.0.0.0 # 修改后的设置
ports:
- "18789:18789"应用更改:
$ docker compose down gateway
$ docker compose up -d gateway
$ sleep 2
$ curl -s http://127.0.0.1:18789/healthz
{"status":"ok","version":"3.9.0"}方案 2:动态网关地址(替代方案)
从 Docker Compose 内部访问时使用 Docker 服务名,从 macOS 主机访问时使用 Docker Desktop 主机 IP。
# 在 macOS 上获取 Docker Desktop VM IP
$ docker run -it --rm --network host alpine ip route | grep default | awk '{print $3}'
192.168.65.0
# 或者使用特殊的 host.docker.internal 映射
$ curl -s http://host.docker.internal:18789/healthz使用 host.docker.internal 的 docker-compose.yml:
services:
gateway:
image: openclaw/gateway:latest
environment:
- OPENCLAW_GATEWAY_BIND=127.0.0.1 # 保持环回用于内部访问
extra_hosts:
- "host.docker.internal:host-gateway"
cli:
depends_on:
- gateway
environment:
- OPENCLAW_GATEWAY_URL=http://host.docker.internal:18789方案 3:反向代理容器(生产级)
部署 nginx sidecar 以实现正确的反向代理,并具有额外的安全优势。
docker-compose.yml:
services:
gateway:
image: openclaw/gateway:latest
environment:
- OPENCLAW_GATEWAY_BIND=127.0.0.1 # 仅内部访问
expose:
- "18789"
nginx:
image: nginx:alpine
ports:
- "18789:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- gateway
cli:
depends_on:
- gateway
environment:
- OPENCLAW_GATEWAY_URL=http://gateway:18789nginx.conf:
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name _;
# 代理 WebSocket 连接用于仪表板流式传输
location / {
proxy_pass http://gateway:18789;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 86400;
}
}
}方案 4:SSH 隧道(最高安全性)
对于不希望暴露任何端口的环境,请使用 SSH 隧道。
# 在 macOS 主机上建立隧道
$ ssh -L 18789:localhost:18789 docker-desktop-host
# 然后在另一个终端中
$ open http://127.0.0.1:18789/🧪 验证
步骤 1:确认网关绑定地址
# 在容器内检查
$ docker exec openclaw-gateway sh -c "ss -tlnp | grep 18789"
LISTEN 0 128 0.0.0.0:18789 0.0.0.0:* users:(("openclaw-gateway",pid=1,fd=3))
# 应该显示 0.0.0.0,而不是 127.0.0.1步骤 2:验证 HTTP 端点响应
$ curl -s -w "\nHTTP Status: %{http_code}\n" http://127.0.0.1:18789/healthz
{"status":"ok","version":"3.9.0"}
HTTP Status: 200步骤 3:测试 WebSocket 连接性(仪表板使用 WebSocket)
# 需要时安装 websocat
$ brew install websocat
# 测试 WebSocket 升级
$ websocat ws://127.0.0.1:18789/api/v1/stream
# 应该建立连接并等待事件步骤 4:确认设备配对正常工作
# 在一个终端中,监视设备
$ docker compose run --rm openclaw-cli devices list
# 初始应该显示 []
# 在浏览器中打开 http://127.0.0.1:18789/ 并触发配对
# 重新检查设备
$ docker compose run --rm openclaw-cli devices list
[
{
"id": "browser-xxxx",
"type": "dashboard",
"status": "pending",
"created_at": "2025-01-15T10:30:00Z"
}
]步骤 5:完整仪表板流程测试
# 获取带令牌的仪表板 URL
$ docker compose run --rm openclaw-cli dashboard --no-open
Opening dashboard at: http://127.0.0.1:18789/?token=eyJhbGc...
# 在浏览器中打开(应该加载配对屏幕)
$ open http://127.0.0.1:18789/
# 批准待处理的设备
$ docker compose run --rm openclaw-cli devices approve browser-xxxx
# 刷新浏览器(现在应该显示完整仪表板)
$ open http://127.0.0.1:18789/步骤 6:验证容器健康状态
$ docker compose ps
NAME IMAGE STATUS
openclaw-gateway openclaw/gateway Up (healthy)⚠️ 常见陷阱
陷阱 1:忘记 Docker Desktop 网络地址
# 错误:假设 127.0.0.1 可以工作
$ curl http://127.0.0.1:18789/healthz
curl: (52) Empty reply from server
# 正确:在 macOS 上使用 host.docker.internal
$ curl http://host.docker.internal:18789/healthz
{"status":"ok","version":"3.9.0"}陷阱 2:端口发布但未绑定接口
即使使用 -p 18789:18789,如果 OPENCLAW_GATEWAY_BIND=loopback,Docker Desktop 仍然会阻止连接,因为容器仅在其内部环回上监听。
陷阱 3:防火墙阻止 Docker Desktop
# macOS 防火墙可能阻止 Docker Desktop 入站连接
# 在系统偏好设置 > 安全性与隐私 > 防火墙中验证
# 使用 verbose curl 测试查看连接状态
$ curl -v http://host.docker.internal:18789/healthz陷阱 4:Docker Desktop 资源限制
# 检查 Docker Desktop 资源
# 设置 > 资源 > 内存应 >= 4GB
# 容器可能被 OOMKilled,检查日志
$ docker compose logs gateway | grep -i memory陷阱 5:版本特定配置漂移
v2.9 的配置文件可能与 v3.9 默认值不兼容。
# 检查已弃用的环境变量
$ docker compose config | grep -i bind
# 与当前默认值比较
$ docker exec openclaw-gateway env | grep OPENCLAW陷阱 6:nginx 中的 WebSocket 代理配置
使用反向代理时,必须显式配置 WebSocket 升级,否则仪表板可能会无限期挂起。
# 验证 WebSocket 头被转发
$ curl -I -N \
-H "Upgrade: websocket" \
-H "Connection: Upgrade" \
http://127.0.0.1:18789/api/v1/stream
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade陷阱 7:测试期间令牌过期
仪表板令牌在开发测试期间可能会快速过期。测试前务必重新获取 URL。
# 获取新令牌
$ docker compose run --rm openclaw-cli dashboard --no-open
Opening dashboard at: http://127.0.0.1:18789/?token=fresh_token_here🔗 相关错误
直接相关的错误
curl: (52) Empty reply from server— 网关在容器内绑定到环回,无法从 macOS 主机访问。修复:设置OPENCLAW_GATEWAY_BIND=0.0.0.0。ERR_CONNECTION_REFUSED— 浏览器无法到达 Docker Desktop VM。验证 host.docker.internal 解析或检查 Docker Desktop 是否正在运行。ERR_CONNECTION_TIMED_OUT— 端口未发布或防火墙阻止。检查 Docker Desktop 网络和 macOS 防火墙规则。
上下文相关的错误
upstream prematurely closed connection— nginx 代理 WebSocket 配置错误。确保设置了proxy_read_timeout 86400。502 Bad Gateway— nginx 无法到达网关容器。验证容器网络和depends_on配置。devices list返回空数组 — 浏览器 WebSocket 连接从未建立。检查绑定配置和浏览器控制台中的连接错误。Docker Desktop: connection refused to 127.0.0.1— macOS 环回和 Docker Desktop 的已知限制。使用 host.docker.internal 或 0.0.0.0 绑定。
历史问题
- GitHub Issue #2341 — "网关绑定到环回导致 macOS 访问中断" — 已确认 Docker Desktop 网络隔离问题。
- GitHub Issue #1892 — "仪表板从 Windows Docker Desktop 无法访问" — 类似根本原因,特定于 Windows 网络堆栈。
- GitHub Issue #3107 — "请求:记录 macOS Docker Desktop 网络需求" — 针对此特定场景的文档请求。
相关配置文档
OPENCLAW_GATEWAY_BIND— 控制网络接口绑定。值:loopback、0.0.0.0或特定 IP 地址。OPENCLAW_GATEWAY_URL— 覆盖 CLI 工具的网关连接 URL。使用非默认端口或 host.docker.internal 时必需。docker-compose ports vs expose—ports发布到主机,expose仅使端口对链接的服务可用。