从本章起正式动手 Docker。镜像是容器的「模板」,理解镜像的关键是分层:它不是一整块只读文件,而是一层层的增量叠加。这个设计直接决定了拉取速度、磁盘占用、构建缓存的体验,是整门 Docker 课最重要的一个概念。本章先看 Docker 的架构(那个 docker 命令背后是谁),再拆分层机制,最后过一遍镜像的日常命令。
敲 docker run 的时候,发生的事比看上去多:
docker CLI(你敲的命令)
│ REST API(默认走 /var/run/docker.sock)
dockerd(Docker 守护进程)
│
containerd(容器生命周期管理)
│
runc(真正调 namespace/cgroup 创建容器,第 7 章)
docker 只是客户端,真正干活的是 dockerd 守护进程(C/S 架构,REST 通信)。所以「Docker 挂了」通常指 dockerd 挂了(所有容器进程会跟着停,数据卷里的文件不丢);再往下 containerd 管容器生命周期,runc 负责最底层的创建动作(设置 namespace/cgroup 后 exec 你的程序)。这套分层后来被标准化成 OCI 规范,containerd 甚至被 Kubernetes 直接采用(绕过 Docker),知道这个背景,以后看到「Kubernetes 弃用 dockershim」的新闻就不会慌。
一个镜像由多个只读层叠加而成,每层记录一串文件系统的增量:
可写层(容器运行时的改动,薄)
─────────────────────
层3 COPY app.jar /app ← 你的代码,几 MB
层2 RUN apt install openjdk ← 依赖,几百 MB
层1 FROM ubuntu:24.04 ← 基础系统,几十 MB
底层机制是 Linux 的 overlayfs(unionfs 家族):多层目录联合挂载,上层文件遮盖下层同名文件。三个由此而来的重要性质:
共享:两台机器都拉 ubuntu:24.04 + openjdk 的镜像,基础层只下一次;你的 100 个 Java 服务镜像,基础层在宿主机上只占一份,密度远超 VM(每台 VM 一个完整内核和根文件系统)。增量传输:pull 时本地已有的层不下载,push 同理;层是内容寻址的(层的校验和就是它的 ID),内容不变层就不变。容器 = 镜像 + 一个可写层:run 的时候在最上面加一个薄薄的读写层,所有容器里的写操作都落在这层(COW,和第 6 章 qcow2 的 backing file 思想同源),镜像本体永远只读,所以一个镜像可以同时跑 100 个容器互不干扰。
容器里改文件不会影响镜像(下次 run 又是干净的),想留住改动要么写进镜像(Dockerfile,第 12 章),要么挂数据卷(第 11 章)。第 9 章「容器删了数据没了」的事故根源就在这层模型上。
docker pull nginx:1.27 # 拉取(名字:标签,无标签默认 latest)
docker images # 本地镜像清单(看 REPOSITORY/TAG/SIZE)
docker search redis # 在 Docker Hub 搜索
docker tag nginx:1.27 myreg/nginx:1.27 # 重打标签(改名/改仓库前缀)
docker rmi myreg/nginx:1.27 # 删除镜像
docker push myreg/nginx:1.27 # 推送到仓库
docker history nginx:1.27 # 看分层明细!(每层多大、哪来的)
docker system df # Docker 占了多少磁盘
docker system prune # 大扫除(删悬空镜像、停掉的容器)
docker history 值得单独点名:它把一个镜像拆回每一层的操作和体积,是「诊断镜像为什么这么大」的第一工具(第 12 章优化 Dockerfile 时天天用)。docker images 里那串 <none>:<none> 的悬空镜像(被新版本顶替的旧层),是磁盘悄悄变满的惯犯,prune 定期清。
镜像名的完整格式是 registry地址/命名空间/名字:标签,省略 registry 就是 Docker Hub。国内拉不动 Hub 的时候,配镜像加速器(registry mirror)或者换成 ghcr.io / 阿里云 ACR 等源,这是国内环境的第一课。
docker rmi 报 image is being used。有容器在用(哪怕停着);先删容器(rm)或强制,想清楚再 -f。docker history 分别看分层;再用两个不同 tag 拉同一镜像不同版本,用 docker system df 观察共享层的占用。docker run -it alpine sh 里 touch /marked.txt,exit 后再 run 一个新 alpine,ls 验证文件没了;docker ps -a 找到旧容器,docker start + exec 进去,文件还在(可写层跟着容器走)。docker save -o nginx.tar nginx:1.27 导出镜像,ls -lh 看大小,换台机器(或删掉后)docker load -i nginx.tar 导入,体验离线交付镜像。