OpenClaw 一台机器跑多个网关:profile 隔离、端口派生与多租户 cell

2026-08-17

想在一台机器上开第二个 OpenClaw Gateway 的人,动机通常是两类:一类是怕主 bot 挂了没人能进去修,想留一条备用通道;另一类是要给几个不同的人或不同的组织各自提供一套,互相不能看见对方的东西。这两件事在 OpenClaw 文档里是分开的两篇,解法也完全不同,混在一起做的话第二类需求会做出一个看着隔离、实际上没有授权边界的东西。

先说官方给的默认判断:多数场景只需要一个 Gateway。一个 Gateway 本身就能同时挂多个消息连接和多个 agent,所以”我要接三个群、跑四个 agent”这类需求不构成开第二个网关的理由。文档明确说,只有当你需要更强的隔离或冗余(例如一个救援机器人)时,才去跑带独立 profile 和独立端口的多个 Gateway。

第二个判断更关键:OpenClaw 的默认安全模型是一个 Gateway 对应一个受信任的操作者边界,而不是在一个共享 Gateway 内部做敌意多租户隔离。所以如果你托管的用户或组织之间不共享信任边界,就意味着每个租户要跑一套完整的独立 OpenClaw 实例,而不是在同一个网关里靠会话去分。

为什么”同一个网关里分会话”不算租户隔离

这一条值得单独拎出来,因为它是很多人自建时最容易想当然的地方。

文档的原话逻辑是:一个 Gateway 内部已认证的操作者拥有的是受信任的控制面角色;session ID 的作用是选择路由,它负责把一个租户对另一个租户做授权。也就是说,会话隔离是路由概念,不是权限概念。

那 agent 沙箱能不能顶上?文档也回答了:沙箱可以降低不可信内容和工具执行带来的影响,但它不会把一个共享的 Gateway 变成租户授权边界。沙箱、工具策略、提权这三者各管一段,边界差异可以另看沙箱、工具策略与提权的边界

结论就是:给互不信任的人做隔离,只能一人一套完整实例。

同主机跑第二个网关:profile 加端口

如果你的需求属于第一类(同一个操作者,只是想要冗余或者分角色),做法是给每个额外的 Gateway 一个命名 profile 和一个独立的 base port。

通用写法:

# main (default profile)
openclaw setup
openclaw gateway --port 18789

# extra gateway
openclaw --profile ops setup
openclaw --profile ops gateway --port 19789

两边都用命名 profile 也可以:

openclaw --profile main setup
openclaw --profile main gateway --port 18789

openclaw --profile ops setup
openclaw --profile ops gateway --port 19789

装成托管服务是同一套模式:

openclaw gateway install
openclaw --profile ops gateway install --port 19789

救援机器人是这套模式里最具体的一个用法:主 bot 留在默认 profile,救援 bot 跑在 --profile rescue 上、用自己的一个 Telegram bot token、放在另一个 base port(文档给的例子是 19789)。这样主 bot 倒下时,救援 bot 还能进去排查或改配置。

# Rescue bot (separate Telegram bot, separate profile, port 19789)
openclaw --profile rescue onboard
openclaw --profile rescue gateway install --port 19789

如果主 bot 已经在跑,通常这样就够了;如果 onboard 过程已经把救援服务装好了,最后那条 gateway install 可以跳过。onboard 过程中文档建议的几点是:用一个专门给救援账号的独立 Telegram bot token(好处是容易保持只有运维能用、跟主 bot 的渠道或应用安装互相独立、并且是一条基于私聊的简单恢复路径)、profile 名就保持 rescue、base port 至少比主 bot 高 20、workspace 用默认的除非你自己已经在管一套。

--profile rescue onboard 跑的是正常的 onboarding 流程,只是把东西全写进一个单独的 profile,于是救援 bot 会拿到自己的:profile 配置文件、状态目录、workspace(默认 ~/.openclaw/workspace-rescue)、托管服务名、base port 及派生端口、Telegram bot token。提示交互本身跟普通 onboarding 一样。

五项必须唯一的配置

多实例出问题,基本都出在下面这张表里的某一项被两个实例共用了:

