Docker生命周期
1、Docker三阶段
1)Dockerfile
本质
Dockerfile是用于构建Docker镜像的脚本文件,其中存放了镜像构建的一系列有先后顺序的步骤(指令)。
内容
如何构建镜像的一系列步骤:
FROM:基于哪个基础镜像
RUN:安装哪些依赖
COPY:复制哪些代码
CMD:启动命令
大小
Dockerfile文本文件由于只是指令的文本描述(纯文本文件),不包含任何实际的依赖和文件内容,大小只有KB级别。可以类比为C语言的源代码文件(.cpp,大小几KB)。
生命周期
由用户手动创建,独立于Docker引擎存在,不占用容器空间,仅作为普通文本文件存在于宿主机
作用
作为docker build的输入,定义镜像的构建规则。
2)Docker build
本质
Dockerfile还要经过Build(构建),构建出一个“可运行文件系统集合”,这些文件被永久固化在镜像的只读层中,是容器运行的“基础文件集”。
Docker会按照Dockerfile中的指令(如FROM、COPY、RUN)逐步执行,每一步生成一个镜像层Layer,最终叠加生成一个完整的镜像。
内容
①包含了基础镜像的
编译产物(如RUN gcc app.c -o app生成的可执行文件)
通过COPY/ADD复制的代码(如index.html)
RUN安装的依赖(如Nginx二进制文件)等,
②记录镜像ID、标签、构建历史、默认启动命令(CMD/ENTRYPOINT)、工作目录(WORKDIR)等信息。
大小
体积一般为MB~GB级别(取决于应用类型)。可以类比cpp文件编译后产生的.obj文件(.obj,大小KB~MB级别)。
生命周期
镜像通过build构建完成后,其只读层、元数据就不会被修改(除非重新构建),可被多次用于创建容器(docker run)。镜像中的文件是“静态的”,不随容器运行而变化(容器本身运行过程中对于文件的修改,仅会被记录在容器自身的可写层,不影响镜像,因此重启后这些修改就会消失,恢复最初的状态)。
build构建完成后,除非通过docker rmi手动删除,否则生成的文件系统会持久存在于宿主机中(存储在Docker数据目录中,如/var/lib/docker/)。
特点
静态只读,无法修改。除非通过修改dockerfile再build之后重新构建,将其作为模板后可重新创建出初始状态一模一样的容器。
用途
作为docker run的输入,提供容器运行所需的基础文件、配置。
3)docker run
docker run基于镜像(即2中build后产生的文件系统集合+镜像属性(如ID、标签等))创建并启动容器,(如果说build是编译,那么run就是运行可执行程序,真正生成进程)容器运行中会产生如下文件:
①容器的可写层文件
容器启动时,Docker会在镜像只读层上添加一个可写层,容器内的文件修改(如新建文件、修改配置)都会被记录到这里(不影响镜像本身,所以容器重启后一切做的临时修改都会被抹除);
②挂载卷(Volumes)中的文件
若通过了docker run -v挂载了宿主机目录、命名卷,容器内对于挂载路径上的文件操作(如写入日志、数据库文件)会直接存储在宿主机,实现持久化修改。
③容器元数据
记录容器ID、名称、端口映射、资源限制等信息,用于Docker引擎管理容器生命周期。
生命周期
与容器绑定,容器删除、重启后,可写层的文件会被清理,但是挂载卷中的文件会保留在宿主机上。
2、镜像、容器
镜像的本质是docker build之后生成的文件系统集合+镜像属性元数据构成的一个“只读模板”;
容器的本质是docker run镜像启动后生成的可运行实例(进程)。
3、关于docker build生成的镜像
1)文件系统
docker build生成的只读文件系统层(存储在/var/lib/docker/overlay2等目录,如果是K8S会是其他的目录,如/var/lib/containerd),这些层叠加起来形成一个完整的可运行的文件系统:
a)包含了OS核心文件(如/bin/lib)、应用依赖(如Nginx二进制文件)、代码(如app.py)等运行所需的静态文件;
b)这个文件系统时docker run启动容器的“基础模板”,容器启动时会直接挂载这些只读层,并在其上添加一个可写层(用于运行时修改)。
2)镜像属性元数据
docker build除了生成文件系统外,还会生成元数据(如镜像ID、CMD/ENTRYPOINT启动命令、WORKDIR工作目录),这些数据告诉docker run如何激活文件系统。
这个元数据位于/var/lib/docker/image/overlay2/imagedb/content/sha256/目录下,是个以该镜像的SHA256值为名的文件,每次build之后都会在该目录下新生成一个文件。
docker run正是基于以上包含了完整文件系统和运行规则(元数据)的镜像,创建出可读写的容器实例——本质上是对镜像文件系统的动态使用(复用只读层+隔离可写层)。
可运行文件系统集合
上述1、2中的分层文件系统、元数据共同构成了一个可运行的文件系统集合,这就是Docker镜像的本质。
这个集合不仅包含了应用运行的所需的所有文件(如代码、依赖、二进制程序),还包含了让Docker引擎能够正确启动、管理容器的元数据,是容器运行的“基础模板”。
完整性
1中所说的分层文件系统,包含了最小化可运行环境的全部文件,在overlay2目录下的diff/文件中,包含了从基础镜像到应用代码的完整文件集:
- 基础层:OS核心文件(如/bin、/lib下的系统库);
- 中间层:通过RUN安装的依赖(如Nginx、Python解释器);
- 顶层:通过COPY/ADD复制的应用代码(如app.py、配置文件)
这些文件(层)组合起来,形成了一个类似精简操作系统的文件系统,足以支撑应用运行。
可操作性
2中所说的元数据(config.json),定义了如何“激活”这个文件系统:
- 启动命令(CMD、ENTRYPOINT):告诉Docker启动容器时需要执行哪个程序(如nginx -g )
- 工作目录(WORKDIR):定义程序运行的默认路径;
- 环境变量(ENV):提供程序运行所需的配置参数
这些元数据让静态的文件系统集合能够被docker引擎激活为动态的运行环境。
隔离性
- 多个容器共享同一个镜像的只读层;
- 每个容器对文件系统的修改仅保存在自身的可写层(该可写层在容器启动时会为每个容器分一个,互不影响),不影响镜像和其他容器。
3)镜像标签
build时通常会用-t参数给镜像打上标签,例如docker build -t app:latest .,这里就给最新构建的镜像打上了标签app:latest。
这个标签存在于文件repositories.json中,位置为:
/var/lib/docker/image/overlay2/repositories.json
打开repositories.json后,可以看到类似以下的结构
{ "Repositories": { "app": { // 镜像仓库名(如 "app") "latest": "sha256:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b" // 标签 "latest" 对应的镜像 ID }, "ubuntu": { // 基础镜像仓库 "22.04": "sha256:f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5" } } }
该文件在Docker中,全局唯一,且每次build都会修改其值(如果是新标签,就在其中新增一个值),因此就算你习惯不好,连续build了很多次,打的标签都是app:latest;Docker也会正确识别出该镜像是哪个,因为就算标签一样,但是repositories.json中这个标签对应的镜像的SHA-256的值都是不一样的(除非你没改dockerfile,重复build,此时镜像本身没变化,它的SHA-256值也不会变化)。
repositories.json
该文件的核心作用是记录当前Docker环境中所有镜像仓库的标签(tag)和镜像ID的映射关系(如nginx:latest、my-app:v1.0等),它有如下特性:
- 对于单Docker守护进程管理的环境,该文件是全局唯一的;
- 无论有多少个镜像、多少个仓库(如nginx、ubuntu、自定义应用仓库),所有标签的映射关系都会集中存储在该文件中(而非按照仓库拆分为多个);
- 每次执行docker tag、docker rmi、docker build -t等操作,Docker都会直接修改这一个文件,确保所有标签映射的一致性、唯一性。
4)关于路径
在docker学习的过程中,经常会看到如下路径(或类似路径):
/var/lib/docker/image/overlay2/repositories.json
解析:
- /var/lib/docker:Docker默认的数据根目录(可通过docker info | grep "Docker Root Dir"确认实际路径)
- image/overlay2:overlay2存储驱动的元数据目录(若使用其他驱动,如devicemapper,则路径会变为image/devicemapper
WORKDIR
容器内部的工作目录
WORKDIR指令可以用来设置容器内部的默认工作目录,不涉及宿主机的文件系统。
若Dockerfile中包含了WORKDIR /u01/app/rules,那么相当于把后续容器中的工作目录都置为了/u01/app/rules,如果用了指令ls,则会列出/u01/app/rules中的文件信息。
实际位置
容器内的/u01/app/rules目录,物理上存储在宿主机的Docker数据目录的对应层中:
/var/lib/docker/overlay2/<层ID>/diff/u01/app/rules。
不过这是Docker联合文件系统的内部映射,对于容器内的进程而言,它看到的就是逻辑上的/u01/app/rules路径,完全感知不到宿主机内的/var/lib/docker。
4、文件系统集合中的layer
之前一直提到层这个概念,即layer。Dockerfile中每多一行可生成层的指令(如RUN、COPY、ADD等),就会生成一个新层,这些层在真实的宿主机中,确实是一个个独立的目录,且通过每层的元数据info.json,找出该层的父层(即上一条指令生成的层)。层与层的核心就体现在存储的文件内容和层间关联关系上,具体如下:
1)层在真实文件系统中的物理形态
在主流的overlay2存储驱动下,每层都对应宿主机数据目录中的一个独立子目录。
以containerd为例,路径为/var/lib/containerd/overlayfs/snapshots/,之后dockerfile建立而成的一层层,就在这个snapshots目录下,作为它的子目录:
/snapshots/ ├── 1/ # 层1(基础层,如 FROM 指令对应的基础镜像层) │ ├── diff/ # 该层新增/修改的文件(真实目录结构) │ │ ├── bin/ │ │ ├── etc/ │ │ └── ... │ └── info.json # 层元数据(记录父层ID、创建时间等) ├── 2/ # 层2(基于层1的新层,如 RUN 指令生成) │ ├── diff/ # 该层新增的文件(如安装的依赖) │ │ ├── usr/ │ │ └── ... │ └── info.json # 元数据中记录父层为1 └── 3/ # 层3(基于层2的新层,如 COPY 指令生成) ├── diff/ # 该层新增的应用代码 │ ├── app/ │ └── ... └── info.json # 元数据中记录父层为2
从上表可以看出,不同指令构成的上下层之间,并不是通过嵌套子目录的方式形成结构的,而是作为同一级目录中的子目录。目录间的关系,通过各层的info.json来关联,这个元数据中记录了该层的父层(本质上是生成该层的指令的上一条指令所形成的层)。
虽然物理上是并列目录,但是Docker/containerd会通过联合文件系统(如overlay2),根据info.json中记录的依赖关系,将这些并列的层逻辑上堆叠起来,形成一个统一的文件系统视图:
- 先生成的层的内容会被后生成的层继承,如有同名文件,则后生成的层会覆盖掉上一层;
- 容器中看到的 /(即根目录),就是这些并列层被合并后的结果,在容器使用过程中完全感知不到物理上的并列存储。
注意:以上1、2、3只是举例,实际中名称本身并不代表顺序,仅做唯一标识。
2)层与层之间的核心区别
①diff/目录的不同
注意:diff/和diff(是否带斜杠)本质上是同一个目录,斜杠仅用于明确表示“这是一个目录路径”,而不改变目录本身的身份和内容。
每层的diff/目录仅包含该层新增、修改的文件/目录,这是层与层之间最直观的区别:
例如:
RUN apt install nginx:该层的diff/会包含usr/sbin/nginx、etc/nginx/等nginx相关文件;
COPY app.py /app/:该层的diff/仅包含app/app.py文件,不会包含前一层的nginx文件。
这种“只记录差异”的特性,使层与层之间的内容天然隔离,避免重复存储。
②元数据文件info.json记录的依赖关系不同
每层都有一个元数据文件info.json,其中会记录父层ID,形成清晰的依赖链:
- 基础层(FROM层)没有父层;
- 后续每层都以上一层为父层,例如层2的父层为1,层3的父层为2(如果多次build,且没有清除之前的层,可能出现层4的父层为2这种情况)。
注:层1的parent为空。
③对最终文件系统的贡献不同
若多层通过联合挂载合并,则后生成的层会覆盖前一层的同名文件:
- 若层2的diff/etc/nginx.conf与层1的diff/etc/nginx.conf同名,则合并后显示层2的文件;
- 若层3删除了层2的某个文件(通过RUN rm),则层3的diff/目录中会生成一个“删除标记”,合并后该文件会被隐藏。
3)总结
层是独立目录 + 差异内容 + 依赖关系的组合:
独立目录:每多一层就会在diff下多一个子目录,代表该层,不同层在物理结构上平级,不存在嵌套关系。
差异内容:diff目录中仅记录该层的差异文件和元数据文件
依赖关系:层通过info.json中记录的父层ID形成依赖链,最终被合并为一个完整的文件系统。
4)疑问
①我首次build后,形成了1、2、3三层,如果我对第三层进行了修改,是会生成一个3.1层吗?
修改了第三层对应的dockerfile指令,如将COPY app.pu /app/改为COPY app_v2.py /app/,重新build后,会:
- Docker发现层3指令被修改,因此不会复用之前的层3;
- 基于层2新建一个层4(层4的父层为层2),层4的diff目录包含了修改后的文件app_v2.py;
- 新镜像的依赖链变为:1→2→4
对于原来的层3:
- 层3仍会保留(除非手动清理),仍以独立目录形式存在于宿主机的存储路径中;
- 但是新镜像不会和层3再产生关联了。
这涉及到我们之前说的:Docker镜像的层一旦创建就是只读的,任何修改都必须通过创建新层来实现。
②虽然可以通过每一层的info.json来上溯它的父层,但是Docker是如何从全局层面知道哪个是最终层呢?如果不知道又该怎么知道如何从1正向构建到最后一层呢?
先看之前说过的——每个层建立完毕后,都会在该层的元数据文件中(如info.json或类似结构),通过parent(父层ID)字段记录该层是基于哪个层构建的:
- 层1的parent为空;
- 层2的parent为1;
- 层3的parent为2;
- 当层3被修改并生成层4后,层4的parent仍为2。
当镜像构建完成后,会生成一份镜像配置文件config.json,其中就有一个关键配置rootfs.layers,它记录了该镜像的所有层的哈希号,使Docker可以从头到尾找到正确的镜像。
因此Docker并非依靠之前说的info.json来找到全部层的。info.json是层级别的元数据文件,而config.json是镜像的配置文件,它俩的区别在于:
-
位置
-
- info.json位于/var/lib/docker/overlay2/<层ID>目录下
- config.json为/var/lib/docker/image/overlay2/imagedb/content/sha256/<镜像ID>下(标红的位置即为二者路径分道扬镳之处)。
-
内容
- info.json记录的是该层的信息:id、parent、created(创建时间)、overlay(层类型);
- config.json记录镜像的全局配置:
- rootfs.layers:数组类型,按顺序列出该镜像所有层的SHA-256哈希(就是这个配置项,使得Docker可以从头到尾找到关联的所有层)
- 容器的启动配置(config,如CMD、WORKDIR、ENV);
- 镜像创建时间(created)
- parent:该镜像的基础镜像ID。
-
例子
假设一个镜像由层1、2、3构成,那么在各层info.json中,就会出现如下写法:
-
- 层1的info.json,会有parent:""(空,代表无父层)
- 层2的info.json,会有parent:"层1的ID;
- 层3的info.json,会有parent:"层2的ID"
而在镜像的config.json中,则会有:
"rootfs":{ "type":"layers", "layers":[ "层1的sha256值", "层2的sha256值", "层3的sha256值" ] }
查找与验证
rootfs.layers定义镜像的层顺序,Docker只需按照该数组顺序读取层,并根据层的info.json中的parent验证层之间的依赖关系,共同保证镜像文件系统的正确构建。
实际过程为:
- Docker读取config.json中的rootfs.layers数组,得到层ID的有序列表;
- 逐个读取每层的info.json,并验证parent字段是否与前一层的ID匹配:
- 层2的parent必须为1的ID;
- 层3的parent必须为2的ID。
- 若所有层的parent都验证通过,则按照rootfs.layers的顺序堆叠这些层,形成完整的文件系统。
③build重新构建镜像,会对镜像所在目录、镜像各层所在的目录有啥影响?
首先明确一个前提:
镜像和镜像的各层是保存在不同的目录下的:
- info.json位于/var/lib/docker/overlay2/<层ID>目录下
- config.json为/var/lib/docker/image/overlay2/imagedb/content/sha256/<镜像ID>下(标红的位置即为二者路径分道扬镳之处)。
这俩文件分别代表层元数据、镜像元数据所在的目录。
每次build,都会:
镜像
- 在/var/lib/docker/image/overlay2/imagedb/content/sha256/目录下生成一个新的文件(文件、非目录),文件名是新镜像完整的SHA-256哈希值(即镜像ID),该文件包含了新镜像的config.json配置信息(包含了rootfs.layers等核心元数据)。
- 如果未修改Dockerfile,重复build,由于前后两次镜像的SHA-256哈希值没发生变化,因此Docker会复用现有层和镜像配置,不会重复生成新文件。
- 如果修改了Dockerfile,即使构建时的标签相同,

浙公网安备 33010602011771号