OpenClaw Docker `healthy` 阶段内存排查记录

涉及镜像:

1. 先说结论

这次排查最后拿到的核心结论有 4 条:

1. latest 的启动内存峰值明显异常,基线实测约 2.111 GiB,而 2026.3.242026.3.23 分别只有 587.2 MiB427.3 MiB

2. 问题不在 Docker 自己,也不在 canvasHostcontrolUi,因为把这些关掉以后,latest 还是会冲到接近 2.0 GiB

3. 问题高度集中在插件启动链路。最关键的对照实验是: latest 在配置里设 plugins.enabled=false 后,峰值直接掉到 478.9 MiB,并且 7s 左右就进入 healthy

4. 更像是“插件系统在启动早期先把一大坨 bundled plugin / provider catalog / manifest 读进来登记一遍”,而不是某一个单独功能比如 UI 或 canvas 把内存吃爆。

一句人话版:

latest 像是开机先把整个仓库所有纸箱都拆开点数,再慢慢往回塞;老版本更像是只拿当天要用的几个箱子。

2. 测试环境

2.1 Docker 环境

实测环境里的 Docker 版本:


Docker version 29.3.0, build 5927d80
ServerVersion: 29.3.0

2.2 镜像基础信息

镜像元信息里有这些关键点:


ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["node","openclaw.mjs","gateway","--allow-unconfigured"]

CMD-SHELL node -e "fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
Interval: 3m
Timeout: 10s
StartPeriod: 15s
Retries: 3

这个点很重要,因为它解释了为什么有些镜像“应用其实早就起来了”,但 healthy 状态还要等一阵子。容器是否已经可用,和 Docker 什么时候把它标成 healthy,不是同一件事。

2.3 本次测试使用的镜像 digest

镜像Digest
`latest``sha256:5900559f795ef15ea2f0b1fc488726d9b27bb2de398424c14d13c9b1f1ff0d66`
`2026.3.24``sha256:7091859602df6b8cdd59b38adbaed723a6d94806fdd4274d488400dd2fcf0fb6`
`2026.3.23``sha256:f34cc7c00de4bea450c3459ca38b35364603b5be592873d86faf4d03c1492f4b`

3. 测试口径

为了保证这次数据能横向比较,我统一用了下面这套口径:

docker run -d 成功返回开始,

docker inspectState.Health.Status 第一次变成 healthy 为止。

- 启动前 30 秒采样更密

- 后面每秒或每几秒继续采样

- 记录整个窗口里的最大值

换句话说,这次测的是:

“容器从点火到 Docker 正式判它健康这段路上,最高冲到了多少内存。”

4. 基线结果

4.1 最终结果表

镜像进入 `healthy` 用时峰值内存峰值出现时间
`latest``199s``2.111 GiB` (`2161.66 MiB`)`45s`
`2026.3.24``11s``587.2 MiB``9s`
`2026.3.23``11s``427.3 MiB``9s`

4.2 第一眼能看出的事

- 2026.3.243.6x

- 2026.3.235x

5. 我是怎么一步步排的

这一节按真实排查顺序写,不只写结果,也写中间判断是怎么做的。

5.1 先确认不是环境问题

先做了 3 件基础检查:

1. 看 Docker 是否正常。

2. 把 3 个目标镜像都拉下来。

3. 看镜像自带的健康检查到底怎么配的。

这样做的目的很简单:

结果:

所以可以继续往下比。

5.2 先跑一轮基线,别一上来就猜

我没有直接开始脑补“是不是某个插件”,而是先按统一口径把 3 个镜像都跑完,拿到第一手数据。

这一步最重要的价值是:

结果就是前面的基线表:

到这里为止,还只能说“latest 异常”,还不能说“为什么异常”。

5.3 先看健康检查时间,避免被假象带偏

latest 有一个很迷惑人的现象:

所以我单独盯了它的健康状态变化和日志,确认了两件事:

1. 它最终是能进入 healthy 的,不是容器挂了。

2. healthy 很晚这件事,除了应用启动慢,也和 Docker 健康检查节奏有关。

观察到的一个典型时间线:

所以后面分析时,我把“应用日志什么时候出来”和“Docker 什么时候判 healthy”分开看,不混为一谈。

