Docker入门|第3章 镜像详解

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容器的基石,容器是镜像的运行实例,只有拥有镜像才能够启动容器。 本章学习内容分为三大部分:

  1. 研究典型镜像,理解镜像的内部结构原理;
  2. 学习多种方式构建自定义镜像;
  3. 掌握镜像的管理、打标签、分发推送。

提示:日常直接下载使用公共镜像不需要理解底层结构;但如果需要自己制作镜像,弄懂镜像内部原理是非常必要的。

二、认识镜像:从最小镜像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"]

指令解读:

  1. FROM scratch:从0开始,空白基础镜像,没有任何系统文件;
  2. COPY hello /:将可执行程序复制到镜像根目录;
  3. CMD ["/hello"]:容器启动执行/hello程序,输出提示信息。

这个镜像里面仅仅只有一个可执行文件,连/bin/usr/lib/dev这些基础目录都不存在。实际业务不会直接使用,用来理解scratch空白镜像。

2.2 Base基础镜像

2.2.1 base镜像两层含义
  1. 从scratch从零构建,不依赖其他上层镜像
  2. 可以被其他镜像作为底层基础来扩展

常见base镜像就是各类Linux发行版镜像:CentOS、Ubuntu、Debian、Alpine。

疑问:完整CentOS操作系统安装后几个GB,但是Docker的centos镜像只有200MB左右?

2.2.2 rootfs与bootfs原理

Linux操作系统分为两大部分:

  1. bootfs(引导文件系统):内核kernel,系统启动完成之后bootfs会被卸载;
  2. 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 操作步骤
  1. 运行基础镜像,进入容器;
  2. 在容器内部手工安装软件、修改配置;
  3. 使用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缺点
  1. 手工操作,不可复现,环境迁移要重复手工操作;
  2. 镜像无法审计,外部不知道镜像内部做过哪些修改,存在安全风险;

虽然不推荐日常生产使用,但是底层Dockerfile构建本质依然是多次commit,学习commit可以加深分层原理理解。

4.2 Dockerfile文件构建(生产推荐)

4.2.1 Dockerfile基础语法规则
  1. 保留字指令大写,后面跟参数;
  2. 指令从上到下顺序执行;
  3. #代表注释;
  4. 每一条指令都会生成一层新镜像层

基础构建命令格式:

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都支持。

  1. Shell格式<指令> command param,底层调用/bin/sh -c,可以解析环境变量;
bash 复制代码
CMD echo "hello $name"
  1. 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三者对比

  1. RUN:镜像构建阶段执行,执行结果保存为镜像层;用于安装软件包。
  2. CMD :容器启动默认命令;docker run后面追加参数会完全替换掉CMD。
  3. ENTRYPOINT:容器启动入口,不会被run参数覆盖;run传入参数追加给ENTRYPOINT当参数。

组合示例(ENTRYPOINT + CMD)

bash 复制代码
FROM busybox
ENTRYPOINT ["/bin/echo","Hello"]
CMD ["world"]
  • docker run xxx →输出 Hello world
  • docker 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.11.91latest。 当发布v1.9.2,把1.91latest移动指向新版本。

打标签命令:

bash 复制代码
docker tag 源镜像:tag 目标镜像:tag

七、镜像分发:公共镜像仓库

7.1 Docker Hub公共仓库

  1. 在Docker Hub官网注册账号;
  2. 本地docker客户端登录:docker login -u 用户名
  3. 修改镜像名字格式:用户名/镜像名:版本
  4. docker push 用户名/镜像名:版本上传;
  5. 其他机器使用docker pull 用户名/镜像名:版本下载镜像。

Docker Hub官方镜像不需要加用户名前缀,如httpdcentos

7.2 华为云SWR容器镜像服务

  1. 登录华为云控制台,进入【容器镜像服务SWR】;
  2. 创建组织,获取临时登录指令;
  3. 本地执行docker login登录SWR仓库;
  4. docker tag把镜像重命名为SWR仓库地址格式;
  5. docker push上传镜像;
  6. 其他主机docker pull拉取镜像;
  7. 可以在控制台设置镜像公开/私有权限。

八、镜像分发:私有镜像仓库

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界面、用户权限管理、审计、项目隔离。

  1. 下载Harbor离线安装包;
  2. 修改harbor.yml配置hostname、admin密码;
  3. 执行./prepare./install.sh完成部署;
  4. 浏览器访问Harborweb页面;
  5. 创建项目、创建用户,给用户分配项目权限;
  6. docker配置daemon.json增加insecure‑registries支持http非安全仓库;
  7. docker login登录Harbor,完成镜像tag、push上传、pull下载。

九、第三章小结

  1. hello‑world演示scratch空白镜像;base镜像只提供rootfs,复用宿主机Linux内核,容器不能修改内核。
  2. Docker镜像采用分层只读结构,容器新增可写容器层,依靠Copy‑on‑Write写时复制实现修改,镜像层可以多容器共享。
  3. 构建镜像两种方式:docker commit手工提交(底层原理,不推荐生产);Dockerfile + docker build(生产标准)。
  4. 掌握Dockerfile核心指令,区分RUN / CMD / ENTRYPOINT,区分Shell格式与Exec数组格式,理解镜像缓存机制与调试手段。
  5. 镜像tag标签管理,不要生产直接使用latest标签,采用语义化版本打多tag。
  6. 镜像分发途径:Docker Hub、公有云镜像仓库(华为SWR)、简易registry私有仓库、企业Harbor私有仓库。

相关推荐
团子股股东峥哥1 小时前
day38-RHEL-管理存储堆栈
linux·运维·服务器
sbjdhjd1 小时前
从兼容路由到任意方法调用:ThinkPHP 5.1.29 RCE 复现与调试复盘 | 06
经验分享·安全·网络安全·数据挖掘·开源·php·rce
luj_17682 小时前
击穿分析五大关键步骤
开发语言·网络·c++·经验分享·算法
典典分享指南2 小时前
AI 配图的排版规范
macos·docker·github·vim·visual studio
JaguarJack2 小时前
PHP Trait 为何可能成为语言的关键特性
后端·php·服务端
开开心心就好2 小时前
低年级识字练笔顺工具,电脑和安卓端都能用
前端·javascript·网络·安全·scala·erlang·语音识别
BingoGo2 小时前
PHP Trait 为何可能成为语言的关键特性
后端·php
个性小王2 小时前
配置kali在线源(阿里云)
网络·网络安全
程序员老赵2 小时前
Docker 部署 DeepSeek Harness:轻松搭建本地 AI Agent 运行时平台
docker·agent·deepseek