【Docker 从入门到实战】第 3 章:容器镜像深度解析------分层结构、Copy-on-Write、Dockerfile 构建与 Registry 分发一文搞定
摘要:镜像是 Docker 容器的基石,容器是镜像的运行实例。本章先解剖 hello-world 这个"最小的镜像",讲清 base 镜像与 rootfs 的关系;然后深入镜像的分层结构与 Copy-on-Write 特性;接着掌握构建镜像的两种方法(docker commit 与 Dockerfile);最后打通镜像分发全链路:Docker Hub、华为云 SWR、本地 Registry、企业级 Harbor。学完这一章,你将彻底理解 Docker 为什么轻量、镜像为什么分层、如何构建并分发自己的镜像。
一、先从最小的镜像说起:hello-world
hello-world 是 Docker 官方用来验证安装是否成功的镜像,它到底有多小?
bash
docker pull hello-world
docker images
# hello-world latest d2c94e258dcb 16 months ago 13.3kB
不到 14KB! 一个完整可运行的镜像,居然只有 13.3kB。它的 Dockerfile 只有三行:
dockerfile
FROM scratch # 从 0 开始,白手起家
COPY hello / # 把可执行文件 hello 复制到镜像根目录
CMD ["/hello"] # 容器启动时执行 /hello
整个镜像里只有一个可执行文件 /hello,连最基本的 /bin、/usr、/lib、/dev 都没有。它的全部功能就是打印那句 "Hello from Docker!"。
启示:hello-world 虽然是"完整"的镜像,但没有实际用途。我们真正常用的是能提供基本操作系统环境的 base 镜像。
二、base 镜像与 rootfs:为什么 CentOS 只有 204MB?
2.1 Linux 操作系统 = 内核空间 + 用户空间
- kernel:Linux 系统内核(内核空间);
- rootfs :用户空间的文件系统,包含我们熟悉的
/dev、/proc、/bin等目录。
2.2 base 镜像的两层含义
- 不依赖其他镜像,从 scratch 构建(scratch 本身是一个空镜像);
- 其他镜像可以之为基础进行扩展。
所以能称作 base 镜像的,通常都是各种 Linux 发行版的镜像:Ubuntu、Debian、CentOS 等。
2.3 灵魂拷问:CentOS 怎么才 204MB?
我们平时安装一个 CentOS 至少好几个 GB,而 centos:7 镜像只有 204MB:
bash
docker images centos:7
# centos 7 eeb6ee3f44bd 3 years ago 204MB
原因很简单:
- base 镜像直接复用 Host 的 kernel,自己只需要提供 rootfs;
- 精简后的 rootfs 只包含最基本的命令、工具和程序库(alpine 甚至不到 10MB);
- 我们平时安装的 CentOS 还选装了软件、服务、图形桌面等,所以好几个 GB 不足为奇。
base 镜像提供的是最小安装 的 Linux 发行版。CentOS 的 Dockerfile 中那句 ADD 指令添加的 tar 包,就是 CentOS 7 的 rootfs,制作镜像时自动解压到 / 目录下。
2.4 关键知识点:容器用谁的 kernel?
不同 Linux 发行版的区别主要就是 rootfs(Ubuntu 用 upstart + apt,CentOS 用 systemd + yum),Linux kernel 差别不大。所以 Docker 能同时模拟多种 Linux 环境。
但要注意两点:
- base 镜像只在用户空间与发行版一致,kernel 版本与发行版不同。例如在 CentOS Stream 8(kernel 4.18.0)的宿主机上跑 ubuntu、centos:7 容器:
bash
[root@docker ~]# uname -r
4.18.0-553.6.1.el8.x86_64 # 宿主 kernel 4.18.0
docker run -it ubuntu
root@4264749aa4af:/# uname -r
4.18.0-553.6.1.el8.x86_64 # 容器用的也是宿主 kernel
- 容器只能使用 Host 的 kernel,且不能修改。所有容器共用宿主内核,在容器里无法升级 kernel。如果应用对内核版本有硬性要求,就不适合用容器,虚拟机更合适。
三、镜像的分层结构与 Copy-on-Write
3.1 分层结构:每步操作生成一层
Docker Hub 上 99% 的镜像都是通过在 base 镜像上安装、配置软件构建出来的。Docker 镜像中引入层(layer)的概念,制作过程中的每一步操作都会生成一个新的镜像层。
容器由若干只读镜像层 + 最上面一个可写的容器层构成:
┌─────────────────────────────┐
│ 可写容器层(容器运行时可改) │ ← 容器运行时加载
├─────────────────────────────┤
│ 镜像层 3(如 RUN 安装 vim) │ ┐
├─────────────────────────────┤ │ 只读镜像层
│ 镜像层 2(如 COPY 文件) │ │ (可被多个容器共享)
├─────────────────────────────┤ │
│ base 镜像层(如 ubuntu) │ ┘
└─────────────────────────────┘
用 docker history 可以看到每一层的构建历史,docker image inspect ubuntu 可以查看分层详情。
3.2 为什么要分层?------ 共享资源
多个镜像从相同的 base 镜像构建而来时,Docker Host 磁盘上只需保存一份 base 镜像,内存中也只需加载一份,就可以为所有容器服务。
3.3 UnionFS:联合文件系统
UnionFS 的核心功能是把多个不同位置的目录联合挂载(union mount)到同一个目录下。使用镜像时,Docker 把所有的镜像层联合挂载到一个统一挂载点,对外表现为一个完整的 Linux 操作系统。
3.4 Copy-on-Write:容器层只记"变化"
当容器启动时,一个新的可写层被加载到镜像顶部,叫容器层 ;它下面的所有镜像层都是只读的。对容器的所有增删改查,都只发生在容器层:
| 操作 | 具体执行 |
|---|---|
| 创建文件 | 新文件只能被添加在容器层中 |
| 删除文件 | 依据分层结构由上往下查找,找到后在容器层中记录删除操作------UnionFS 会在容器层创建一个 whiteout 文件,把被删文件"遮挡"起来 |
| 修改文件 | 由上往下查找,找到后把镜像层中的数据复制到容器层进行修改,修改后的数据保存在容器层(这就是 copy-on-write) |
| 读取文件 | 依据分层结构由上往下依次查找 |
重点理解**:只有当需要修改时才复制一份数据,这就是 Copy-on-Write。容器层保存的是镜像变化的部分,不会对镜像本身做任何修改------所以多个容器共享同一个镜像,各改各的互不影响。
四、构建镜像的两种方法
4.1 方法一:docker commit(不推荐)
三步走:运行容器 → 修改容器 → 保存为镜像。以在 ubuntu 中安装 vim 为例:
bash
# ① 运行容器(-it 交互模式进入终端)
docker run -it ubuntu
# ② 在容器中安装 vim
apt-get update
apt-get install -y vim
# ③ 另开一个终端,先查询容器名,然后把容器保存为镜像
docker ps # 查询容器名
docker commit cool_darwin ubuntu-with-vim # cool_darwin 是容器名
# 验证
docker images # ubuntu-with-vim 189MB(ubuntu 原镜像 78.1MB,变大了)
docker run -it ubuntu-with-vim
which vim # /usr/bin/vim ✓
为什么 Docker 不推荐 commit?
- 手工方式,容易出错、效率低、可重复性弱;
- 更重要的:使用者不知道镜像是怎么创建出来的,里面是否有恶意程序,无法审计,存在安全隐患。
但必须学它:即便用 Dockerfile 构建,底层也是通过一层层 commit 完成的。理解 commit 有助于深入理解构建过程和分层结构。
4.2 方法二:Dockerfile(推荐)
Dockerfile 是一个文本文件,记录了镜像构建的所有步骤。
基础语法规则:
- 每条保留字指令必须大写,且后跟至少一个参数;
- 指令从上到下顺序执行;
#表示注释;- 每条指令都会创建一个新的镜像层并提交。
bash
# 命令格式
docker build -f [Dockerfile路径] [构建上下文路径]
# 示例:-t 指定镜像名,. 表示 build context 为当前目录
docker build -t ubuntu-with-vim-dockerfile .
build context(构建上下文) :Docker 把该目录下的所有文件发送给 daemon,供 Dockerfile 中的 ADD、COPY 使用。注意不要把 /、/usr 作为 build context,否则构建会非常缓慢甚至失败。
第一个 Dockerfile(等价于上一节的 commit 效果):
dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y vim
bash
docker build -t ubuntu-with-vim-dockerfile .
构建时注意 RUN 的执行过程:Docker 会启动一个临时容器执行操作,并通过 commit 保存为新的镜像层 (输出中的 Running in xxx 就是临时容器)。用 docker history 可以看到新镜像比 ubuntu 多了一层(apt-get 创建的 111MB 层)。
4.3 Dockerfile 常用指令速查
| 指令 | 作用 | 格式 |
|---|---|---|
| FROM | 指定 base 镜像(第一条必须是 FROM) | FROM <image>:<tag> |
| MAINTAINER | 注明镜像作者 | MAINTAINER <name> |
| RUN | 构建时运行命令(安装软件包) | RUN <command> |
| COPY | 从 build context 复制文件到镜像 | COPY <src> <dest> |
| ADD | 同 COPY,但 tar/zip 等归档文件会自动解压 | ADD <src> <dest> |
| ENV | 设置环境变量(可被后续指令使用) | ENV <key> <value> |
| EXPOSE | 声明容器中应用监听的端口 | EXPOSE <port> |
| VOLUME | 将目录声明为 volume | VOLUME ["/data"] |
| WORKDIR | 设置后续指令的工作目录(不存在会自动创建) | WORKDIR /path |
| CMD | 容器启动时执行的默认命令(可被 docker run 后参数替换,多个时最后一个生效) | CMD command param |
| ENTRYPOINT | 容器启动时执行的主命令(一定执行,CMD 或 run 参数作为其参数) | ENTRYPOINT command param |
一个综合示例:
dockerfile
# my dockerfile
FROM busybox # 从 busybox 开始构建
MAINTAINER 6946630@qq.com # 声明作者信息
WORKDIR /testdir # 设置工作目录为 /testdir
RUN touch tmpfile1 # 创建 tmpfile1
COPY ["tmpfile2", "."] # 复制 tmpfile2 到镜像
ADD ["passwd.tar.gz", "."] # 复制并自动解压
ENV WELCOME "You are in my container,welcome!"
4.4 RUN / CMD / ENTRYPOINT 终极辨析
三句话记牢:
- RUN:构建时执行命令,创建新的镜像层,常用于装软件;
- CMD :容器启动后的默认命令,可被
docker run后面的参数替换; - ENTRYPOINT :容器启动时执行的主命令,一定会执行 ,
docker run的参数会作为参数传给它。
| 指令 | 执行时机 | 核心作用 | 关键特性 | Shell/Exec 格式注意点 |
|---|---|---|---|---|
| RUN | docker build 镜像构建阶段 |
执行命令,安装依赖、配置系统、生成新镜像层 | 每一条 RUN 都会新增一层镜像;容器运行时不会再执行 RUN多条 RUN 尽量合并,减少镜像层数 | Shell:底层/bin/sh -c,解析环境变量Exec:直接调用程序,不走 shell,不自动解析$变量 |
| CMD | docker run 容器启动阶段 |
定义容器默认启动命令 | 可被docker run 镜像名 后面跟的指令直接完全替换多个 CMD 只生效最后一条 |
CMD 格式会作为 ENTRYPOINT 的参数;Exec 推荐使用 |
| ENTRYPOINT | docker run 容器启动阶段 |
定义容器固定入口主程序 | 不会被docker run后的命令覆盖;run 后的内容当作参数传给 ENTRYPOINT多个 ENTRYPOINT 只生效最后一条 |
一般推荐 Exec 格式,方便搭配 CMD 提供默认参数 |
两种写法格式(重要):
| 格式 | 写法 | 特点 |
|---|---|---|
| Shell 格式 | RUN apt-get install python3 |
底层调用 /bin/sh -c,会解析环境变量 |
| Exec 格式 | RUN ["apt-get", "install", "python3"] |
直接执行,不经过 shell,不会解析环境变量 (如 $name 不会被替换) |
bash
# Shell 格式:环境变量会被替换,输出 Hello,gqd
FROM busybox
ENV name gqd
ENTRYPOINT echo "Hello, $name"
# Exec 格式:环境变量不替换,输出 Hello, $name
FROM busybox
ENV name gqd
ENTRYPOINT ["/bin/echo", "Hello, $name"]
# 想用环境变量就用 sh -c 包一层
ENTRYPOINT ["/bin/sh", "-c", "echo Hello, $name"]
4.41 RUN
只在 build 构建镜像的时候执行,写在 Dockerfile 里,构建完就固化进镜像。
bash
# Shell格式
RUN apt-get update && apt install -y nginx
# Exec格式
RUN ["apt-get","install","-y","nginx"]
4.42 CMD
CMD 的 3 种写法
- Shell 写法:
CMD python app.py - Exec 写法(推荐):
CMD ["python","app.py"] - 搭配 ENTRYPOINT 写法:CMD 只提供默认参数
bash
ENTRYPOINT ["python","app.py"]
CMD ["--port","80"]
4.43 ENTRYPOINT
- ENTRYPOINT 的 Exec 格式用于设置要执行的命令及其参数,同时可通过 CMD 提供额外的参数。
- ENTRYPOINT 中的参数始终会被使用,而 CMD 的额外参数可以在容器启动时动态替换掉。
- ENTRYPOINT 的 Shell 格式会忽略任何 CMD 或 docker run 提供的参数。
bash
# Exec 格式用于设置要执行的命令及其参数,
[root@docker ~]# vim Dockerfile
FROM busybox
ENTRYPOINT ["/bin/echo", "Hello"]
CMD ["world"]
[root@docker ~]# docker build -t dockerfile6 .
[root@docker ~]# docker run -it dockerfile6
Hello world
[root@docker ~]# docker run -it dockerfile6 gqd
Hello gqd
# shell 格式
[root@docker ~]# vim Dockerfile
FROM busybox
ENTRYPOINT echo "Hello"
CMD ["world"]
[root@docker ~]# docker build -t dockerfile7 .
[root@docker ~]# docker run -it dockerfile7
Hello
[root@docker ~]# docker run -it dockerfile7 gqd
Hello
最佳实践 :应用/服务类镜像优先用 Exec 格式的 ENTRYPOINT 固定入口,用 CMD 提供可替换的默认参数 ;RUN 用来安装软件包(apt-get update 和 apt-get install 要放在同一个 RUN 中,避免用到过期的缓存层)。
五、镜像缓存特性与调试
5.1 缓存特性
Docker 会缓存已有镜像的镜像层 :构建或下载时,如果某层已存在,直接使用(Using cache),无需重新创建。
bash
# 构建时加上 --no-cache 可强制不用缓存
docker build --no-cache -t ubuntu-with-vim-dockerfile .
关键规则:每一层都依赖下层,只要某一层发生变化,它上面所有层的缓存都会失效。 因此:
- 修改或添加指令 → 后续层缓存失效;
- 交换指令顺序(即使逻辑上镜像内容不变)→ Docker 也必须重建受影响的层;
docker pull下载镜像时也会复用已有层(输出中显示Already exists的层就是复用的)。
实践启示 :把变动频繁的指令(如 COPY 代码)放在 Dockerfile 后面 ,把变动少的(如 RUN 装依赖)放在前面,可以最大化利用缓存、加快构建。
5.2 调试 Dockerfile
Dockerfile 执行失败时,前一条指令成功构建的镜像还在,可以基于它调试。经典案例:
dockerfile
FROM busybox
RUN touch tmpfile
RUN /bin/bash -c "echo continue to build..." # ← 失败:busybox 里没有 bash!
COPY testfile /
bash
# 构建报错后,用失败指令之前的镜像层启动容器,手工执行定位
docker run -it busybox
# 发现 busybox 用的是 /bin/sh,没有 /bin/bash
# 修改为 /bin/sh 后重新构建即成功
六、镜像命名与 tag 最佳实践
6.1 镜像名称格式
bash
[image name] = [repository]:[tag]
完整格式:[镜像仓库地址]/[Namespace]/[Repository]:[tag]
-
Registry(仓库服务器地址):054b8ac70e8010d90f2ac00ef29e6580.mirror.swr.myhuaweicloud.com 去哪里拉取镜像(华为云 SWR 镜像仓库域名)
-
Namespace(命名空间):library 仓库内分组,隔离不同项目 / 用户,官方镜像默认 library
-
Repository(repo,镜像仓库名):nginx 同一软件的镜像集合,同 repo 用 tag 区分版本
-
Tag(标签):latest 版本标记,latest 只是标签名,不自动永远最新
bash
054b8ac70e8010d90f2ac00ef29e6580.mirror.swr.myhuaweicloud.com/library/nginx:latest
└─────────────── 仓库地址 ───────────────┘└namespace┘└repo┘└tag┘
docker build未指定 tag 时,默认使用 latest;docker tag可以给镜像打新标签:docker tag httpd httpd:v8.6。
6.2 小心 latest 陷阱
latest 并没有特殊含义 ,只是"未指定 tag 时的默认值"。Docker Hub 上很多仓库把 latest 当作最新稳定版的别名,但这只是约定,不是强制规定 。生产环境建议明确指定 tag(如 httpd:2.3、ubuntu:xenial),不要依赖 latest。
6.3 tag 版本方案(社区通用做法)
发布 v1.9.1 时打四个 tag:1.9.1、1.9、1、latest;发布 v1.9.2 后把 1.9、1、latest 挪过来;发布 v2.0.0 后新增 2.0.0、2.0、2 并把 latest 指向它:
bash
docker tag myimage-v1.9.1 myimage:1
docker tag myimage-v1.9.1 myimage:1.9
docker tag myimage-v1.9.1 myimage:1.9.1
docker tag myimage-v1.9.1 myimage:latest
这样 myimage:1 始终指向 1 分支最新、myimage:1.9 指向 1.9.x 最新、myimage:latest 指向所有版本最新,具体版本随意选用。
七、镜像分发:Registry 实战
7.1 Docker Hub(公共 Registry)
bash
# 1. 注册账号(hub.docker.com)
# 2. 登录
docker login
# 3. 镜像名必须包含用户名(官方镜像除外)
docker tag httpd:latest gaoqd/httpd:v1
# 4. 上传(只会上传本地新增的层,base 层已存在则跳过)
docker push gaoqd/httpd:v1
# 5. 其他 Host 即可下载使用
docker pull gaoqd/httpd:v1
7.2 华为云 SWR(国内公共/私有 Registry)
bash
docker login -u <用户名> -p <密码> swr.cn-east-3.myhuaweicloud.com
# 镜像名格式:仓库地址/组织名/镜像名:版本
docker tag httpd:centos swr.cn-east-3.myhuaweicloud.com/gaoqiaodong/centos_httpd:v1
docker push swr.cn-east-3.myhuaweicloud.com/gaoqiaodong/centos_httpd:v1
# 删除本地镜像后再 pull 验证
docker pull swr.cn-east-3.myhuaweicloud.com/gaoqiaodong/centos_httpd:v1
7.3 搭建本地 Registry(一行命令)
bash
# 启动 registry 容器(5000 是服务端口;-v 把镜像数据存到宿主机 /myregistry)
docker run -d -p 5000:5000 -v /myregistry:/var/lib/registry registry:2
# 重命名镜像使其符合格式 [registry-host]:[port]/[username]/xxx
docker tag httpd:latest localhost:5000/httpd:v1
# 上传
docker push localhost:5000/httpd:v1
# 查看仓库里有哪些镜像
curl http://localhost:5000/v2/_catalog
# {"repositories":["httpd"]}
# 删除本地镜像后从自建仓库拉取
docker pull localhost:5000/httpd:v1
注意:用内网地址(如 192.168.137.99:1000)作私有仓库时,需要在 /etc/docker/daemon.json 配置 insecure-registries 并重启 Docker:
bash
{
"insecure-registries": ["192.168.137.99:1000"]
}
7.4 企业级私有仓库 Harbor
Harbor 是 Docker Registry 的企业级封装:提供 Web UI、角色与用户权限管理、操作审计,还整合了 K8s 插件仓库。核心流程:
bash
# 下载解压离线安装包
wget https://github.com/goharbor/harbor/releases/download/v2.9.1/harbor-offline-installer-v2.9.1.tgz
tar -xvf harbor-offline-installer-v2.9.1.tgz
# 部署到 /opt/harbor,导入镜像、修改配置(hostname/密码/注释 https)
mkdir /opt/harbor && mv harbor/* /opt/harbor/
cd /opt/harbor
docker load -i harbor.v2.9.1.tar.gz
cp -ar harbor.yml.tmpl harbor.yml && vim harbor.yml
# 安装(先 prepare 再 install,全部容器 healthy 即成功)
./prepare
./install.sh
# 使用:创建用户/项目 → 配置 insecure-registries → login → push/pull
docker login 192.168.108.30
docker tag nginx:latest 192.168.108.30/cloud/nginx:latest
docker push 192.168.108.30/cloud/nginx:latest
docker pull 192.168.108.30/cloud/nginx
# 卸载
docker compose down && rm -rf /data && rm -rf /opt/harbor
Harbor 常用功能:项目 (每个项目独立镜像地址)、用户管理 (不同项目不同维护人)、项目定额(限制仓库最大空间)。
八、综合案例:Dockerfile 实战三连
案例 1:制作带 SSH 的 CentOS 镜像(官方镜像默认不带 ssh)
dockerfile
# centos.ssh.dockerfile
FROM centos:8.4.2105
MAINTAINER gaoqiaodong
# 替换 yum 源为阿里云 vault 源
RUN minorver=8.4.2105 && \
sed -e "s|^mirrorlist=|#mirrorlist=|g" \
-e "s|^#baseurl=http://mirror.centos.org/\$contentdir/\$releasever|baseurl=https://mirrors.aliyun.com/centos-vault/$minorver|g" \
-i.bak /etc/yum.repos.d/CentOS-*.repo
RUN yum install -y openssh-server
RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
RUN ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key
RUN echo "root:huawei" | chpasswd
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
bash
docker build -t centos:ssh -f centos.ssh.dockerfile .
docker run -d -p 2022:22 --name sshtest centos:ssh
ssh root@localhost -p 2022 # 登录成功
案例 2:自定义 httpd 镜像
dockerfile
FROM centos:8.4.2105
MAINTAINER gaoqiaodong
RUN minorver=8.4.2105 && sed -e "s|^mirrorlist=|#mirrorlist=|g" \
-e "s|^#baseurl=http://mirror.centos.org/\$contentdir/\$releasever|baseurl=https://mirrors.aliyun.com/centos-vault/$minorver|g" \
-i.bak /etc/yum.repos.d/CentOS-*.repo
RUN yum install -y httpd && yum clean all && rm -rf /var/cache/yum
COPY index.html /var/www/html/
EXPOSE 80
CMD ["/usr/sbin/httpd", "-DFOREGROUND"]
案例 3:定制带 vim + ifconfig + JDK8 的 CentOS 镜像
dockerfile
FROM centos:8.4.2105
MAINTAINER gaoqiaodong<6946630@qq.com>
ENV MYPATH /usr/local
WORKDIR $MYPATH
# 换 yum 源(同上)
RUN minorver=8.4.2105 && sed -e "s|^mirrorlist=|#mirrorlist=|g" \
-e "s|^#baseurl=http://mirror.centos.org/\$contentdir/\$releasever|baseurl=https://mirrors.aliyun.com/centos-vault/$minorver|g" \
-i.bak /etc/yum.repos.d/CentOS-*.repo
RUN yum -y install vim
RUN yum -y install net-tools
RUN yum -y install glibc.i686
RUN mkdir /usr/local/java
# 安装包 jdk-8u461-linux-x64.tar.gz 必须与 Dockerfile 在同一目录
ADD jdk-8u461-linux-x64.tar.gz /usr/local/java/
ENV JAVA_HOME /usr/local/java/jdk1.8.0_461
ENV JRE_HOME $JAVA_HOME/jre
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
ENV PATH $JAVA_HOME/bin:$PATH
EXPOSE 80
CMD /bin/bash
bash
docker build -t centosjava8:461 .
docker run -it centosjava8:461 /bin/bash
# pwd 输出 /usr/local(验证 WORKDIR),java -version 正常(验证 JDK)
九、镜像管理常用命令与 save/load
| 命令 | 作用 |
|---|---|
docker images |
显示镜像列表 |
docker history |
显示镜像构建历史(分层结构) |
docker commit |
从容器创建新镜像 |
docker build |
从 Dockerfile 构建镜像 |
docker tag |
给镜像打 tag |
docker pull |
从 Registry 下载镜像 |
docker push |
上传镜像到 Registry |
docker rmi |
删除本地镜像(不会删除 Registry 中的镜像) |
docker search |
命令行搜索 Docker Hub 镜像 |
docker save / docker load |
镜像导出为 tar 文件 / 从 tar 导入(离线分发利器) |
两个易错点:
- rmi 与多 tag :一个镜像对应多个 tag 时,删除其中一个 tag 只是删除标签,只有最后一个 tag 被删除时镜像才真正被删除;
- save/load 离线迁移:
bash
# 导出(默认存 overlay2,直接拷贝文件系统很复杂,save 一步搞定)
docker save httpd -o httpd.tar
docker save httpd hello-world -o images.tar
# 导入(注意:本地已有同名镜像时会被覆盖)
docker rmi httpd:latest hello-world:latest
docker load -i httpd.tar
十、总结
本章要点回顾
- 最小镜像 hello-world 只有 13.3kB,
FROM scratch从零构建; - base 镜像 = 精简 rootfs + 复用宿主 kernel,所以 CentOS 才 204MB;容器只能使用且不能修改宿主 kernel;
- 分层结构 :镜像由只读镜像层 + 可写容器层构成,分层带来共享资源的高效;
- Copy-on-Write:容器层只记录变化(whiteout 遮挡删除、复制修改),镜像层永不被修改;
- 两种构建方式 :
docker commit(直观但不可审计、不推荐)vs Dockerfile(推荐,每条指令一层); - 缓存特性 :层不变则缓存复用,指令顺序变化会破坏缓存;RUN 装依赖放前面,COPY 代码放后面;
- 命名规范 :
[仓库地址]/[namespace]/[repository]:[tag],慎用 latest,多 tag 版本方案更专业; - 分发链路:Docker Hub / 华为云 SWR / 本地 registry:2 / 企业级 Harbor。