Docker镜像由只读分层叠加构成,每层对应Dockerfile中改变文件系统的指令(如RUN、COPY),基础层来自FROM,删除操作仅加屏蔽标记;分层实现空间复用与构建缓存,优化需合并RUN、启用多阶段构建及规范使用.dockerignore。
docker 镜像不是一整块硬盘镜像,而是一叠可复用、只读、按顺序堆叠的“文件系统快照层”。理解这一点,是做对构建、压得动体积、查得清问题的前提。
分层结构怎么来的?每层到底存什么
每一层对应 Dockerfile 中一条指令:FROM、RUN、COPY、ADD、ENV 等。但只有改变文件系统内容的指令(如 RUN、COPY)才真正生成新层;LABEL、CMD、EXPOSE 这类元数据指令不产生层,只写入镜像配置。
- 基础层来自 FROM,比如 python:3.11-slim,它本身已是多层叠加的精简 rootfs
- RUN 指令执行后,把命令产生的所有新增/修改文件打包为一个新层,旧层原封不动
- COPY 或 ADD 把本地文件复制进去,也单独成层;哪怕只复制一个 config.json,也会新增一层
- 删除操作(如 rm -rf)不会抹掉下层文件,只是在当前层加个“屏蔽标记”,原始字节仍占空间
为什么分层能省空间和时间?关键在复用与缓存
宿主机上所有镜像共享底层相同层。比如 10 个服务都基于 ubuntu:22.04 构建,系统只存一份该基础层;每个服务只需额外存自己业务相关的几层。
- 构建时,Docker 从上到下逐层比对:如果某层的指令内容和缓存中完全一致,就跳过执行,直接复用已有的层
- 所以把变动少的内容(基础镜像、系统依赖、环境变量)放在前面,变动频繁的内容(源码、配置)放在后面,缓存命中率才高
- docker history --no-trunc <镜像名> 可查看每层的构建命令和大小,是诊断体积和缓存的第一工具
生产级调优的三个硬动作
不是加参数、换基础镜像就能见效,而是围绕分层逻辑做结构性调整。
- 合并 RUN 操作:apt install 后立刻清理 /var/lib/apt/lists,必须写在同一行 RUN 中,否则缓存层里就永久带着几百MB安装包列表
- 启用多阶段构建:编译环境(golang:1.22)和运行环境(alpine:3.20)彻底分离,最终镜像只包含静态二进制文件,不带任何编译器、头文件或源码
- 严格使用 .dockerignore:排除 .git、node_modules、__pycache__、*.log 等非运行必需内容,避免它们被 COPY 进某一层,白白增大镜像
怎么看懂你自己的镜像层?实操两步法
别只看 docker images 显示的总大小,那是个“幻觉”。真实占用和传输成本藏在层里。
- 运行 docker history --format "{{.ID}}\t{{.Size}}\t{{.CreatedBy}}" <镜像名>,观察哪几层异常大(比如某层显示 789MB),再回看 Dockerfile 对应位置
- 用 dive 工具(docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock wagoodman/dive:<version> <镜像名>)交互式钻入每一层,直观看到哪些文件被引入、哪些被删除却未清理


















