← 全部文章
Docker镜像原理

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 会先把该文件复制到可写层,再修改副本。只读层始终不变。

这解释了两个重要现象:

  1. 同一镜像启动多个容器,它们共享只读层,各自只有独立的可写层,所以极其省空间。
  2. 容器删除后,可写层也随之消失——容器内产生的数据默认不会持久保存。这正是我们 第五篇要讲「数据卷」的原因。

常用镜像管理命令

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:20node:22node: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,构建属于你自己的镜像

相关水晶