凌晨两点,CI流水线又挂了。构建日志里全是npm install的进度条,而你只改了一行注释。
问题不在网络,在你的Dockerfile顺序。
![]()
镜像不是文件,是千层饼
多数人以为docker build是在"编译应用",其实是在叠文件系统快照。
每个指令生成一层:FROM打底、RUN拍快照、COPY塞文件、ENV写元数据。这些层只读、用SHA256寻址、能被别的镜像复用。
关键设计:层一旦写好,永不修改。改了就叠新层,旧层原地不动。
这是Docker快的核心,也是你构建慢的元凶。
缓存失效的残酷法则
Docker有一条铁律:某层变了,它上面所有层全部作废。
看看这个经典反例:
FROM node:20COPY . . # 代码天天变RUN npm install # 被迫每次都跑
代码一动,依赖重装。团队越大,浪费越惊人。
调个顺序,世界清静:
FROM node:20COPY package*.json ./ # 很少变RUN npm install # 大多时候直接命中缓存COPY . .
口诀:稳的放上面,变的沉底。
多阶段构建:把垃圾留在门外
编译工具、测试依赖、源码——这些不该出现在生产镜像里。
BuildKit的做法是分阶段:
# 构建阶段FROM node:20 AS builderWORKDIR /appCOPY package*.json ./RUN npm installCOPY . .RUN npm run build# 生产阶段FROM node:20-alpineWORKDIR /appCOPY --from=builder /app/dist ./distCOPY --from=builder /app/node_modules ./node_modulesCMD ["node", "dist/server.js"]
最终镜像只有dist和node_modules,builder阶段的一切都被丢弃。
体积更小、攻击面更小、部署更快。
BuildKit:现在已经是默认答案
老构建器逐行执行,BuildKit能并行、能缓存挂载、能按需加载。
启用方式:
DOCKER_BUILDKIT=1 docker build .
新版本Docker已默认开启,但老旧CI环境可能还在用 legacy builder。
排查慢构建,先确认引擎版本。
调试三板斧
构建慢?检查指令顺序,把稳定依赖往上挪。
缓存诡异失效?docker build --no-cache强制重来,排除干扰。
镜像膨胀?多阶段配合.dockerignore,把不需要的文件挡在构建上下文外。
想看底层细节?docker build --progress=plain .会暴露每一层的实际执行。
文件系统怎么"叠"起来的
Docker用联合文件系统(Union File System,比如OverlayFS)合并各层。
底层只读,顶层可写。你看到的是完整文件系统,内部是多层叠加。
读文件时从上往下找,写文件时复制到顶层再修改(写时复制机制)。
这种设计让镜像分发极高效:相同层只传一次,本地直接复用。
你的Dockerfile是性能蓝图
它不是命令清单,是缓存策略的具象化表达。
层序决定速度,阶段决定体积,上下文决定可重复性。
团队里第一个搞懂这套的人,通常会成为"那个优化构建的"。
下次写Dockerfile前,先画个依赖变更频率图——最静的放最上,最躁的沉到底。你的CI账单会感谢你。
你们团队的构建时间目前是多少?有没有试过把package.json单独COPY一层后,缓存命中率变化有多大?
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.