配置项作用
OPENCLAW_CONFIG_PATH每实例的配置文件
OPENCLAW_STATE_DIR每实例的会话、凭据、缓存
agents.defaults.workspace每实例的 workspace 根目录
gateway.port(或 --port每实例唯一
派生的 browser/CDP 端口见下一节

任何一项被共享,都会造成配置冲突、状态冲突或端口冲突。这里还有一个容易被误解的点:即使用 OPENCLAW_ALLOW_MULTI_GATEWAY=1 跳过了按配置文件做的单例检查,Gateway 启动时仍然会强制状态目录所有权唯一。也就是说这个环境变量不是”允许两个实例共用一份 state”的开关。

不想走 profile 也可以纯手工用环境变量分开:

OPENCLAW_CONFIG_PATH=~/.openclaw/main.json \
OPENCLAW_STATE_DIR=~/.openclaw \
openclaw gateway --port 18789

OPENCLAW_CONFIG_PATH=~/.openclaw/rescue.json \
OPENCLAW_STATE_DIR=~/.openclaw-rescue \
openclaw gateway --port 19789

端口冲突表现出来的症状常常跟锁文件、残留后台进程混在一起,分辨方法见网关起不来时怎么查

端口是派生出来的,所以 base port 之间要留空

这是”两个网关看起来端口不一样却还是撞车”的根源。base port 就是 gateway.port(或 OPENCLAW_GATEWAY_PORT / --port),其它端口是从它算出来的:

  • 浏览器控制服务端口 = base + 2,只监听 loopback;
  • Canvas host 就跑在 Gateway 的 HTTP 服务上,跟 gateway.port 同端口;
  • 浏览器 profile 的 CDP 端口,从”浏览器控制端口 + 9”到”+ 108”自动分配。

所以文档才要求两个实例的 base port 之间至少留 20 个端口,让派生出来的 browser/CDP 端口永远不会撞上。如果你在配置或环境变量里覆盖了上面任何一项,那唯一性就得你自己保证。

浏览器这一块还有一个明确点名的常见坑:

  • 不要把多个实例的 browser.cdpUrl 钉成同一个值;
  • 每个实例要有自己的浏览器控制端口和 CDP 范围(由各自的 gateway 端口派生);
  • 要显式指定 CDP 端口,用 browser.profiles.<name>.cdpPort,按实例分别设;
  • 接远程 Chrome 用 browser.profiles.<name>.cdpUrl,同样是按 profile、按实例设。

装完之后的自查命令:

openclaw gateway status --deep
openclaw --profile rescue gateway status --deep
openclaw --profile rescue gateway probe
openclaw status
openclaw --profile rescue status
openclaw --profile rescue browser status

gateway status --deep 的价值在于能抓出旧版本安装遗留的 launchd / systemd / schtasks 服务。另外 gateway probe 可能报 multiple reachable gateway identities detected,这条警告只在两种情况下算正常:你确实有意在跑多个隔离网关,或者 OpenClaw 无法证明几个可达的探测目标其实是同一个网关。文档特别说明,SSH 隧道、代理 URL、配置好的远程 URL 指向同一个网关,那属于一个网关的多种传输方式,即使传输端口不同也是一个网关——这类远程通道的配置见远程访问的三条路

真多租户:fleet 的 cell

到了第二类需求,官方给的东西叫 openclaw fleet,它把每个隔离实例称为一个 cell。一个 cell 是跑在硬化容器里的完整 Gateway,拥有自己的状态、凭据、workspace、渠道账号、token,以及一个只发布到宿主 loopback 的端口。

先记两条限定条件:Fleet 目前是实验性的,命令、flag 和容器 profile 可能在版本之间变化且不给弃用窗口;Fleet 在 Linux 和 macOS 宿主上测试过,Windows 宿主目前未测试。

架构上,Fleet CLI 是一个宿主侧的生命周期管理器:它把 cell 记录在 OpenClaw 的状态数据库里,然后请求本地的 Docker 或 Podman 运行时去创建、检查、启动、停止、替换、移除对应容器。远程运行时端点不支持,因为 Fleet 的 bind 路径和 loopback URL 都属于本地宿主。Fleet 不代理租户消息,也不在 cell 之间加共享的应用级数据通路。

每个 cell 跑官方镜像 ghcr.io/openclaw/openclaw,各自在一个用户自定义 bridge 网络上——分开的 bridge 阻止 cell 之间直接走容器 IP 通信,同时保留对模型供应商和渠道的出站 NAT 访问。出站默认不受限;Podman 的 cell 可以用 --network internal 阻断出站同时保留已发布的 loopback 网关端口,而 Docker 的 internal 网络会让那个已发布端口失效,所以 Fleet 直接拒绝这个组合,Docker 侧要用宿主防火墙规则(例如 DOCKER-USER 链)来做出站策略。

端口这块的做法跟单机多网关不同:cell 内部的 Gateway 一律监听 18789,由运行时把它只发布到宿主的 127.0.0.1:<分配端口>。需要远程访问时,在这个 loopback 端点前面放经批准的反向代理、SSH 隧道或 tailnet。

持久化路径值得抄下来:Gateway 持久状态来自 <state-dir>/fleet/cells/<tenant>/,挂载到 /home/node/.openclaw;auth-profile 的加密密钥来自另一条宿主路径 <state-dir>/fleet/auth-profile-secrets/<tenant>/,挂载到 /home/node/.config/openclaw——密钥嵌套在普通状态挂载下面。容器镜像的用户与挂载布局与官方 Docker 部署一致,相关注意点见Docker 部署要点。官方镜像默认用非 root 的 node 用户、UID 1000,Fleet 用宿主兼容的用户映射保证私有 bind 挂载可写:Podman 用 keep-id,rootful Docker 用调用者的非 root 身份,rootless Docker 把容器 root 映射到非特权的 daemon 用户;宿主开了 SELinux 时,Docker 和 Podman 会做私有的 :Z 重标记。

隔离到哪一层,取决于你托管的是谁

文档给了一个三级阶梯,按你租户之间的敌意程度选:

层级做法适用
1硬化容器基线:丢弃所有 Linux capability、开 no-new-privileges、限制 PID/内存/CPU 和可选的可写层磁盘、独立持久挂载与每 cell 网络、只发布到宿主 loopback信任运营者和宿主的租户,默认档
2更强的容器或 VM 隔离:给 Docker/Podman 配 gVisor、Kata Containers 这类更强的 OCI 隔离运行时,或者把 cell 放进 microVM风险更高的负载
3敌意租户分机器:不同 VM 或物理机,运行时管理也分开租户之间不信任同一个宿主运营者

第 2 级有个容易搞错的点:--runtime docker|podman 选的是容器 CLI,不是 OCI 隔离后端,换 gVisor 那类是运行时或基础设施层面的配置。而且文档强调,这个阶梯的任何一级都不改变 OpenClaw 的应用信任模型——一个 Gateway 仍然等于一个受信任的操作者域。

信任边界还要再明确一次:多租户保护的是租户彼此,而 Fleet 运营者和宿主是被所有租户信任的,抵抗一个已被攻陷的宿主是明确的非目标。宿主管理员可以查看容器配置和环境、读挂载的 cell 数据、替换镜像、进入容器;Gateway token 和用 --env 传进去的值,管理员通过 Docker / Podman 的 inspect 都看得到。所以密钥要靠宿主控制、管理访问策略、监控、备份和经批准的密钥管理器来兜,不能指望容器本身。

fleet 的几条命令

创建一个 cell,这条命令只打印一次生成的 Gateway token,务必当场存好:

openclaw fleet create acme

然后在 Fleet 宿主上打开它报告的 http://127.0.0.1:<port>,用该租户的 token 认证,再进到 cell 里面去配供应商凭据和渠道账号。

查容器状态和 Gateway 存活:

openclaw fleet status acme

升级时会保留宿主端口、挂载数据、资源 profile、用户提供的环境变量和 Gateway token:

openclaw fleet upgrade acme

移除容器和注册记录、但保留租户数据:

openclaw fleet rm acme --force

要连持久数据一起删,加 --purge-data。这个操作必须配 --force,不可逆,删之前会做一次解析路径的包含性检查:

openclaw fleet rm acme --purge-data --force

它现在还不做什么

Fleet 明确列了几块没有提供的能力,做方案时别按”应该有”来假设:

  • 共享渠道账号或共享的入站路由(每租户的渠道账号在拥有它的 cell 内部终结);
  • 用精简的每租户宿主进程代替完整 OpenClaw 实例;
  • 一个管理器管多台远程 cell 宿主;
  • 租户自助门户、计费面或委托管理 UI。

文档给的理由是这些能力需要明确的身份、路由、授权和故障域契约,不能靠让多个租户共用一个 Gateway 或共用它的凭据来近似。Fleet 定位是单宿主的生命周期管理器,跨机器、带身份治理的 fleet 需要另一层控制面。

另外几个场景也不适合直接套本文的做法:只是想多接几个渠道、多跑几个 agent 的,回到单网关;Windows 宿主上要用 Fleet 的,官方标注为未测试;需要严格出站管控的 Docker 用户,得自己在宿主防火墙上落规则,因为 Fleet 不会替你做。至于同一操作者下的多网关,隔离清单里那五项只要有一项漏掉,问题往往不会在启动时立刻炸,而是等到浏览器控制或某个 CDP 端口被抢时才暴露出来,所以装完先把 gateway status --deepgateway probe 跑一遍更省事。

延伸阅读


本文依据 OpenClaw 官方仓库(github.com/openclaw/openclawdocs/ 下的官方文档整理,核对日 2026-08-17。 我们没有安装或运行过 OpenClaw,因此不涉及界面外观、操作手感与实测耗时的任何描述; 文中的默认值、命令与配置项均为文档口径,不构成对实际运行结果的保证。 该项目迭代很快,请以仓库最新内容为准。接入即时通讯平台前,请自行确认所在平台的规则与合规要求。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。