Docker 部署 OpenClaw 最新版实战:从端口映射到 Control UI 配对全流程排查
Docker 部署 OpenClaw 最新版实战:从端口映射到 Control UI 配对全流程排查
这次我的目标很直接:在 Docker 中部署最新版 OpenClaw,并让 Mac 本地浏览器通过
http://localhost:8888访问容器中的 OpenClaw 控制界面。看起来只是一个简单的端口映射问题,实际排查下来,涉及 Docker 端口发布、服务绑定地址、Control UI 的 Origin 校验,以及浏览器设备配对四个层面。
背景
我希望实现下面这条访问链路:
Mac 浏览器
-> http://localhost:8888
-> Docker 端口映射 8888:18789
-> 容器内 OpenClaw 监听 18789
-> OpenClaw Control UI / Gateway
一开始以为只要把 Docker 参数写成 -p 8888:18789 就够了,但实际情况比这复杂一些。好在这次完整走通之后,整个路径反而变得很清晰:
- 先让容器真正跑起来
- 再让宿主机能访问到容器内服务
- 再让浏览器来源通过 Control UI 的安全检查
- 最后完成浏览器设备配对
一、先用 Docker 启动最新版 OpenClaw
机器里已经有 OpenClaw 的最新镜像,因此第一步不是重新构建,而是直接用现成镜像启动容器。
我最终使用的是这种方式:
docker rm -f claw
docker run -d \
--name claw \
-p 8888:18789 \
dr34m/openclaw:latest \
openclaw gateway --allow-unconfigured
这里最关键的是:
8888是 Mac 宿主机端口18789是 容器内 OpenClaw Gateway 端口
也就是说,我想要的是:
- 浏览器打开
http://localhost:8888 - Docker 把请求转发到容器内的
18789
到这一步,Docker 层的端口映射已经满足目标了。
二、为什么端口映射对了,页面还是打不开?
最初最容易误判的是:既然已经 -p 8888:18789 了,那打不开一定是 Docker 端口没映射好。
但继续排查后发现,问题不在 Docker 本身,而在 OpenClaw 服务监听地址。
1. 宿主机端口其实已经被 Docker 占住了
从 Docker 状态能看到类似信息:
0.0.0.0:8888->18789/tcp
这说明 Docker 已经在宿主机上监听了 8888。
2. 但访问 localhost:8888 仍然失败
这时访问健康检查会看到 connection reset 一类错误,表面上像是服务没起来。
3. 真正原因:OpenClaw 只绑定在容器内部 loopback
继续看容器日志,发现关键信息是:
[gateway] listening on ws://127.0.0.1:18789
这就意味着:
- OpenClaw 进程是活着的
- 它确实监听在 18789
- 但它只监听 容器内部的
127.0.0.1 - Docker 虽然把宿主机的流量转发到了容器,但服务本身只接受容器 loopback 访问
这也是为什么:
- 容器里测
127.0.0.1:18789是通的 - 宿主机测
127.0.0.1:8888却不通
本质上不是“端口映射失败”,而是“服务没有对容器网络开放”。
三、把 OpenClaw 的绑定地址改成 lan
OpenClaw 的 Docker 文档里提到,Docker 场景下不要让 Gateway 只绑定在 loopback,而应该使用 lan。
1. 配置文件位置
这次修改的是容器内部的配置文件:
/home/node/.openclaw/openclaw.json
2. 需要增加的配置
{
"gateway": {
"bind": "lan",
"mode": "local"
}
}
3. 两个配置项分别做什么
gateway.bind = "lan"
这是这次修复里最核心的一步。
它的作用是让 OpenClaw 从只监听容器内部 loopback,变成监听对容器网络可达的地址。
修改前,日志里是:
ws://127.0.0.1:18789
修改后,日志变成:
ws://0.0.0.0:18789
也就是说服务从“只在容器内部自言自语”,变成了“容器网络里的请求也能进来”。
gateway.mode = "local"
这个配置主要是为了让 Docker / 本地场景下的 Gateway 行为更稳定,避免后续 Control UI、CLI 或配对时把目标地址理解错。
可以把它看作一个配套项:
bind解决“服务能不能被外部打到”mode让“本地部署语义”保持一致
4. 改完后重启容器
docker restart claw
5. 验证结果
这时再访问:
curl http://127.0.0.1:8888/healthz
已经能返回:
{"ok":true,"status":"live"}
说明:
- Docker 端口映射正常
- OpenClaw 监听地址也已经正确
- Mac 宿主机终于能真正打到容器内服务
四、页面能打开了,但又报 origin not allowed
到这里,静态页面已经能打开,但并不代表 Control UI 已经能和 Gateway 建立控制连接。
此时浏览器会提示类似:
origin not allowed
(open the Control UI from the gateway host or allow it in gateway.controlUi.allowedOrigins)
1. 这是什么问题?
这不是端口问题,而是 Control UI 的来源校验(Origin 校验)。
我这次是通过:
http://localhost:8888
打开页面的。
于是浏览器在建立 WebSocket / 控制连接时,会带上 Origin:
http://localhost:8888
但 OpenClaw 当前配置里并没有明确允许这个来源,于是连接被拒绝。
2. 解决方法
继续修改同一个配置文件:
/home/node/.openclaw/openclaw.json
加入:
{
"gateway": {
"bind": "lan",
"mode": "local",
"controlUi": {
"allowedOrigins": [
"http://localhost:8888",
"http://127.0.0.1:8888"
]
}
}
}
3. 为什么要写两个 Origin
因为访问本地服务时,实际浏览器来源可能有两种常见形式:
http://localhost:8888http://127.0.0.1:8888
两个都加进去,能避免后续切换地址时再踩一次坑。
如果你未来打算通过局域网 IP 访问,比如:
http://192.168.x.x:8888
那也需要把对应的 Origin 再补进去。
五、Origin 通过了,但又提示 pairing required
这一步也很有代表性。
很多时候看到 pairing required 会以为服务还是坏的,其实恰恰相反:
- 端口已经通了
- Origin 校验也已经过了
- 浏览器已经开始真正接触 Gateway 了
- 只是当前浏览器设备还没有被信任
换句话说,这是一个 安全配对问题,不是网络问题。
1. pairing required 的含义
OpenClaw 不会默认信任所有能打开页面的浏览器设备。Control UI 和 Gateway 建立控制连接前,需要这个浏览器设备先完成配对授权。
2. 这一步怎么做
要做三件事:
- 列出待配对设备
- 找到当前浏览器对应的 requestId
- 批准这个请求
因为这次不是通过 Docker Compose,而是单容器 docker run 启动的,所以我直接在容器里调用 OpenClaw CLI。
3. 先获取 token,再查看待配对设备
OpenClaw Gateway 当前是 token 鉴权,所以 CLI 连接 Gateway 时必须显式带 token。
查看待配对设备的思路是:
openclaw devices list --url ws://127.0.0.1:18789 --token <token>
这时能看到一个 Pending 请求。
4. 批准该浏览器设备
拿到 requestId 后,执行:
openclaw devices approve <requestId> --url ws://127.0.0.1:18789 --token <token>
批准成功后,再次查看设备列表,就会从 Pending 变成 Paired。
此时浏览器刷新页面,就可以真正进入 Control UI。
六、这次到底改了哪些地方?
如果把整个过程压缩成最关键的几个改动,其实就是下面这些。
1. Docker 启动方式
docker run -d \
--name claw \
-p 8888:18789 \
dr34m/openclaw:latest \
openclaw gateway --allow-unconfigured
2. 容器内配置文件
/home/node/.openclaw/openclaw.json
3. 最终关键配置
{
"gateway": {
"auth": {
"mode": "token",
"token": "..."
},
"bind": "lan",
"mode": "local",
"controlUi": {
"allowedOrigins": [
"http://localhost:8888",
"http://127.0.0.1:8888"
]
}
}
}
4. 设备配对
- 列出待配对设备
- 找到 requestId
- 手动 approve
七、这次排查真正解决的是哪四层问题?
这次最值得记住的一点是:浏览器能不能打开一个 Docker 里的 OpenClaw,不是只有端口映射这一层。
实际上需要依次打通四层:
第 1 层:Docker 端口映射
8888 -> 18789
这是入口,但只是入口。
第 2 层:OpenClaw 的监听地址
如果服务只监听 127.0.0.1,那 Docker 端口映射再对也没用。
要改成:
"gateway": {
"bind": "lan"
}
第 3 层:Control UI 的 Origin 白名单
页面资源能打开,不等于 Control UI 可以连接 Gateway。
要允许:
"allowedOrigins": [
"http://localhost:8888",
"http://127.0.0.1:8888"
]
第 4 层:浏览器设备配对
即使前面都没问题,浏览器设备依然可能因为没被批准而卡在:
pairing required
需要手动批准当前浏览器设备。
八、我的结论
这次看起来只是“把 Docker 端口从 18789 换成 8888”,但真实工作量其实在排查路径上。
如果只看 Docker 参数,很容易以为问题已经解决;但实际还要一路确认:
- 服务到底有没有起来
- 它监听的是不是对外可达地址
- Control UI 有没有允许当前浏览器 Origin
- 这个浏览器设备有没有完成配对
真正跑通之后,思路反而非常清楚:
端口映射解决“流量能不能到容器门口”,绑定地址解决“服务接不接这个流量”,Origin 白名单解决“Control UI 认不认这个页面来源”,设备配对解决“Gateway 信不信这个浏览器”。
把这四层都打通之后,http://localhost:8888 才真正变成一个可用的 OpenClaw 控制入口。
写在最后
这次部署虽然绕了一点,但也顺手把 OpenClaw 在 Docker 场景下的关键连接链路梳理清楚了。对后面做长期部署、改成 docker compose、补充模型 provider 配置,都会轻松很多。
如果后续要把它变成长期可维护的方案,我更推荐继续往两个方向走:
- 把当前运行参数固化成
docker-compose.yml - 补齐模型 provider / API key / 持久化配置
这样它就不只是“页面能打开”,而是一个真正可长期使用的 Docker 版 OpenClaw 环境。