Docker 镜像分层与构建缓存
镜像不是一个大压缩包,而是由一系列只读层堆叠而成。每条 Dockerfile 指令生成一层,理解分层是优化构建的前提。
分层与缓存
构建时 Docker 逐条比对指令:指令没变、上下文相关文件没变,直接复用缓存层。一旦某层失效,其后的所有层全部重建。
# 反例:代码一改,依赖全重装
COPY . /app
RUN pip install -r requirements.txt
# 正例:依赖文件不变时,安装层永远命中缓存
COPY requirements.txt /app/
RUN pip install -r /app/requirements.txt
COPY . /app
这条「把变动少的放前面」是 Dockerfile 优化的核心。
镜像为什么大
常见原因按频率排序:
- 包管理器缓存:
apt-get update和install没合并成一层,或没清理列表; - 调试工具:curl、vim、git 进了生产镜像;
- COPY . . 把 .git、node_modules、日志全拷进去——用
.dockerignore排除; - 多阶段构建没用上:编译工具链(gcc、node 全量)留在了最终镜像。
# 多阶段:构建环境不进入运行镜像
FROM node:22 AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
层是只读的
运行时的写操作发生在可写层,容器删除即消失。不要把持久化数据写在容器内——数据库文件、上传文件用 volume 挂载。同理,镜像内用 RUN echo > /etc/app.conf 再被 volume 覆盖是常见误操作。
标签与清理
latest 标签会漂移,回滚时找不到确切版本。CI 里同时打两个标签:语义版本 + git sha。定期清理构建垃圾:
docker image prune -f
docker builder prune -f # BuildKit 缓存也会占空间
一条检验标准
docker history <image> 能看到每层的大小和指令。从上往下找最大的几层问自己:这一层在生产运行时需要吗?不需要,就是优化空间。镜像从 1.2GB 降到 200MB 通常只需要一轮这样的审视。