Docker构建并非每次都从零开始,它依赖镜像分层与指令缓存来加速重复构建。理解缓存命中规则,并针对性调整Dockerfile写法,是提升CI效率和本地开发体验的关键。不少团队在没搞清楚规则前随意安排指令顺序,导致缓存几乎从未生效。

Dockerfile缓存是如何工作的
Docker将Dockerfile中的每一条指令(如RUN、COPY、ADD)都视为一层。构建时,Docker会为当前构建上下文和指令内容计算校验值。如果某条指令及其之前的所有层都和本地已有镜像层完全一致,且指令文本未变,Docker就直接复用缓存层,跳过实际执行。
需要注意的是,缓存具有传递性。一旦某一层未命中,它之后的所有层都会被迫重新构建。这意味着把频繁变动的操作放在前面,会让后续所有层的缓存全部作废。例如先COPY源代码再RUN安装依赖,每次改代码都会让耗时的依赖安装重来一遍。
校验范围与隐藏陷阱
对于RUN指令,Docker比较的是指令字符串本身,而不是命令执行后的环境差异。如果你写RUN apt-get update,第一次构建后缓存层固定;之后即使远程仓库有了新包,只要指令文本不变,Docker仍用旧缓存。因此常将update与install写在同一层。
对于COPY和ADD,Docker不仅看指令,还会对拷贝的文件内容做校验。只要被复制文件任意一个字节变化,该层及之后全部失效。这也是为什么要把不常变的文件先拷贝,常变的源码放后面。
# 低效写法:源码变动导致依赖重装 COPY . /app RUN pip install -r /app/requirements.txt # 高效写法:先拷贝依赖描述,再拷贝源码 COPY requirements.txt /app/requirements.txt RUN pip install -r /app/requirements.txt COPY . /app
常见优化策略
除了调整指令顺序,还有多种手段可以压缩构建时间和镜像体积。最核心的思路是:让易变内容尽量靠后,让重量级且稳定的操作尽量靠前,并削减进入上下文的文件。
使用.dockerignore
构建上下文是执行docker build时指定的目录,Docker默认将其全部打包发给守护进程。若其中包含node_modules、git历史或日志,不仅传输慢,还可能因文件变动破坏COPY缓存。通过.dockerignore排除无关内容,能显著减少上下文体积。
下面是一个典型的Python项目忽略配置。它跳过了本地依赖、缓存与版本控制目录,确保只有源码和声明文件参与校验,从而避免无谓的缓存失效。
.git __pycache__ *.pyc venv node_modules dist *.log
多阶段构建
多阶段构建允许在一个Dockerfile中使用多个FROM基础镜像。前期阶段负责编译和安装工具链,后期阶段只复制产物,从而抛弃编译器等庞杂内容。这样既缩小了最终镜像,也隔离了构建缓存与运行缓存。
以下示例第一阶段用golang镜像编译二进制,第二阶段基于极小的alpine仅携带可执行文件。即便源码改动,只要编译指令不变,第一阶段依赖下载层仍可命中。
FROM golang:1.21 AS builder WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . RUN go build -o /app/server FROM alpine:3.19 COPY --from=builder /app/server /usr/local/bin/server CMD ["server"]
指令合并与层清理
每条RUN都会产生新层,过多零散层会拖慢推送拉取。将相关联的安装与清理写在同一RUN中,既能减少层数,也能避免缓存了含垃圾的层。比如在apt安装后立刻rm掉列表文件。
下面写法把更新、安装、清理放在一层,既利用缓存又控制体积。若之后需增删包,改这一行即可,旧层失效但新层干净。
RUN apt-get update
&& apt-get install -y --no-install-recommends curl ca-certificates
&& rm -rf /var/lib/apt/lists/*
利用构建参数规避缓存
有时我们确实想绕过某层缓存,比如强制拉取最新基础镜像。可通过BuildKit的--no-cache或ARG配合时间戳实现。但要注意这会降低效率,仅建议在依赖外源频繁变动时使用。
例如在CI中每日一次不使用缓存重建基础依赖层,平时则依靠缓存加速。这种折中方案在大型团队中较为常见。
# 日常构建 docker build -t app:dev . # 强制跳过缓存 docker build --no-cache -t app:fresh .
总结与建议
优化Dockerfile的核心在于尊重缓存传递性:稳定且繁重的工作前置,易变代码后置,配合.dockerignore与多阶段构建。上线前可用docker build --progress=plain观察每层命中情况,针对性调整。
当构建慢成为瓶颈,先别急着升级机器,花十分钟重排Dockerfile往往能换来十倍提速。缓存机制本身不复杂,难的是在真实项目里坚持良好分层习惯。
Dockerfile构建缓存镜像优化修改时间:2026-08-11 21:48:30