5.4 看进程级内存,先找是谁在吃

接下来最关键的一步,是在容器里直接看进程 RSS。

目的是确认:

latest 采样时看到的典型情况:

时间容器总内存主要进程
`0s``228.6 MiB``openclaw`
`7s``643.3 MiB``openclaw-gateway` RSS 约 `696152 KB`
`14s``1.358 GiB``openclaw-gateway` RSS 约 `1409208 KB`
`21s``1.821 GiB``openclaw-gateway` RSS 继续涨
`28s``1.982 GiB``openclaw-gateway` RSS 接近峰值
`35s``1.989 GiB`仍然是 `openclaw-gateway` 为主

这一步的结论非常明确:

人话版:

不是一群蚂蚁搬空冰箱,是一头河马自己把整盆饲料干了。

5.5 和 `2026.3.24` 做同样的进程对照

同样方式看 2026.3.24,看到的是另一种节奏:

时间容器总内存主要进程
`0s``249.2 MiB``openclaw`
`4s``607.7 MiB``openclaw-gateway`
`9s``608.4 MiB`基本稳定
`15s``healthy`仍然约 `608 MiB`

这一步说明:

5.6 看镜像内容差异,先查“带了什么”

因为 latest 的 Docker 配置基本没变,所以我去看镜像内容本身。

主要看了几类东西:

5.6.1 `/app` 体积对比

latest:

2026.3.24:

这个结果说明:

5.6.2 内置扩展数量对比

统计到的目录数量:

也就是说,latest 确实多带了几类能力。

5.6.3 `latest` 相比 `2026.3.24` 新出现的扩展

/app/extensions 里只在 latest 出现的:

只在 2026.3.24 出现、但 latest 没有的:

/app/dist/extensions 里只在 latest 出现的:

这一步只能说明:

所以还得继续做剥离实验。

5.7 看镜像 revision,对应源码提交范围

镜像 label 里可以看到 git revision:

我把这两个 revision 中间的提交列表和 diff stat 拉出来看了一遍。

不是每个提交都跟问题有关,但里面有几条非常可疑:

这一步的意义不是“直接定罪”,而是给后面的实验找方向。

因为这些名字都指向同一个味道:

启动时扫描 bundled plugins / provider catalogs / manifests。

6. 关键剥离实验

这一部分是最有价值的,因为它们不是猜,是直接动手做对照。

6.1 实验 A: 关掉 `canvasHost`

做法:

结果:

结论:

6.2 实验 B: 配置里同时关 `controlUi` 和 `canvasHost`

做法:


{
  "gateway": {
    "controlUi": { "enabled": false }
  },
  "canvasHost": {
    "enabled": false
  }
}

结果:

结论:

6.3 实验 C: 直接全局关插件

做法:


{
  "plugins": {
    "enabled": false
  },
  "gateway": {
    "controlUi": { "enabled": false }
  },
  "canvasHost": {
    "enabled": false
  }
}

结果:

指标数值
峰值内存`478.9 MiB`
进入 `healthy` 用时`7s`

日志里还能看到一条:


[plugins] memory slot plugin not found or not marked as memory: memory-core

这很正常,因为插件都被关掉了。

这组实验是整次排查里最关键的一锤子。

它直接证明:

6.4 实验 D: 只关掉这次新增的几个插件

为了避免“是不是 just 某个新插件特别肥”,我把 latest 新增的这几项都关了:

并且继续关掉 controlUi / canvasHost

结果:

结论:

6.5 实验 E: 尝试只放行 `anthropic`

为了验证是不是某个 provider 插件特别重,我试了:


{
  "plugins": {
    "allow": ["anthropic"]
  }
}

配合关闭 controlUi / canvasHost

latest 上看到:

2026.3.24 上看到:

这个实验给了两个信号:

1. anthropic 这条链路本身并不轻。

2. 但 latest 即使在相近场景下,仍然比 2026.3.24 更重。

不过这里有一个要特别说明的坑:

所以 plugins.allow 更像是“有参考价值,但不是绝对硬隔离”的实验。

6.6 实验 F: 显式把 `anthropic` 关掉

我还试了:


{
  "plugins": {
    "entries": {
      "anthropic": { "enabled": false }
    }
  }
}

