CI 镜像里预装 dsh plugins:DeepSeek Harness 缓存分层、lockfile 与版本固定
把 dsh plugins 预装进 CI 镜像,真正要解决的是版本可复现与断网可用,而不是把安装命令塞进 Dockerfile。 DeepSeek Harness 本体、插件依赖和包管理器缓存的变化频率完全不同,混在一层里会让每次构建都重新下载。下面按构造顺序拆开讲,每一层都给出可以直接抄用的片段与验证方式。
CI 镜像预装 dsh plugins 的分层理由
分层不是为了好看,而是为了让变化慢的东西留在缓存里。
镜像构建的时间主要花在依赖下载上,而三类内容的变化频率并不一致:Harness 本体几个月才动一次,插件跟随 lockfile 变化,包管理器缓存几乎每次都可能不同。把三者压进同一条 RUN,任何一次插件升级都会让本体层失效,缓存命中率随之归零。分层之后,稳定层可以长期复用,易变层才需要重跑。
这也是为什么构建日志里最值得看的不是总耗时,而是每一层是 CACHED 还是重新执行。如果每次改一个插件版本都导致本体重新安装,说明层顺序写反了。层顺序应该从最稳定写到最易变,把 COPY lockfile 放在安装依赖之前,把源码与配置放到最后。
需要强调的是,分层只解决构建效率,不解决运行时行为。插件被装进镜像,并不等于它在运行时不会访问网络,模型调用与外部服务仍然需要出网策略。把构建期依赖和运行期依赖分开看,后面的验证才不会误判。
固定 DeepSeek Harness 本体:基础层只做一件事
基础层只做一件事:把 DeepSeek Harness 本体固定到精确版本,并准备好运行它的最小环境。
基础层里最容易犯的错是写 latest 或者只写主版本号,短时间内看不出问题,过一段时间重建就会拿到不同的运行时。正确做法是写完整的语义化版本,并在注释里记录升级原因,让下一次改动有据可查。Node 主版本也要一起写死,因为原生模块会随 ABI 变化。
FROM node:20-bookworm-slim
ARG DSH_VERSION=0.8.3
RUN npm install -g @deepseek-ai/dsh@${DSH_VERSION} \
&& dsh --version \
&& npm cache clean --force
WORKDIR /workspace
片段里的 dsh --version 不是装饰,它让构建在版本解析失败时立刻失败,而不是把问题留到运行时。npm cache clean --force 放在同一条 RUN 里,避免缓存被写进镜像层,否则清理动作本身也会变成一层垃圾。ARG 默认值可以在 CI 里覆盖,但生产镜像应当显式传入同一个值。
基础层不需要装任何社区插件。把插件留到下一层,既能让本体镜像被多个项目复用,也能让插件升级不牵动运行时。若团队确实需要一组所有任务共用的插件,也应单独做一层,而不是混进本体安装命令。
按 lockfile 装 DSH插件:插件层怎么写
插件层要回答的问题只有一个:这一次构建装进去的 DSH插件,是否与 lockfile 记录的完全一致。
项目目录安装比全局安装更容易复现,因为依赖解析结果被 lockfile 固定,安装时可以要求冻结模式。全局安装适合所有任务共用的小集合,但它没有天然的版本边界,时间一长就会出现某个任务依赖旧版本的情况。两种方式不要在同一个镜像里混用,否则排查冲突会非常痛苦。
COPY package.json package-lock.json ./
RUN npm ci --omit=dev \
&& npx dsh plugin install --from-lockfile \
&& npx dsh plugin list
npm ci 与 npm install 的差别在于前者严格按 lockfile 安装,发现不一致会直接报错,这正是 CI 需要的行为。把 package.json 与 lockfile 一起 COPY,再执行安装,可以让人只改业务代码时仍然命中依赖层缓存。安装完立即执行 plugin list,把实际版本写进构建日志,事后对账才有依据。
如果插件来自多个来源,优先让 lockfile 收敛版本,而不是用强制覆盖把冲突压下去,社区 DeepSeek插件 尤其容易在这里出问题。覆盖只会把冲突推迟到运行时,届时表现可能是某个工具静默失效。遇到无法收敛的依赖,宁可拆成两个镜像,也不要在同一层里赌。
缓存层:DeepSeek Harness plugin 构建里 layer cache 为什么不能省
缓存不能省,但也不能当成安装结果;它只负责加速,正确性必须由 lockfile 与验证来保证。
CI 平台的构建缓存通常按指令与输入文件计算键,lockfile 一改键就变,这是最省心的失效策略。若把缓存目录挂到持久卷,也要让缓存键跟随 lockfile 哈希,否则会命中旧依赖。缓存损坏时的正确反应是回退到全新安装,而不是继续复用。
RUN --mount=type=cache,target=/root/.npm \
npm ci --omit=dev
这条指令只加速下载,不改变安装结果,因此可以在不同构建之间共享。真正决定镜像内容的是 lockfile 与 npm ci 的严格模式,缓存命中与否都不应影响最终插件版本。若发现清空缓存后构建失败,说明安装步骤本身有隐含依赖,应该先修构建脚本。
缓存层还有一个常被忽略的作用:它让失败重试变便宜。CI 里网络抖动很常见,如果没有缓存,一次超时就要从头下载全部依赖。把缓存与安装结果区分开,重试时可以放心复用下载,而验证仍然每次照做。
断网验证:确认 DeepSeek插件 真的在镜像里
只有在一个没有 registry 访问的容器里仍能列出插件并跑通最小任务,预装才算完成。
构建成功不代表插件在镜像里,它可能只是在构建阶段被临时下载,运行时又依赖外网。验证要在禁用出网或移除 registry 配置的容器里进行,先列插件,再跑一个最小会话。任何一步需要联网,都说明镜像的自包含性没有做到。
- 用
--network none启动镜像,确认容器本身能正常运行。 - 执行
dsh plugin list,把输出与构建日志里的清单逐项对照。 - 跑一个只依赖单个插件的最小任务,确认加载路径正确。
- 检查插件目录权限,确保 CI 用户不是靠 root 才能读取。
- 把上述命令与输出写进构建产物,便于回溯。
验证时最好使用与生产一致的启动参数,包括 profile、工作目录与用户。用 root 在默认目录里能跑通,不代表普通 CI 用户也能跑通。权限问题往往在切换用户后才暴露,而那时已经很难和插件安装联系起来。
docker run --rm --network none <image> \
dsh plugin list
把验证做成镜像构建的一部分,而不是文档里的建议,是这套流程能长期成立的关键。每次构建都留下插件清单与最小任务的输出,版本漂移就会在合并前暴露。至于 DSH插件 与 DeepSeek插件 的具体条目,可以到 DSH Plugin Hub 对照名称与来源。
来源:DeepSeek Harness 快速开始、npm @deepseek-ai/dsh、deepseek-harness 仓库。
常见问题
CI 镜像里预装 dsh plugins,系统目录适合所有任务共用,项目目录适合按 lockfile 复现。两者不要混用,否则会出现版本漂移与权限问题。
DeepSeek Harness 插件预装进镜像后仍可能联网,是因为插件运行时要访问模型或外部服务。预装解决的是安装阶段,不解决运行时依赖。
用 lockfile 固定 dsh plugins 版本能保证可复现,但构建缓存仍能显著加速。缓存要按 lockfile 哈希分层,lockfile 变了就失效。
验证 dsh plugins 是否进镜像,要在断网或禁用 registry 的容器里启动并列出插件。能列出且版本正确,才说明镜像里真的有包。
CI 镜像预装多个 dsh plugins 出现版本冲突时,先按 lockfile 收敛,再考虑分层镜像。不要用强制覆盖解决,冲突会在运行时以更难查的方式出现。
相关术语
- base image(基础镜像)
- base image 是 CI 任务复用的底层镜像,预装 DeepSeek Harness 与常用插件能减少每次构建时间。它应保持最小化与可复现。— https://deepseek-harness.github.io/deepseek-harness/guide/quickstart
- lockfile(依赖锁文件)
- lockfile 记录依赖的精确版本与解析结果,是 CI 可复现的关键。镜像缓存应按 lockfile 哈希分层,避免命中过期缓存。— https://www.npmjs.com/package/@deepseek-ai/dsh
- layer cache(镜像层缓存)
- layer cache 是容器构建复用的镜像层,依赖安装步骤越靠前越容易复用。把易变步骤放后面,能减少重复下载。— https://github.com/deepseek-ai/deepseek-harness
- offline verification(断网验证)
- offline verification 是在无网络环境启动镜像并检查插件,用来证明包真的在镜像里而不是构建时临时下载。它是预装成功的硬证据。— https://deepseek-harness.github.io/deepseek-harness/guide/quickstart
来源
- DeepSeek Harness 文档 — 快速开始· deepseek-harness.github.io
- npm — @deepseek-ai/dsh· npm
- deepseek-ai/deepseek-harness 仓库· GitHub