Docker入门|第3章 镜像详解
摘要:本章深入讲解 Docker 镜像的核心原理与实战操作。首先通过 hello‑world 最小镜像认识 scratch 空白镜像,并剖析 base 镜像只提供 rootfs、复用宿主机内核的原理;接着介绍镜像分层只读结构与容器层 Copy‑on‑Write 写时复制机制。构建镜像方面,对比 docker commit 手工提交与 Dockerfile 标准构建两种方式,详解 FROM、RUN、CMD、ENTRYPOINT 等核心指令及 Shell/Exec 两种格式。最后讲解镜像 tag 语义化版本管理,以及 Docker Hub、华为云 SWR、Registry、Harbor 等公共与私有仓库的分发实践。
一、本章概述
1.1 学习目标
镜像是Docker容器的基石,容器是镜像的运行实例,只有拥有镜像才能够启动容器。 本章学习内容分为三大部分:
- 研究典型镜像,理解镜像的内部结构原理;
- 学习多种方式构建自定义镜像;
- 掌握镜像的管理、打标签、分发推送。
提示:日常直接下载使用公共镜像不需要理解底层结构;但如果需要自己制作镜像,弄懂镜像内部原理是非常必要的。
二、认识镜像:从最小镜像hello‑world开始
2.1 hello‑world最小镜像
2.1.1 下载并查看hello‑world镜像
hello‑world是Docker官方最小测试镜像,用于验证Docker环境是否正常。
bash
docker pull hello-world
docker images hello-world
输出可以看到镜像大小只有13.3KB,体积极小。
运行镜像:
bash
docker run hello-world
2.1.2 hello‑world的Dockerfile源码
bash
FROM scratch
COPY hello /
CMD ["/hello"]
指令解读:
FROM scratch:从0开始,空白基础镜像,没有任何系统文件;COPY hello /:将可执行程序复制到镜像根目录;CMD ["/hello"]:容器启动执行/hello程序,输出提示信息。
这个镜像里面仅仅只有一个可执行文件,连
/bin、/usr、/lib、/dev这些基础目录都不存在。实际业务不会直接使用,用来理解scratch空白镜像。
2.2 Base基础镜像
2.2.1 base镜像两层含义
- 从scratch从零构建,不依赖其他上层镜像;
- 可以被其他镜像作为底层基础来扩展。
常见base镜像就是各类Linux发行版镜像:CentOS、Ubuntu、Debian、Alpine。
疑问:完整CentOS操作系统安装后几个GB,但是Docker的centos镜像只有200MB左右?
2.2.2 rootfs与bootfs原理
Linux操作系统分为两大部分:
- bootfs(引导文件系统):内核kernel,系统启动完成之后bootfs会被卸载;
- rootfs(根文件系统) :用户空间,包含
/dev /proc /bin /etc /usr等目录。
✅Docker base镜像只提供rootfs,直接复用宿主机Host的kernel内核。 所以镜像不需要携带内核,体积可以做得很小。alpine基础镜像甚至不到10MB。
- 宿主机是什么内核,容器就使用什么内核;
- 容器内部不能修改、升级内核;
- 如果业务应用对内核版本有硬性要求,不适合容器,优先选择虚拟机。
验证现象: 在CentOS Stream8宿主机(内核4.18),启动ubuntu/centos7容器,执行uname -r,显示的依然是宿主机4.18内核。
2.2.3 CentOS base镜像Dockerfile片段
bash
FROM scratch
ADD centos-7-docker.tar.xz /
CMD ["/bin/bash"]
把centos的rootfs压缩包解压到根目录,就完成基础镜像构建。
三、Docker镜像分层结构原理
3.1 分层镜像思想
Docker Hub上绝大多数镜像,都是在base镜像之上一层层叠加软件构建而成。 每执行一条构建指令,就新增一层镜像。
示例Dockerfile:
bash
FROM debian
RUN apt-get install emacs
RUN apt-get install apache2
CMD ["/bin/bash"]
- 第一层:debian base镜像;
- 第二层:安装emacs生成新层;
- 第三层:安装apache2生成新层。
3.1.1 分层最大优势:资源共享
多个镜像如果基于同一个base镜像,宿主机磁盘只保存一份base镜像;内存也只加载一份,多个容器可以同时复用。
3.2 容器层:Copy‑on‑Write写时复制机制
所有镜像层全部是只读 ;容器启动的时候,会在所有只读镜像层之上新增一层可写容器层。
- 只读层:镜像层,永远不会被修改;
- 可写层:容器层,所有增删改操作全部发生在这里。
3.2.1 文件操作规则(写时复制Copy‑on‑Write)
| 操作 | 执行逻辑 |
|---|---|
| 读取文件 | 从上往下逐层查找,找到文件直接读取 |
| 新建文件 | 直接写入上层可写容器层 |
| 删除文件 | 在容器层生成whiteout遮挡文件,屏蔽下层镜像的文件,底层镜像不会改动 |
| 修改文件 | 找到下层镜像文件,复制一份到上层容器层,再修改副本,底层镜像原文件保持不变 |
镜像层只读,修改全部发生容器层,所以镜像可以被成千上百容器安全共享。
四、两种构建镜像方式
4.1 docker commit(手动提交构建)
4.1.1 操作步骤
- 运行基础镜像,进入容器;
- 在容器内部手工安装软件、修改配置;
- 使用
docker commit把当前容器状态保存为新镜像。
实操示例:
bash
#1.启动ubuntu容器
docker run -it ubuntu
#容器内部安装vim
apt-get update
apt-get install -y vim
#新开终端,查看容器ID/名字
docker ps
#提交容器生成新镜像
docker commit cool_darwin ubuntu-with-vim
#查看镜像
docker images
#从新镜像启动验证vim可用
docker run -it ubuntu-with-vim
4.1.2 docker commit缺点
- 手工操作,不可复现,环境迁移要重复手工操作;
- 镜像无法审计,外部不知道镜像内部做过哪些修改,存在安全风险;
虽然不推荐日常生产使用,但是底层Dockerfile构建本质依然是多次commit,学习commit可以加深分层原理理解。
4.2 Dockerfile文件构建(生产推荐)
4.2.1 Dockerfile基础语法规则
- 保留字指令大写,后面跟参数;
- 指令从上到下顺序执行;
#代表注释;- 每一条指令都会生成一层新镜像层。
基础构建命令格式:
bash
docker build -f Dockerfile路径 -t 镜像名:标签 构建上下文目录
-f:指定Dockerfile文件;-t:给新镜像打名字tag;- 末尾
.代表构建上下文为当前目录;
⚠️构建上下文:docker build会把上下文目录全部文件发送给dockerd服务端,不要把
/根目录作为上下文,避免传输海量文件,构建极慢。
示例Dockerfile(构建ubuntu-with-vim)
bash
FROM ubuntu
RUN apt-get update && apt-get install -y vim
构建:
bash
docker build -t ubuntu-with-vim-dockerfile .
4.2.2 镜像缓存特性
Docker会缓存已经生成的镜像层,构建镜像时如果指令不变,直接复用缓存,不用重新执行。
- 只要某一条指令发生改动,该指令之后所有层缓存全部失效,全部重新构建。
- 参数
--no-cache,构建时强制不使用缓存。
docker pull拉取镜像同样会复用本地已经存在镜像层,只下载缺失层。
4.2.3 Dockerfile调试技巧
构建中途某条指令报错,Docker会保留上一步成功构建出来的镜像。 直接运行这个中间镜像,进入容器手工复现执行指令,定位错误原因。
示例错误Dockerfile(busybox没有bash)
bash
FROM busybox
RUN touch tmpfile
RUN /bin/bash -c "echo continue to build..."
COPY testfile /
构建会报错,原因busybox只有sh没有bash;修改为/bin/sh即可修复。
五、Dockerfile常用指令详解
5.1 基础指令清单
| 指令 | 说明 |
|---|---|
FROM |
指定base基础镜像,Dockerfile第一条有效指令必须是FROM |
MAINTAINER |
设置镜像作者信息 |
COPY |
把构建上下文文件复制到镜像内部,不会自动解压 |
ADD |
复制文件,若源是压缩tar包会自动解压到目标路径 |
ENV |
设置环境变量,后续指令、容器运行时都可以读取 |
EXPOSE |
声明容器打算监听哪些端口,不会自动做端口映射 |
VOLUME |
声明数据卷挂载点 |
WORKDIR |
设置RUN/CMD/ENTRYPOINT的工作目录,目录不存在自动创建 |
RUN |
构建镜像阶段执行命令,生成新镜像层,常用于安装软件包 |
CMD |
容器启动时执行默认命令,可被docker run后面参数覆盖,多个CMD仅最后一条生效 |
ENTRYPOINT |
容器启动入口命令,不会被run参数覆盖;run参数会当做ENTRYPOINT参数传入 |
5.2 Shell格式与Exec格式
两种书写格式,RUN / CMD / ENTRYPOINT都支持。
- Shell格式 :
<指令> command param,底层调用/bin/sh -c,可以解析环境变量;
bash
CMD echo "hello $name"
- Exec格式(数组格式,推荐CMD、ENTRYPOINT使用) :
["可执行程序","参数1","参数2"],不经过shell,不会解析环境变量。
bash
CMD ["/bin/echo","hello world"]
如果exec格式需要解析环境变量,需要手动调用shell:
ENTRYPOINT ["/bin/sh","-c","echo hello $name"]
5.3 RUN、CMD、ENTRYPOINT三者对比
- RUN:镜像构建阶段执行,执行结果保存为镜像层;用于安装软件包。
- CMD :容器启动默认命令;
docker run后面追加参数会完全替换掉CMD。 - ENTRYPOINT:容器启动入口,不会被run参数覆盖;run传入参数追加给ENTRYPOINT当参数。
组合示例(ENTRYPOINT + CMD)
bash
FROM busybox
ENTRYPOINT ["/bin/echo","Hello"]
CMD ["world"]
docker run xxx→输出Hello worlddocker run xxx gqd→输出Hello gqd
最佳实践:
- RUN用来构建镜像安装软件;
- 业务服务镜像优先使用Exec格式ENTRYPOINT;
- CMD给ENTRYPOINT提供默认参数。
5.4 实战Dockerfile案例
5.4.1 案例1:制作带SSH的CentOS镜像
bash
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/](http://mirror.centos.org/)\$contentdir/\$releasever|baseurl=[https://mirrors.aliyun.com/centos-vault/$minorver|g](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
5.4.2 案例2:自定义httpd网页镜像
bash
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/](http://mirror.centos.org/)\$contentdir/\$releasever|baseurl=[https://mirrors.aliyun.com/centos-vault/$minorver|g](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"]
5.4.3 案例3:CentOS8 + vim + net‑tools + JDK8镜像
bash
FROM centos:8.4.2105
MAINTAINER gaoqiaodong<6946630@qq.com>
ENV MYPATH /usr/local
WORKDIR $MYPATH
RUN minorver=8.4.2105 \
&& sed -e "s|^mirrorlist=|#mirrorlist=|g" -e "s|^#baseurl=[http://mirror.centos.org/](http://mirror.centos.org/)\$contentdir/\$releasever|baseurl=[https://mirrors.aliyun.com/centos-vault/$minorver|g](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
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
六、镜像命名、标签tag最佳实践
6.1 镜像完整命名格式
bash
[仓库地址]/命名空间/仓库名:tag标签
示例: swr.cn‑east‑3.myhuaweicloud.com/library/nginx:latest
- 如果省略仓库地址,默认指向Docker Hub;
- tag用来标记版本,
latest只是默认标签,不保证是最新版本,生产环境禁止直接使用latest,必须明确指定版本号。
6.2 tag版本管理最佳实践
同一个镜像可以打多个tag,参考语义化版本:主版本.次版本.补丁版本 例如镜像版本v1.9.1,可以打标签:1.9.1、1.9、1、latest。 当发布v1.9.2,把1.9、1、latest移动指向新版本。
打标签命令:
bash
docker tag 源镜像:tag 目标镜像:tag
七、镜像分发:公共镜像仓库
7.1 Docker Hub公共仓库
- 在Docker Hub官网注册账号;
- 本地docker客户端登录:
docker login -u 用户名; - 修改镜像名字格式:
用户名/镜像名:版本; docker push 用户名/镜像名:版本上传;- 其他机器使用
docker pull 用户名/镜像名:版本下载镜像。
Docker Hub官方镜像不需要加用户名前缀,如
httpd、centos。
7.2 华为云SWR容器镜像服务
- 登录华为云控制台,进入【容器镜像服务SWR】;
- 创建组织,获取临时登录指令;
- 本地执行docker login登录SWR仓库;
- docker tag把镜像重命名为SWR仓库地址格式;
- docker push上传镜像;
- 其他主机docker pull拉取镜像;
- 可以在控制台设置镜像公开/私有权限。
八、镜像分发:私有镜像仓库
8.1 Docker官方Registry(简易私有仓库)
直接运行registry容器快速搭建私有仓库,端口5000。
bash
#启动registry容器,数据持久化到宿主机/myregistry
docker run -d -p 5000:5000 -v /myregistry:/var/lib/registry registry:2
#打标签,格式 localhost:5000/镜像名:tag
docker tag httpd:latest localhost:5000/httpd:v1
#上传镜像到本地私有仓库
docker push localhost:5000/httpd:v1
#测试拉取镜像
docker rmi localhost:5000/httpd:v1
docker pull localhost:5000/httpd:v1
registry功能简单,无web界面,无用户权限管理,适合学习测试。
8.2 Harbor企业级私有镜像仓库
Harbor是Registry上层封装的企业级私有仓库,提供WebUI界面、用户权限管理、审计、项目隔离。
- 下载Harbor离线安装包;
- 修改
harbor.yml配置hostname、admin密码; - 执行
./prepare、./install.sh完成部署; - 浏览器访问Harborweb页面;
- 创建项目、创建用户,给用户分配项目权限;
- docker配置
daemon.json增加insecure‑registries支持http非安全仓库; - docker login登录Harbor,完成镜像tag、push上传、pull下载。
九、第三章小结
- hello‑world演示scratch空白镜像;base镜像只提供rootfs,复用宿主机Linux内核,容器不能修改内核。
- Docker镜像采用分层只读结构,容器新增可写容器层,依靠Copy‑on‑Write写时复制实现修改,镜像层可以多容器共享。
- 构建镜像两种方式:
docker commit手工提交(底层原理,不推荐生产);Dockerfile + docker build(生产标准)。 - 掌握Dockerfile核心指令,区分RUN / CMD / ENTRYPOINT,区分Shell格式与Exec数组格式,理解镜像缓存机制与调试手段。
- 镜像tag标签管理,不要生产直接使用latest标签,采用语义化版本打多tag。
- 镜像分发途径:Docker Hub、公有云镜像仓库(华为SWR)、简易registry私有仓库、企业Harbor私有仓库。