再配合关 controlUi / canvasHost

结果:

这说明了一个很有意思的现象:

这反而更加支持“问题在插件启动早期的加载 / 注册 / 扫描阶段”这个判断。

7. 这次排查中遇到的现场问题

中间碰到过一次宿主机磁盘满了,不记下来容易让后面复现的人踩坑。

当时看到的情况:


Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        48G   48G     0 100% /

docker system df 显示:

我没有去清你的运行中容器或大范围 prune,只删了我自己临时克隆的 Git 仓库,大约腾出 205M,然后继续完成后面的实验。

这个插曲本身不影响结论,但说明:

8. 为什么我最后判断是“插件启动链路”而不是别的

不是因为某一条日志看起来像,而是因为几组对照实验一起指向它。

证据链大概是这样:

1. latest 默认峰值 2.111 GiB,老版本显著更低。

2. 进程级采样显示,主要吃内存的是单个 openclaw-gateway 主进程。

3. 关 canvasHost 没用。

4. 关 controlUi 也没用。

5. 直接 plugins.enabled=false 后,峰值瞬间掉到 478.9 MiB

6. 只关几个新增插件不够。

7. 单独禁 anthropic 也不够,说明问题可能发生在“插件框架先整体加载”这一步。

这串证据拼起来,比单看源码提交名更靠谱。

9. 目前最合理的技术判断

9.1 高置信度判断

9.2 中等置信度判断

下面这些改动方向很可疑,但我这次没有直接做到源码级定罪:

它们可疑,是因为:

但要说“就是哪一行代码”,还需要下一轮更偏源码/heap profile 的排查。

9.3 暂时没法 100% 定论的点

这些问题都值得继续挖,但已经不影响本次“先定位大方向”的结论。

10. 复现建议

如果后面要继续追源码根因,我建议按这个顺序继续,不容易走弯路:

10.1 先保留这次已经证实的最强对照

优先保留这两组:

1. latest 默认启动

2. latest + plugins.enabled=false

这两组差距最大,也最能快速复现问题。

10.2 下一步最值得做的不是再关 UI

controlUi / canvasHost 已经基本排除了,再围着它们转意义不大。

更值得做的是:

10.3 业务层面的临时绕法

如果现在只是想先把服务稳住:

这不是根治,但能立刻止血。

11. 本次关键实验汇总表

实验配置 / 条件结果结论
基线 `latest`默认`2.111 GiB`, `199s`异常明显
基线 `2026.3.24`默认`587.2 MiB`, `11s`正常范围
基线 `2026.3.23`默认`427.3 MiB`, `11s`更低
关 canvas`OPENCLAW_SKIP_CANVAS_HOST=1`约 `1.998 GiB`不是 canvas
关 UI + canvas`controlUi=false`, `canvasHost=false`约 `2.0 GiB`不是 UI / canvas
全关插件`plugins.enabled=false``478.9 MiB`, `7s`关键证据,问题在插件链路
关新增插件组关 `browser` / `litellm` / `speech-core` 等约 `1.999 GiB`不是单纯新增插件组
只放行 anthropic`plugins.allow=["anthropic"]``latest` 约 `2.013 GiB`provider 链路本身不轻,但实验不够干净
老版本只放行 anthropic同上,镜像改为 `2026.3.24`约 `1.688 GiB``latest` 仍更重
显式禁 anthropic`plugins.entries.anthropic.enabled=false`约 `1.99 GiB`禁用可能晚于加载成本发生

12. 最终归纳

这次排查的结论,不是“某个小开关写错了”,而是:

latest 在启动阶段的插件相关加载路径,比 2026.3.24 明显更重,而且这个重度足以把容器启动峰值拉到 2 GiB 级别。

如果只看表面,很容易怀疑 UI、canvas、浏览器服务这些“看得见的东西”。

但真正一轮轮剥离下来,最有杀伤力的证据反而是:

plugins.enabled=false 一关,世界立刻清净。

这就像排查家里跳闸:

这次排查做到的,就是已经把问题从“整栋楼”缩到了“厨房回路”。

下一步如果要继续追,就该去厨房里一条线一条线拆,而不是再回楼下看电表了。