April 19, 2026 • 版本: v3.9

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 主机访问说明
loopback127.0.0.1✓ 可访问✗ 被阻止服务仅绑定到容器环回接口
0.0.0.0✓ 可访问✓ 可访问监听所有接口,包括虚拟 Docker 接口
未指定(默认)因版本而异因版本而异通常默认为环回以提高安全性

版本回归分析

v2.9 和 v3.9 之间的回归表明配置发生了变化:

  1. v2.9:网关可能默认绑定到 0.0.0.0,或者配置了端口发布
  2. 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:18789

nginx.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 — 控制网络接口绑定。值:loopback0.0.0.0 或特定 IP 地址。
  • OPENCLAW_GATEWAY_URL — 覆盖 CLI 工具的网关连接 URL。使用非默认端口或 host.docker.internal 时必需。
  • docker-compose ports vs exposeports 发布到主机,expose 仅使端口对链接的服务可用。

依据与来源

本故障排除指南由 FixClaw 智能管线从社区讨论中自动合成。