perf(构建): builder 分开拷源码,只改后端不再陪编一次前端
Some checks failed
Deploy / deploy (push) Has been cancelled

builder 阶段是 `COPY . .` 一把梭,而 docker 的层缓存是链式的:那一层的校验和
覆盖整个仓库,改任何一个文件都会把它作废,后面 vite build 和 bun compile 全部
重跑。前端那 160s 一次都躲不掉,改一篇 markdown 也要陪着编。

按各自的输入分开拷,慢的那条放前面(缓存链上越靠前越不容易被碰到):

  只改 apps/api           前端两层命中缓存,只重编后端       ~1s
  只改 apps/web           前端重编,后端被链条带着            ~160s
  改 packages/contract    两边都重编(本来就都依赖它,是对的)
  改 docs / 根上的杂项    builder 根本看不见                  0

代价是 builder 只看得见这里点名拷进来的四样,新加顶层目录要同步加 COPY。

顺带堵掉两个同源的洞:

· apps/web/docs、tests、**/CLAUDE.md 进了 .dockerignore。它们在 apps/web 里面,
  整个目录拷进去的话,改一篇文档也会让 vite build 那层失效。

· .dockerignore 末尾为 --prebuilt 开的例外(!dist/oj2-api、!apps/web/dist)让本地
  产物照样进构建上下文 —— 这是比 `COPY . .` 更隐蔽的一条:在本机编一次后端,
  80MB 的 dist/oj2-api 变了,rsync 上去,服务器就重编一次前端,源码一行没动。
  dist/oj2-api 现在不再被 builder 拷贝;apps/web/dist 仍在 COPY apps/web 里面,
  所以 deploy.sh 头上那条手动 rsync 补了 --exclude dist。CI 走 --prebuilt,
  builder 整个不进构建图,不受影响。

验证:用老 Dockerfile 和新 Dockerfile 各构建一次 --target artifacts 导出对比,
后端二进制 sha256 相同,前端 dist 500 个文件逐个 sha256 一致。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-26 10:25:15 -06:00
parent 1d9686b929
commit f00c941ede
3 changed files with 39 additions and 6 deletions

View File

@@ -62,17 +62,30 @@ RUN bun install --frozen-lockfile \
|| bun install --frozen-lockfile \
|| bun install --frozen-lockfile
COPY . .
# 源码**分开拷**,别用 `COPY . .`:那样任何一个文件变了都会一次作废下面两条
# 构建只改了后端也得陪着重编一次前端160s
#
# 顺序是「先前端后后端」,因为 Dockerfile 的缓存是链式的 —— 一层失效,后面全失效。
# 慢的那条要待在更靠前、更不容易被碰到的位置:
# 只改 apps/api → 前端两层命中缓存只重编后端bun compile几秒
# 只改 apps/web → 前端重编,后端跟着重编,但那点时间无所谓
# 改 packages/contract → 两边都重编(本来就都依赖它,是对的)
#
# 加新的顶层目录 / 根文件时注意builder 只看得见这里点名拷进来的东西。
COPY tsconfig.base.json ./
COPY packages/contract packages/contract
# 前端:产物直接进镜像,不走挂载。
# 一次构建 = 一个版本,切换那天不会出现「后端换了前端忘了拷」这种半新半旧状态。
COPY apps/web apps/web
RUN cd apps/web && bun run build
# 后端:编译成不依赖 node_modules 的单二进制。
# 能这么编是因为 wasm / 原生模块 / 词典都在源码里用 `with { type: "file" }`
# 内嵌成了资源,详见 apps/api/src/vendor/jieba.ts 的注释。
COPY apps/api apps/api
RUN bun build --compile --target=bun-linux-x64 apps/api/src/main.ts --outfile /build/oj2-api
# 前端:产物直接进镜像,不走挂载。
# 一次构建 = 一个版本,切换那天不会出现「后端换了前端忘了拷」这种半新半旧状态。
RUN cd apps/web && bun run build
# ---------------------------------------------------------------- 产物来源
# 两个来源都归一成同样的布局(/artifacts/oj2-api、/artifacts/web下面的运行时
# 阶段只认这个布局,不关心产物是谁编的。