Docker 系列(三):镜像与分层存储原理
2026年7月26日 · 11 分钟
镜像不是一个大文件,而是「一叠」只读层
这是 Docker 最精妙的设计之一。一个镜像不是单个文件,而是由多个只读层 (layer)叠加而成,底层技术叫 联合文件系统(UnionFS)。
镜像 my-node-app
┌────────────────────────────┐
│ L4: COPY 你的应用代码 │ ← 每一层只记录"变化"
├────────────────────────────┤
│ L3: RUN npm install │
├────────────────────────────┤
│ L2: 装 Node.js 运行时 │
├────────────────────────────┤
│ L1: 基础系统 (debian slim) │ ← 最底层的基础镜像
└────────────────────────────┘
每一层只保存「相对于下一层的改动」。这套设计带来两个巨大好处:
好处一:层可以被共享复用
如果你有 10 个镜像都基于 node:22-alpine,那个基础层在磁盘上只存一份,
10 个镜像共享它。这就是为什么你拉第二个基于相同基础的镜像时,会看到很多层显示
Already exists——它们复用了本地已有的层。
docker pull node:22-alpine
docker pull python:3.12-alpine
# 你会发现两者共享了 alpine 基础层,第二个拉得飞快
好处二:构建缓存
因为层是独立的,Docker 构建镜像时会缓存每一层。只要某一层及其之前的层没变, 就直接用缓存,不重新执行。这也是我们下一篇优化 Dockerfile 的核心依据。
容器 = 镜像 + 一个可写层
镜像的所有层都是只读的。那容器运行时产生的数据(新文件、日志、临时文件)存哪?
答案:Docker 在镜像的只读层之上,加了一个可写层(container layer)。
┌────────────────────────────┐
│ 可写层(容器专属,读写) │ ← 容器运行时的所有改动都在这
╞════════════════════════════╡
│ 镜像的只读层(多个共享) │
└────────────────────────────┘
这带来一个关键机制——写时复制(Copy-on-Write, CoW):当容器要修改一个来自 只读层的文件时,Docker 会先把该文件复制到可写层,再修改副本。只读层始终不变。
这解释了两个重要现象:
- 同一镜像启动多个容器,它们共享只读层,各自只有独立的可写层,所以极其省空间。
- 容器删除后,可写层也随之消失——容器内产生的数据默认不会持久保存。这正是我们 第五篇要讲「数据卷」的原因。
常用镜像管理命令
docker images # 列出本地所有镜像
docker pull redis:7 # 拉取指定标签的镜像
docker rmi nginx # 删除镜像
docker image prune # 删除悬空镜像(<none> 标签的)
查看镜像的分层历史
docker history nginx
# 会列出每一层是由什么指令产生的、占多大空间
这条命令非常适合分析「我的镜像为什么这么大」——你能清楚看到哪一层最占空间。
查看镜像的详细元数据
docker inspect nginx
# 输出 JSON:包含层信息、环境变量、启动命令、暴露端口等
镜像的命名与标签
镜像的完整名字格式是:
[仓库地址/][命名空间/]镜像名[:标签]
例:
nginx # 等价于 docker.io/library/nginx:latest
node:22-alpine # node 镜像的 22-alpine 标签
ghcr.io/jared2566/blog:latest # 私有仓库 GHCR 上的镜像
- 标签(tag):区分同一镜像的不同版本,如
node:20、node:22、node:22-alpine。 - latest 的坑:不写标签默认用
latest,但latest不等于「最新」,它只是个 默认标签名。生产环境务必写明确版本号,否则某天基础镜像更新,你的构建可能突然出问题。
给镜像打标签
docker tag myapp:latest myapp:v1.0.0 # 加一个版本标签
docker tag myapp:latest registry.example.com/team/myapp:v1.0.0 # 打成推送用的名字
alpine:为什么大家都爱它
你会经常看到 -alpine 后缀的镜像(如 node:22-alpine)。Alpine Linux 是一个极简
发行版,基础镜像只有 5 MB 左右,而普通 Debian 基础要上百 MB。用 alpine 能显著
缩小镜像体积、加快分发速度。
小提醒:alpine 用的是 musl libc 而非 glibc,极少数依赖 glibc 的程序可能不兼容, 遇到诡异问题时可换回 debian slim 版本试试。
本篇小结
- 镜像由多个只读层叠加而成,底层是联合文件系统。
- 分层的两大红利:层共享复用 + 构建缓存。
- 容器 = 只读镜像层 + 一个可写层;修改走写时复制。
- 容器删除后可写层消失,所以要持久化数据必须用数据卷。
- 标签要写明确版本,别迷信
latest;能用 alpine 就用 alpine 瘦身。
原理铺垫完毕,下一篇进入实战核心:编写 Dockerfile,构建属于你自己的镜像。