高效编写 dockerfile 的核心是让缓存尽可能久地保持有效;应先复制依赖文件(如 package.json、go.mod)再安装依赖,避免过早 copy .,配合 .dockerignore 和多阶段构建提升缓存命中率。
高效编写 dockerfile 指令顺序的核心,是让缓存“尽可能久地保持有效”。关键不是写得快,而是让 docker 在绝大多数代码变更时,仍能复用前面的层。
把依赖文件复制放在最前面
依赖描述文件(如 package.json、requirements.txt、go.mod)通常比源码稳定得多。只要它们没变,安装依赖这一步就能命中缓存。
- COPY package.json package-lock.json ./
- RUN npm install --production
- COPY . .
这样改一个 JS 文件,不会导致 npm install 重新执行;而如果先 COPY . .,哪怕只改 README.md,整个依赖安装层都会失效。
避免 COPY . . 过早出现
COPY . . 是缓存杀手——它会把当前目录下所有未被 .dockerignore 排除的文件都带入构建上下文。一旦任意文件内容变化,该层哈希就变,后续所有层全部重建。
- 不要在 Dockerfile 开头就写
COPY . /app - 优先拆分:先 COPY 明确需要的配置/依赖文件,再 COPY 源码
- 配合
.dockerignore排除node_modules、.git、logs/等无关内容
多阶段构建中分层控制依赖缓存
构建阶段和运行阶段分开,能让编译工具链、测试依赖等不污染最终镜像,同时保留构建阶段的缓存。
- 第一阶段用完整镜像(如
golang:1.21),只 COPYgo.mod+go.sum,再go mod download - 之后再 COPY 源码并 build,生成二进制
- 第二阶段用
alpine,只COPY --from=builder二进制文件
这样即使源码天天改,go mod download 层依然稳定命中缓存。
其他提升缓存稳定性的细节
有些小操作看似无关紧要,实则悄悄破坏缓存:
- ENV 变量写在 RUN 命令之后:会导致 RUN 层哈希变化,应尽量提前定义
- RUN 命令合并:把多个 apt-get 安装写成一条命令,减少层数,也减少缓存断裂点
-
基础镜像固定标签:用
python:3.9-slim-buster而非python:latest,避免 FROM 层意外更新导致全链失效










