文章目录
Docker:镜像
最小的镜像
镜像是docker 容器的基石,容器是镜像的运行实例,有了镜像才能启动容器
镜像的内部结构
如果我们只是单纯的使用docker,那么我们不需要了解这部分,但是我们想要自己创建属于自己的镜像,或者想明白docker为什么是轻量级的,那么这部分内容可以给你答案
在上一节,我们一起部署了docker,并且运行了第一个容器hello-world,在运行的过程中,我们是先去下载了它的镜像,后面再运行的容器,我们现在可以使用docker pull命令去从docker hub上去下载镜像
[root@docker ~]# docker pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
17eec7bbc9d7: Pull complete
Digest: sha256:f7931603f70e13dbd844253370742c4fc4202d290c80442b2e68706d8f33ce26
Status: Downloaded newer image for hello-world:latest
docker.io/library/hello-world:latest
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest 1b44b5a3e06a 3 months ago 10.1kB
我们可以看见这个镜像的大小非常小,我们安装虚拟机需要镜像可比这个要大得多,这也是docker容器轻量的一个体现
通过docker run运行
[root@docker ~]# docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
其实我们更关心的是hello-world镜像中都包含什么内容
Dockerfile 是镜像的描述文件,定义了如何构建 Docker 镜像。Dockerfile 的语法简洁且可读性强,后面我们会专门讨论如何编写 Dockerfile。
hello-world 的 Dockerfile 内容如下:
dockerhub hello-word网页:hello-world - Official Image | Docker Hub
github hello-wrold网页:hello-world/amd64/hello-world at master · docker-library/hello-world · GitHub

从网页中我们可以看出只有短短的三条指令
- FROM scratch
此镜像是从白手起家,从 0 开始构建。 - COPY hello /
将文件“hello”复制到镜像的根目录。 - CMD [“/hello”]
容器启动时,执行 /hello
镜像 hello-world 中就只有一个可执行文件 “hello”,其功能就是打印出 “Hello from Docker …” 等信息。
/hello 就是文件系统的全部内容,连最基本的 /bin,/usr, /lib, /dev 都没有。
hello-world 虽然是一个完整的镜像,但它并没有什么实际用途。通常来说,我们希望镜像能提供一个基本的操作系统环境,用户可以根据需要安装和配置软件。这样的镜像我们称作 base 镜像。
base镜像
base 镜像有两层含义:
- 不依赖其他镜像,从 scratch 构建。
- 其他镜像可以之为基础进行扩展。
所以,能称作 base 镜像的通常都是各种 Linux 发行版的 Docker 镜像,比如 Ubuntu, Debian, CentOS 等。
我们以 CentOS 为例考察 base 镜像包含哪些内容。
下载镜像:
docker pull centos:7
[root@docker ~]# docker pull centos:7
7: Pulling from library/centos
2d473b07cdd5: Pull complete
Digest: sha256:be65f488b7764ad3638f236b7b515b3678369a5124c47b8d32916d6487418ea4
Status: Downloaded newer image for centos:7
docker.io/library/centos:7
[root@docker ~]# docker images centos:7
REPOSITORY TAG IMAGE ID CREATED SIZE
centos 7 eeb6ee3f44bd 4 years ago 204MB
可以看见一个centos:7的镜像不到300MB
我们在装cenos虚拟机的时候可没有这么小吧
相信这是几乎所有 Docker 初学者都会有的疑问,包括我自己。下面我们来解释这个问题。
Linux 操作系统由内核空间和用户空间组成。如下图所示:

rootfs
内核空间是 kernel ,Linux刚启动时会加载 bootfs 文件系统,之后 bootfs 会被卸载掉。
用户空间的文件系统是 rootfs,包含我们熟悉的 /dev, /proc, /bin 等目录。
对于 base 镜像来说,底层直接用 Host 的 kernel,自己只需要提供 rootfs 就行了。
而对于一个精简的 OS,rootfs 可以很小,只需要包括最基本的命令、工具和程序库就可以了。相比其他 Linux 发行版,CentOS 的 rootfs 已经算臃肿的了,alpine 还不到 10MB。
所以 base 镜像提供的是最小安装的 Linux 发行版
支持运行多种Linux OS
不同 Linux 发行版的区别主要就是 rootfs。
比如 Ubuntu 14.04 使用 upstart 管理服务,apt 管理软件包;而 CentOS 7 使用 systemd 和 yum。这些都是用户空间上的区别,Linux kernel 差别不大。
所以docker 可以同时支持多种Linux 镜像,模拟多种操作系统环境

上图 Debian 和 BusyBox(一种嵌入式 Linux)上层提供各自的 rootfs,底层共用 Docker Host 的 kernel。
base 镜像只是在用户空间与发行版一致,kernel 版本和发行版是不同的
上面说过,实际base镜像底层使用的是本地host 的kernel,例如 ubuntu使用 3.x.x 的 kernel,如果 Docker Host 是 CentOS Stream 8(比如我们的实验环境),那么在 CentOS 容器中使用的实际是是 Host 4.18.0 的 kernel。
所以,所有容器都共用 host 的 kernel,在容器中没办法对 kernel 升级。如果容器对 kernel 版本有要求(比如应用只能在某个 kernel 版本下运行),则不建议用容器,这种场景虚拟机可能更合适。
通过对base 镜像的了解,我们也能明白对比虚拟机为什么docker容器体量会比虚拟机小很多的原因
镜像的分层结构
Docker 支持通过扩展现有镜像,创建新的镜像。
实际上,Docker Hub 中 99% 的镜像都是通过在 base 镜像中安装和配置需要的软件构建出来的。比如我们现在构建一个新的镜像,Dockerfile 如下:

① 新镜像不再是从 scratch 开始,而是直接在 Debian base 镜像上构建。
② 安装 emacs 编辑器。
③ 安装 apache2。
④ 容器启动时运行 bash。
意思其实就是像搭积木一样,新镜像是从 base 镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增加一层。
这里提出疑问,为什么要采用这样的分层结构呢?
最大的一个好处就是 - 共享资源。
比如:有多个镜像都从相同的 base 镜像构建而来,那么 Docker Host 只需在磁盘上保存一份 base 镜像;同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享
可写的容器层
当一个容器启动时,一个新的可写层会被加载到镜像的顶部
这一层通常被叫做“容器层”,“容器层”之下都叫做“镜像层”

所有对容器的改动 - 无论添加、删除、还是修改文件都只会发生在容器层中。
只有容器层是可写的,容器层下面的所有镜像层都是只读的。
所以不用担心,镜像对多个容器共享,但是对容器进行修改不会修改到底下的镜像层
容器层的细节
镜像层数量可能会很多,所有镜像层会联合在一起组成一个统一的文件系统。如果不同层中有一个相同路径的文件,比如 /a,上层的 /a 会覆盖下层的 /a,也就是说用户只能访问到上层中的文件 /a。在容器层中,用户看到的是一个叠加之后的文件系统。
对容器增删改差操作如下:
| 操作 | 具体执行 |
|---|---|
| 创建文件 | 新文件只能被添加在容器层中。 |
| 删除文件 | 依据容器分层结构由上往下依次查找。找到后,在容器层中记录该删除操作。 具体实现是,UnionFS会在容器层创建一个”whiteout”文件,将被删除的文件“遮挡”起来。 |
| 修改文件 | 依据容器分层结构由上往下依次查找。找到后,将镜像层中的数据复制到容器层进行修改,修改后的数据保存在容器层中。(copy-on-write) |
| 读取文件 | 依据容器分层结构由上往下依次查找。 |
只有当需要修改时才复制一份数据,这种特性被称作 Copy-on-Write。可见,容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改。
构建镜像
对于 Docker 用户来说,最好的情况是不需要自己创建镜像。几乎所有常用的数据库、中间件、应用软件等都有现成的 Docker 官方镜像或其他人和组织创建的镜像,我们只需要稍作配置就可以直接使用。
使用现成镜像的好处除了省去自己做镜像的工作量外,更重要的是可以利用前人的经验。特别是使用那些官方镜像,因为 Docker 的工程师知道如何更好的在容器中运行软件。
当然,某些情况下我们也不得不自己构建镜像,比如:
- 找不到现成的镜像,比如自己开发的应用程序。
- 需要在镜像中加入特定的功能,比如官方镜像几乎都不提供 ssh。
docker 容器文件系统

- Docker 镜像代表了容器的文件系统里的内容,是容器的基础,镜像一般是通过 Dockerfile 生成的;
- Docker 的镜像是分层的,所有的镜像(除了基础镜像)都是在之前镜像的基础上加上自己这层的内容生成的;
- Docker 中每一层镜像的元数据都是存在 json 文件中的,除了静态的文件系统之外,还会包含动态的数据;
- Docker 镜像生产容器后会在此基础之上加入挂载点到安装Docker宿主机文件系统之中,并提供一个读写层(Read-Write Layer),所以容器进程的所有操作都在读写层进行;
docker 两种构建镜像的方法
- docker commit 命令
- Dockerfile 构建文件
docker commit
docker commit 命令是创建新镜像最直观的方法,步骤包括:
- 运行容器
- 修改容器
- 将容器保存为新的镜像
接下来我们来试试创建一个新的镜像,在 ubuntu base 镜像中安装 vim并保存为新镜像。
-
运行容器
[root@docker ~]# docker run -it ubuntu Unable to find image 'ubuntu:latest' locally latest: Pulling from library/ubuntu 20043066d3d5: Pull complete Digest: sha256:c35e29c9450151419d9448b0fd75374fec4fff364a27f176fb458d472dfc9e54 Status: Downloaded newer image for ubuntu:latest root@925b15d0e46c:/#-it参数的作用是以交互模式进入容器,并打开终端。925b15d0e46c是容器的内部 ID。 -
修改容器
安装vim
# 验证没有安装过vim root@925b15d0e46c:/# vim bash: vim: command not found root@925b15d0e46c:/# apt-get update && apt-get install -y vim -
保存为新的镜像
新开一个窗口,查看当前运行的容器
[root@docker ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 925b15d0e46c ubuntu "/bin/bash" 4 minutes ago Up 4 minutes boring_engelbart上面查看结果的解释如下:
925b15d0e46c是新创建容器的IDboring_engelbart是 Docker 为我们的容器随机分配的名字。执行 docker commit 命令将容器保存为镜像。
[root@docker ~]# docker commit boring_engelbart ubuntu-with-vim sha256:329dbfa2aeb037f3bc817f9dd8d6f8146889c02987407131231dfa3c01040626新镜像命名为
ubuntu-with-vim。查看新镜像的属性。
[root@docker ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu-with-vim latest 329dbfa2aeb0 35 seconds ago 206MB ubuntu latest c3a134f2ace4 6 weeks ago 78.1MB从 size 上看到镜像因为安装了软件而变大了。
-
从新镜像启动一个容器,验证vim已经可以使用
[root@docker ~]# docker run -it ubuntu-with-vim root@6c9c9c6eda46:/# which vim /usr/bin/vim root@6c9c9c6eda46:/# vim file1 root@6c9c9c6eda46:/# cat file1 it's work以上演示了如何用 docker commit 创建新镜像。然而,Docker 并不建议用户通过这种方式构建镜像。原因如下:
- 这是一种手工创建镜像的方式,容易出错,效率低且可重复性弱。比如要在 debian base 镜像中也加入 vim,还得重复前面的所有步骤。
- 更重要的:使用者并不知道镜像是如何创建出来的,里面是否有恶意程序。也就是说无法对镜像进行审计,存在安全隐患。
既然 docker commit 不是推荐的方法,我们干嘛还要花时间学习呢?
原因是:即便是用 Dockerfile(推荐方法)构建镜像,底层也 docker commit 一层一层构建新镜像的。学习 docker commit 能够帮助我们更加深入地理解构建过程和镜像的分层结构。
Dockerfile 构建镜像
Dockerfile 是一个文本文件,记录了镜像构建的所有步骤
Dockerfile内容基础知识
- 每条保留字指令都必须为大写字母且后面要跟随至少一个参数
- 指令按照从上到下,顺序执行
- #表示注释
- 每条指令都会创建一个新的镜像层并对镜像进行提交
常用命令格式
docker build -f [Dockerfile路径] [构建上下文路径]
| 参数 | 作用 |
|---|---|
-f 或 --file | 标志符,声明要使用自定义 Dockerfile |
[Dockerfile路径] | 绝对路径或相对于构建上下文的路径(如 subdir/Dockerfile.dev) |
[构建上下文路径] | Docker 打包发送给守护进程的目录(通常用 . 表示当前目录) |
第一个Dockerfile
用Dockerfile 创建上面的 ubuntu-with-vim
[root@docker ~]# mkdir dockerfile
[root@docker ~]# cd dockerfile/
[root@docker dockerfile]# vim Dockerfile
[root@docker dockerfile]# cat Dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y vim

-
运行 docker build 命令,
-t将新镜像命名为ubuntu-with-vim-dockerfile,命令末尾的.指明 build context 为当前目录。Docker 默认会从 build context 中查找 Dockerfile 文件,我们也可以通过-f参数指定 Dockerfile 的位置。 -
从这步开始就是镜像真正的构建过程。 首先 Docker 将 build context 中的所有文件发送给 Docker daemon。build context 为镜像构建提供所需要的文件或目录。
Dockerfile 中的 ADD、COPY 等命令可以将 build context 中的文件添加到镜像。此例中,build context 为当前目录/root,该目录下的所有文件和子目录都会被发送给 Docker daemon。所以,使用 build context 就得小心了,不要将多余文件放到 build context,特别不要把
/、/usr作为 build context,否则构建过程会相当缓慢甚至失败 -
Step 1:执行
FROM,将 ubuntu 作为 base 镜像。 -
Step 2:执行
RUN,安装 vim -
镜像构建成功。
-
镜像重命名为ubuntu-with-vim-dockerfile
查看镜像信息
[root@docker dockerfile]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu-with-vim-dockerfile latest df1f4e88d6c9 7 minutes ago 206MB
ubuntu-with-vim latest 329dbfa2aeb0 3 hours ago 206MB
ubuntu latest c3a134f2ace4 6 weeks ago 78.1MB
在上面的构建过程中,我们要特别注意指令 RUN 的执行过程。Docker 会在启动的临时容器中执行操作,并通过 commit 保存为新的镜像。
查看镜像的分层结构
ubuntu-with-vim-dockerfile 是通过在 base 镜像的顶部添加一个新的镜像层而得到的。
这个新镜像层的内容由 RUN apt-get update && apt-get install -y vim 生成。这一点我们可以通过 docker history 命令验证。

docker history 会显示镜像的构建历史,也就是 Dockerfile 的执行过程。
ubuntu-with-vi-dockerfile 与 ubuntu 镜像相比,确实只是多了顶部的一层 df1f4e88d6c9,由 apt-get 命令创建,大小为 111MB。docker history 也向我们展示了镜像的分层结构,每一层由上至下排列。
注: 表示无法获取 IMAGE ID,通常从 Docker Hub 下载的镜像会有这个问题。
镜像的缓存特性
Docker 会缓存已有镜像的镜像层,构建新镜像时,如果某镜像层已经存在,就直接使用,无需重新创建。
但是我们希望在构建镜像的时候不使用缓存,可以在 docker build 命令中加上 --no-cache 参数。
Dockerfile 中每一个指令都会创建一个镜像层,上层是依赖于下层的。无论什么时候,只要某一层发生变化,其上面所有层的缓存都会失效。
也就是说,如果我们改变 Dockerfile 指令的执行顺序,或者修改或添加指令,都会使缓存失效。
虽然在逻辑上这种改动对镜像的内容没有影响,但由于分层的结构特性,Docker 必须重建受影响的镜像层。
举个示例:
现在我们再创建一个ubuntu镜像,但是在最后加一条指令
[root@docker ~]# cd dockerfile/
[root@docker dockerfile]# vim Dockerfile
[root@docker dockerfile]# touch testfile
[root@docker dockerfile]# cat Dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y vim
COPY testfile .
[root@docker dockerfile]# docker build -t ubuntu-with-vim-dockerfile-2 .

可以看见之前已经运行过相同的 RUN 指令,这次直接使用缓存中的镜像层,很快的新镜像就创建好了
我们来试试调换一下指令的位置,意思就是修改指令的结构
[root@docker dockerfile]# vim Dockerfile
[root@docker dockerfile]# cat Dockerfile
FROM ubuntu
COPY testfile .
RUN apt-get update && apt-get install -y vim

可以看见输出可以看到[2/3],[3/3]都没有使用缓存,缓存已经失效
Dockerfile 常用指令
下面列出了 Dockerfile 中最常用的指令,完整列表和说明可参看官方文档。
FROM
指定 base 镜像。第一条必须是FROM
MAINTAINER
设置镜像的作者,可以是任意字符串。
COPY
将文件从 build context 复制到镜像。
COPY 支持两种形式:
- COPY src dest
- COPY [“src”, “dest”]
注意:src 只能指定 build context 中的文件或目录。
ADD
与 COPY 类似,从 build context 复制文件到镜像。不同的是,如果 src 是归档文件(tar, zip, tgz, xz 等),文件会被自动解压到 dest。
ENV
设置环境变量,环境变量可被后面的指令使用。例如:
...
ENV MY_VERSION 1.3
RUN apt-get install -y mypackage=$MY_VERSION
...
EXPOSE
指定容器中的进程会监听某个端口,Docker 可以将该端口暴露出来。我们会在容器网络部分详细讨论。
VOLUME
将文件或目录声明为 volume。我们会在容器存储部分详细讨论。
WORKDIR
为后面的 RUN, CMD, ENTRYPOINT, ADD 或 COPY 指令设置镜像中的当前工作目录。
RUN
在容器中运行指定的命令。
CMD
容器启动时运行指定的命令。
Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效。CMD 可以被 docker run 之后的参数替换。
ENTRYPOINT
设置容器启动时运行的命令。
Dockerfile 中可以有多个 ENTRYPOINT 指令,但只有最后一个生效。CMD 或 docker run 之后的参数会被当做参数传递给 ENTRYPOINT。
接下来我们来看一个比较完整的Dockerfile:
# my dockerfile
FROM busybox #从busybox开始构建
MAINTAINER aabbcc #声明作者信息
WORKDIR /testdir #设置工作目录为/testdir
RUN touch tmpfile1 #在新镜像中创建tmpfille1
COPY ["tmpfile2","."] #将Dockerfile文件所在目录中的tmpfile2文件拷贝到新镜像中
ADD ["passwd.tar.gz","."] #将Dockerfile文件所在目录中的passwd.tar.gz拷贝到新镜像中并解压缩
ENV WELCOME "You are in my container,welcome!" #设置环境变量WELOCME
ENTRYPOINT echo "$WELCOME"
完整步骤:
[root@docker dockerfile]# pwd
/root/dockerfile
[root@docker dockerfile]# ls
[root@docker dockerfile]# touch tmpfile2
[root@docker dockerfile]# cp /etc/passwd .
[root@docker dockerfile]# tar -czvf passwd.tar.gz passwd
passwd
[root@docker dockerfile]# rm passwd
rm: remove regular file 'passwd'? yes
[root@docker dockerfile]# vim Dockerfile
[root@docker dockerfile]# cat Dockerfile
FROM busybox
MAINTAINER aabbcc
WORKDIR /testdir
RUN touch tmpfile1
COPY ["tmpfile2","."]
ADD ["passwd.tar.gz","."]
ENV WELCOME "You are in my container,welcome!"
ENTRYPOINT echo "$WELCOME"
[root@docker dockerfile]# ls
Dockerfile passwd.tar.gz tmpfile2
[root@docker dockerfile]# docker build -t my-image .

运行容器,验证镜像效果:
[root@docker ~]# docker run my-image
You are in my container,welcome!
在上面这些指令中,RUN、CMD、ENTRYPOINT 很重要且容易混淆
下面专门介绍
RUN vs CMD vs ENTRYPOINT
RUN、CMD 和 ENTRYPOINT 这三个 Dockerfile 指令看上去很类似,很容易混淆。
简单的说:
- RUN 执行命令并创建新的镜像层,RUN 经常用于安装软件包。
- CMD 设置容器启动后默认执行的命令及其参数,但 CMD 能够被
docker run后面跟的命令行参数替换。 - ENTRYPOINT 配置容器启动时运行的命令。
Shell 和 Exec 格式
我们可用两种方式指定 RUN、CMD 和 ENTRYPOINT 要运行的命令:Shell 格式和 Exec 格式,二者在使用上有细微的区别。
Shell 格式
<instruction> <command>
例如:
RUN apt-get install python3
CMD echo "Hello world"
ENTRYPOINT echo "Hello world"
当指令执行时,shell 格式底层会调用 /bin/sh -c 。
例如下面的 Dockerfile :
[root@docker ~]# vim Dockerfile
FROM busybox
ENV name aabbcc
ENTRYPOINT echo "Hello, $name"
用上面的Dockerfile创建镜像dockerfile1用于测试
[root@docker ~]# docker build -t dockerfile1 .
执行 docker run dockerfile1:
[root@docker ~]# docker run dockerfile1
Hello,aabbcc
注意环境变量 name 已经被值 aabbcc 替换。
Exec 格式
<instruction> ["executable", "param1", "param2", ...]
例如:
RUN ["apt-get", "install", "python3"]
CMD ["/bin/echo", "Hello world"]
ENTRYPOINT ["/bin/echo", "Hello world"]
当指令执行时,会直接调用 ,不会被 shell 解析。
例如下面的 Dockerfile :
[root@docker ~]# vim Dockerfile
FROM busybox
ENV name aabbcc
ENTRYPOINT ["/bin/echo", "Hello, $name"]
用上面的Dockerfile创建镜像dockerfile2用于测试
[root@docker ~]# docker build -t dockerfile2 .
执行 docker run dockerfile2:
[root@docker ~]# docker run dockerfile2
hello,$name
注意环境变量“name”没有被替换。
如果希望使用环境变量,照如下修改
[root@docker ~]# vim Dockerfile
FROM busybox
ENV name aabbcc
ENTRYPOINT ["/bin/sh", "-c", "echo Hello, $name"]
用上面的Dockerfile创建镜像dockerfile3用于测试
[root@docker ~]# docker build -t dockerfile3 .
执行 docker run dockerfile3:
[root@docker ~]# docker run dockerfile3
Hello, aabbcc
CMD 和 ENTRYPOINT 推荐使用 Exec 格式,因为指令可读性更强,更容易理解。RUN 则两种格式都可以。
RUN
RUN 指令通常用于安装应用和软件包。
RUN 在当前镜像的顶部执行命令,并通过创建新的镜像层。Dockerfile 中常常包含多个 RUN 指令。
RUN 有两种格式:
- Shell 格式:RUN
- Exec 格式:RUN [“executable”, “param1”, “param2”]
注意:apt-get update 和 apt-get install 被放在一个 RUN 指令中执行,这样能够保证每次安装的是最新的包。如果 apt-get install 在单独的 RUN 中执行,则会使用 apt-get update 创建的镜像层,而这一层可能是很久以前缓存的。
例如:
[root@docker ~]# vim Dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion
CMD
CMD 指令允许用户指定容器的默认执行的命令。
此命令会在容器启动且 docker run 没有指定其他命令时运行。
- 如果 docker run 指定了其他命令,CMD 指定的默认命令将被忽略。
- 如果 Dockerfile 中有多个 CMD 指令,只有最后一个 CMD 有效。
CMD 有三种格式:
- Exec 格式:CMD [“executable”,“param1”,“param2”] 这是 CMD 的推荐格式。
- CMD [“param1”,“param2”] 为 ENTRYPOINT 提供额外的参数,此时 ENTRYPOINT 必须使用 Exec 格式。
- Shell 格式:CMD command param1 param2
例如:
[root@docker ~]# vim Dockerfile
FROM busybox
CMD echo "Hello,world"
使用这个Dockerfile创建的镜像来测试
[root@docker ~]# docker build -t dockerfile5 .
[root@docker ~]# docker run -it dockerfile5
Hello,world
当我们在后面加一条命令,比如 /bin/sh ,CMD就会被忽略掉,命令 sh 将被执行
[root@docker ~]# docker run -it dockerfile5 /bin/sh
/ #
ENTRYPOINT
ENTRYPOINT 指令可让容器以应用程序或者服务的形式运行。
ENTRYPOINT 看上去与 CMD 很像,它们都可以指定要执行的命令及其参数。不同的地方在于 ENTRYPOINT 不会被忽略,一定会被执行,即使运行 docker run 时指定了其他命令。
ENTRYPOINT 有两种格式:
- Exec 格式:ENTRYPOINT [“executable”, “param1”, “param2”] 这是 ENTRYPOINT 的推荐格式。
- Shell 格式:ENTRYPOINT command param1 param2
在为 ENTRYPOINT 选择格式时必须小心,因为这两种格式的效果差别很大。
Exec 格式
ENTRYPOINT 的 Exec 格式用于设置要执行的命令及其参数,同时可通过 CMD 提供额外的参数。
ENTRYPOINT 中的参数始终会被使用,而 CMD 的额外参数可以在容器启动时动态替换掉。
比如下面的 Dockerfile :
[root@docker ~]# vim Dockerfile
FROM busybox
ENTRYPOINT ["/bin/echo", "Hello"]
CMD ["world"]
用上面的Dockerfile创建镜像dockerfile6用于测试
[root@docker ~]# docker build -t dockerfile6 .
当容器通过 docker run -it dockerfile6 启动时,输出为:
[root@docker ~]# docker run -it dockerfile6
Hello world
而如果通过 docker run -it dockerfile6 aabbcc 启动,则输出为:
[root@docker ~]# docker run -it dockerfile6 aabbcc
Hello aabbcc
Shell 格式
ENTRYPOINT 的 Shell 格式会忽略任何 CMD 或 docker run 提供的参数。
比如下面的 Dockerfile :
FROM busybox
ENTRYPOINT echo "Hello,"
CMD ["world"]
用上面的Dockerfile创建镜像dockerfile7用于测试
[root@docker ~]# docker build -t dockerfile7 .
当容器通过 docker run -it dockerfile7 启动时,输出为:
[root@docker ~]# docker run -it dockerfile7
Hello,
而如果通过 docker run -it dockerfile7 aabbcc 启动,则输出为:
[root@docker ~]# docker run -it dockerfile7 aabbcc
Hello,
到这里,我们已经具备编写 Dockerfile 的能力了。如果大家还觉得没把握,推荐一个快速掌握 Dockerfile 的方法:去 Docker Hub 上参考那些官方镜像的 Dockerfile。
Dockerfile 实践
配置SSH镜像
项目背景:官方下载的centos镜像默认不带ssh,管理起来不方便,自己制作一个带SSH功能的centos镜像
创建Dockerfile
[root@docker ~]# cd dockerfile/
[root@docker dockerfile]# vim Dockerfile
[root@docker dockerfile]# cat Dockerfile
FROM centos:8.4.2105
MAINTAINER aabbcc
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"]
构建镜像
[root@docker dockerfile]# docker build -t centos:ssh .
[+] Building 32.4s (10/10) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 528B 0.0s
=> [internal] load metadata for docker.io/library/centos:8.4.2105 5.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/6] FROM docker.io/library/centos:8.4.2105@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177 17.6s
=> => resolve docker.io/library/centos:8.4.2105@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177 0.0s
=> => sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177 762B / 762B 0.0s
=> => sha256:a1801b843b1bfaf77c501e7a6d3f709401a1e0c83863037fa3aab063a7fdb9dc 529B / 529B 0.0s
=> => sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6 2.14kB / 2.14kB 0.0s
=> => sha256:a1d0c75327776413fa0db9ed3adcdbadedc95a662eb1d360dad82bb913f8a1d1 83.52MB / 83.52MB 11.7s
=> => extracting sha256:a1d0c75327776413fa0db9ed3adcdbadedc95a662eb1d360dad82bb913f8a1d1 5.7s
=> [2/6] RUN minorver=8.4.2105 && sed -e "s|^mirrorlist=|#mirrorlist=|g" -e "s|^#baseurl=http://mirror.centos.org/$contentdi 0.8s
=> [3/6] RUN yum install -y openssh-server 7.8s
=> [4/6] RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key 0.4s
=> [5/6] RUN ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key 0.3s
=> [6/6] RUN echo "root:huawei" | chpasswd 0.4s
=> exporting to image 0.1s
=> => exporting layers 0.1s
=> => writing image sha256:7bd9b16a949c4b6bcd27a43be794cb49828f51b2d4a6684856869fc9587ed84c 0.0s
=> => naming to docker.io/library/centos:ssh
[root@docker dockerfile]# docker history centos:ssh
IMAGE CREATED CREATED BY SIZE COMMENT
7bd9b16a949c 31 seconds ago CMD ["/usr/sbin/sshd" "-D"] 0B buildkit.dockerfile.v0
<missing> 31 seconds ago EXPOSE map[22/tcp:{}] 0B buildkit.dockerfile.v0
<missing> 31 seconds ago RUN /bin/sh -c echo "root:huawei" | chpasswd… 1.77kB buildkit.dockerfile.v0
<missing> 31 seconds ago RUN /bin/sh -c ssh-keygen -t ecdsa -f /etc/s… 695B buildkit.dockerfile.v0
<missing> 31 seconds ago RUN /bin/sh -c ssh-keygen -t rsa -f /etc/ssh… 3.18kB buildkit.dockerfile.v0
<missing> 32 seconds ago RUN /bin/sh -c yum install -y openssh-server… 51.9MB buildkit.dockerfile.v0
<missing> 40 seconds ago RUN /bin/sh -c minorver=8.4.2105 && sed -e "… 17.6kB buildkit.dockerfile.v0
<missing> 40 seconds ago MAINTAINER aabbcc 0B buildkit.dockerfile.v0
<missing> 4 years ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 4 years ago /bin/sh -c #(nop) LABEL org.label-schema.sc… 0B
<missing> 4 years ago /bin/sh -c #(nop) ADD file:805cb5e15fb6e0bb0… 231MB
测试
[root@docker ~]# docker run -d -p 2022:22 --name sshtest centos:ssh
ebb58a5bbcdc4148d9e63cc287a96e39be5a1d73c8b49011662573b31e3dafb5
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ebb58a5bbcdc centos:ssh "/usr/sbin/sshd -D" 12 seconds ago Up 10 seconds 0.0.0.0:2022->22/tcp, :::2022->22/tcp sshtest
# 使用ssh登陆容器测试ssh
[root@docker ~]# ssh root@localhost -p 2022
The authenticity of host '[localhost]:2022 ([::1]:2022)' can't be established.
ECDSA key fingerprint is SHA256:oEkLU1L7e2ZuciWTXxDr0qZ86Hc0zfxWi26WGNguGqI.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[localhost]:2022' (ECDSA) to the list of known hosts.
root@localhost's password: `huawei`
"System is booting up. Unprivileged users are not permitted to log in yet. Please come back later. For technical details, see pam_nologin(8)."
Last failed login: Wed Dec 3 10:59:44 UTC 2025 from 172.17.0.1 on ssh:notty
There was 1 failed login attempt since the last successful login.
[root@ebb58a5bbcdc ~]#
镜像命名
当我们执行 docker build命令时已经为镜像取了个名字,例如前面:docker build -t ubuntu-with-vim
这里的ubuntu-with-vim 就是镜像的名字,通过 docker images 可以查看到镜像的信息
实际上一个特定镜像的名字由两部分组成:repository 和 tag
[image name] = [repository]:[tag]
如果执行 docker build 时没有指定 tag,会使用默认值 latest。其效果相当于:
docker build -t ubuntu-with-vim:latest
tag 通常用于描述镜像版本信息,比如 httpd 镜像:

当然tag 也可以是任意字符串,比如 ubuntu 镜像:

当然不要被tag 误导,latest 其实没有什么特殊含义,只是docker在没有明确指定tag的时候,会默认使用tag=latest,仅此而已
我们也可以使用docker tag命令来对镜像进行重命名和tag
比如:
我们将 httpd:2.4 进行一个重命名

我们可以看见重命名成功,并且与我们平常的重命名不一样,他不是对原本的镜像的修改,而是保留了原先的镜像,并多出来一个我们修改的镜像
Registry-dockerhub 仓库
Docker仓库类似于代码仓库,它是Docker集中存放镜像文件的场所。仓库注册服务器存放着很多类镜像,每类镜像包括多个镜像文件,通过不同的标签(tag)来进行区分。
根据所存储的镜像公开分享与否,Docker仓库可以分为:
- 公开仓库(Public)
- 私有仓库(Private)
目前,最大的公开仓库是官方提供的Docker Hub,其中存放了数量庞大的镜像供用户下载。国内不少云服务提供商(如华为、阿里云等)也提供了仓库的本地源,可以提供稳定的国内访问。
保存和分发镜像的最直接方法就是使用 Docker Hub。
Docker Hub 是 Docker 公司维护的公共 Registry。用户可以将自己的镜像保存到 Docker Hub 免费的 repository 中。如果不希望别人访问自己的镜像,也可以购买私有 repository。
公共Registry-华为云
-
登陆华为云
-
产品 -> 容器镜像服务SWR
-
选择区域,离自己近的数据中心,创建组织
-
进入组织 -> 总览 -> 登陆指令
通过上面获取的登陆指令返回docker上进行登录
注意:登陆指令每个人是不一样的
[root@docker ~]# docker login -u cn-east-3@HST3WQTLO0QYJ0324VBD -p 621a0d69755a4965765ed1e39c86d8bbffc1a800bdfc65c1f5c251fdc7d4dc48 swr.cn-east-3.myhuaweicloud.com WARNING! Using --password via the CLI is insecure. Use --password-stdin. WARNING! Your password will be stored unencrypted in /root/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store Login Succeeded -
上传镜像
#命令格式 sudo docker tag {镜像名称}:{版本名称} swr.cn-east-3.myhuaweicloud.com/{组织名称}/{镜像名称}:{版本名称} sudo docker push swr.cn-east-3.myhuaweicloud.com/{组织名称}/{镜像名称}:{版本名称}# 将刚才Dockerfile 制作的httpd:centos镜像来上传 [root@docker ~]# docker images httpd:centos REPOSITORY TAG IMAGE ID CREATED SIZE httpd centos 7bd9b16a949c 4 hours ago 288MB # 修改镜像名 [root@docker ~]# docker tag httpd:centos swr.cn-east-3.myhuaweicloud.com/huangxinlong/centos_httpd:v1 # 查看现象 [root@docker ~]# docker images # 上传镜像 [root@docker ~]# docker push swr.cn-east-3.myhuaweicloud.com/huangxinlong/centos_httpd:v1 -
上传成功登陆华为云查看
组织 -> 镜像
-
将自己刚上传的镜像设置为公开,然后删除自己的镜像,下载测试
华为云镜像非常贴心的为你提供了下载镜像的指令,直接复制到命令行即可
本地Registry
当然,用户如果不希望公开分享自己的镜像文件,Docker也支持用户在本地网络内创建一个只能自己访问的私有仓库。当用户创建了自己的镜像之后就可以使用push命令将它上传到指定的公有或者私有仓库。这样用户下次在另外一台机器上使用该镜像时,只需要将其从仓库上pull下来就可以了。
Registry
Docker 已经将 Registry 开源了,同时在 Docker Hub 上也有官方的镜像 registry。下面我们就在 Docker 中运行自己的 registry。
1.启动registry 容器
[root@docker ~]# docker run -d -p 5000:5000 -v /myregistry:/var/lib/registry registry:2
我们使用的镜像是registry:2
-d 是后台启动容器。
-p 将容器的 5000 端口映射到 Host 的 5000 端口。5000 是 registry 服务端口。
-v 将容器 /var/lib/registry 目录映射到 Host 的 /myregistry,用于存放镜像数据。
2.通过docker tag重命名镜像,使之与 registry匹配
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
registry 2 26b2eb03618e 2 years ago 25.4MB
[root@docker ~]# docker tag registry:2 localhost:5000/httpd:v1
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
registry 2 26b2eb03618e 2 years ago 25.4MB
localhost:5000/httpd v1 26b2eb03618e 2 years ago 25.4MB
3.通过docker push上传镜像
[root@docker ~]# docker push localhost:5000/httpd:v1
The push refers to repository [localhost:5000/httpd]
53c600587fd6: Pushed
858f5c95b990: Pushed
811f3777554a: Pushed
f646c8e10325: Pushed
f44f286046d9: Pushed
v1: digest: sha256:266f282fabd7cd3df053ee7c658c77b42380d44344e33d16c5a4e58d0d5a77d7 size: 1363
[root@docker ~]# curl http://localhost:5000/v2/_catalog
{"repositories":["httpd"]}
4.现在通过docker pull从本地registry 下载镜像
#先将本地镜像删除
[root@docker ~]# docker rmi localhost:5000/httpd:v1
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
registry 2 26b2eb03618e 2 years ago 25.4MB
#从自建的仓库下载镜像
[root@docker ~]# docker pull localhost:5000/httpd:v1
v1: Pulling from httpd
Digest: sha256:266f282fabd7cd3df053ee7c658c77b42380d44344e33d16c5a4e58d0d5a77d7
Status: Downloaded newer image for localhost:5000/httpd:v1
localhost:5000/httpd:v1
#验证从自建仓库下载的镜像
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
registry 2 26b2eb03618e 2 years ago 25.4MB
localhost:5000/httpd v1 26b2eb03618e 2 years ago 25.4MB
以上是搭建本地 registry 的简要步骤。当然 registry 也支持认证,https 安全传输等特性,具体可以参考官方文档 https://docs.docker.com/registry/configuration/
企业级私有仓库Harbor
我们如果需要部署一个私有镜像仓库来使用,最简单的就是 registry ,一行命令就可以运行在 Docker 中,但功能也比较弱,如果想要私有镜像仓库功能更丰富些,可以使用 Harbor。
Harbor是构建企业级私有docker镜像的仓库的开源解决方案,它是Docker Registry的更高级封装,除了提供友好的Web UI界面,角色和用户权限管理,用户操作审计等功能外,它还整合了K8s的插件(Add-ons)仓库。
harbor下载:https://github.com/goharbor/harbor/releases
安装
[root@docker ~]# wget https://github.com/goharbor/harbor/releases/download/v2.9.1/harbor-offline-installer-v2.9.1.tgz
如果无法通过 wget 进行下载,可以直接到 Github 网站:https://github.com/goharbor/harbor/releases/ 进行下载,然后拷贝到服务器中
[root@docker ~]# ls
harbor-offline-installer-v2.9.1.tgz
解压安装包
[root@docker ~]# tar -xvf harbor-offline-installer-v2.9.1.tgz
harbor/harbor.v2.9.1.tar.gz
harbor/prepare
harbor/LICENSE
harbor/install.sh
harbor/common.sh
harbor/harbor.yml.tmpl
新建一个目录,并将程序文件复制到目录中
[root@docker ~]# mkdir /opt/harbor
[root@docker ~]# mv harbor/* /opt/harbor/
导入harbor镜像
[root@docker ~]# cd /opt/harbor/
[root@docker harbor]# docker load -i harbor.v2.9.1.tar.gz
修改harbor的配置文件
[root@docker harbor]# pwd
/opt/harbor
[root@docker harbor]# cp -ar harbor.yml.tmpl harbor.yml
[root@docker harbor]# vim harbor.yml

**hostname:**如果只是内网访问,设置为内网 IP,如果需要外网访问,就必须设置为外网域名或 IP
https: #注释掉
harbor_admin_password: harbor登录密码
然后再harbor目录下安装Harbor,先进行预处理更新配置文件
[root@docker harbor]# ./prepare
WARNING:root:WARNING: HTTP protocol is insecure. Harbor will deprecate http protocol in the future. Please make sure to upgrade to https
Generated configuration file: /config/portal/nginx.conf
Generated configuration file: /config/log/logrotate.conf
Generated configuration file: /config/log/rsyslog_docker.conf
Generated configuration file: /config/nginx/nginx.conf
Generated configuration file: /config/core/env
Generated configuration file: /config/core/app.conf
Generated configuration file: /config/registry/config.yml
Generated configuration file: /config/registryctl/env
Generated configuration file: /config/registryctl/config.yml
Generated configuration file: /config/db/env
Generated configuration file: /config/jobservice/env
Generated configuration file: /config/jobservice/config.yml
Generated and saved secret to file: /data/secret/keys/secretkey
Successfully called func: create_root_cert
Generated configuration file: /compose_location/docker-compose.yml
Clean up the input dir
安装
[root@docker harbor]# ./install.sh
[Step 0]: checking if docker is installed ...
Note: docker version: 26.1.3
[Step 1]: checking docker-compose is installed ...
Note: Docker Compose version v2.27.0
[Step 2]: loading Harbor images ...
Loaded image: goharbor/harbor-jobservice:v2.9.1
Loaded image: goharbor/harbor-registryctl:v2.9.1
Loaded image: goharbor/harbor-core:v2.9.1
Loaded image: goharbor/harbor-log:v2.9.1
Loaded image: goharbor/harbor-db:v2.9.1
Loaded image: goharbor/harbor-exporter:v2.9.1
Loaded image: goharbor/redis-photon:v2.9.1
Loaded image: goharbor/nginx-photon:v2.9.1
Loaded image: goharbor/registry-photon:v2.9.1
Loaded image: goharbor/trivy-adapter-photon:v2.9.1
Loaded image: goharbor/prepare:v2.9.1
Loaded image: goharbor/harbor-portal:v2.9.1
[Step 3]: preparing environment ...
[Step 4]: preparing harbor configs ...
prepare base dir is set to /opt/harbor
WARNING:root:WARNING: HTTP protocol is insecure. Harbor will deprecate http protocol in the future. Please make sure to upgrade to https
Clearing the configuration file: /config/portal/nginx.conf
Clearing the configuration file: /config/log/logrotate.conf
Clearing the configuration file: /config/log/rsyslog_docker.conf
Clearing the configuration file: /config/nginx/nginx.conf
Clearing the configuration file: /config/core/env
Clearing the configuration file: /config/core/app.conf
Clearing the configuration file: /config/registry/passwd
Clearing the configuration file: /config/registry/config.yml
Clearing the configuration file: /config/registryctl/env
Clearing the configuration file: /config/registryctl/config.yml
Clearing the configuration file: /config/db/env
Clearing the configuration file: /config/jobservice/env
Clearing the configuration file: /config/jobservice/config.yml
Generated configuration file: /config/portal/nginx.conf
Generated configuration file: /config/log/logrotate.conf
Generated configuration file: /config/log/rsyslog_docker.conf
Generated configuration file: /config/nginx/nginx.conf
Generated configuration file: /config/core/env
Generated configuration file: /config/core/app.conf
Generated configuration file: /config/registry/config.yml
Generated configuration file: /config/registryctl/env
Generated configuration file: /config/registryctl/config.yml
Generated configuration file: /config/db/env
Generated configuration file: /config/jobservice/env
Generated configuration file: /config/jobservice/config.yml
loaded secret from file: /data/secret/keys/secretkey
Generated configuration file: /compose_location/docker-compose.yml
Clean up the input dir
Note: stopping existing Harbor instance ...
WARN[0000] /opt/harbor/docker-compose.yml: `version` is obsolete
[Step 5]: starting Harbor ...
WARN[0000] /opt/harbor/docker-compose.yml: `version` is obsolete
[+] Running 10/10
✔ Network harbor_harbor Created 0.2s
✔ Container harbor-log Started 0.4s
✔ Container registryctl Started 1.9s
✔ Container redis Started 1.8s
✔ Container harbor-db Started 2.1s
✔ Container registry Started 1.8s
✔ Container harbor-portal Started 1.9s
✔ Container harbor-core Started 2.6s
✔ Container harbor-jobservice Started 2.9s
✔ Container nginx Started 3.0s
✔ ----Harbor has been installed and started successfully.----
等待安装,当所有容器的状态都是 healthy ,说明正常
登陆web界面:http://192.168.108.30

使用
进入主页面,开始使用
Harbor 里功能比较多,常用的有项目、用户管理、项目定额。
- 项目:可以针对不同的项目单独创建,每个项目都有自己的镜像地址
- 用户管理:可以维护用户,不同的项目可以设置不同的维护人员
- 项目定额:设置项目对应的镜像仓库最大空间容量
我们来试试上传推送一个镜像到harbor中
1.在用户管理中创建一个名称为 images_admin 的用户

在项目中创建名为cloud的项目,并添加用户 images_admin 为管理员

点击进入cloud项目,选择成员开始添加管理员


然后将内网服务器IP 和端口配置到 daemon.json 文件中,执行下面命令来配置
[root@docker ~]# vim /etc/docker/daemon.json
[root@docker ~]# cat /etc/docker/daemon.json
{
"insecure-registries": ["192.168.108.30"],
"registry-mirrors":["https://910c20a65ff847f58b3893f2153f8612.mirror.swr.myhuaweicloud.com"]
}
[root@docker ~]# systemctl restart docker
# 重新执行安装命令
[root@docker ~]# cd /opt/harbor/
[root@docker harbor]# ./install.sh
登陆服务器
[root@docker ~]# docker login 192.168.108.30
Username: images_admin
Password: Huawei@123
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
[root@docker ~]#
登陆后家目录下会有一个.docker文件夹
[root@docker ~]# ls -a
. .bash_history .bash_profile .config .docker harbor-offline-installer-v2.9.1.tgz .tcshrc
.. .bash_logout .bashrc .cshrc harbor .ssh .viminfo
[root@docker ~]# cd .docker/
[root@docker .docker]# pwd
/root/.docker
[root@docker .docker]# cat config.json
{
"auths": {
"192.168.108.30": {
"auth": "aW1hZ2VzX2FkbWluOkh1YXdlaUAxMjM="
},
"swr.cn-east-3.myhuaweicloud.com": {
"auth": "Y24tZWFzdC0zQEhTVDNXUVRMTzBRWUowMzI0VkJEOjYyMWEwZDY5NzU1YTQ5NjU3NjVlZDFlMzljODZkOGJiZmZjMWE4MDBiZGZjNjVjMWY1YzI1MWZkYzdkNGRjNDg="
}
}
}
上传镜像

[root@docker .docker]# docker tag nginx:latest 192.168.108.30/cloud/nginx:latest
# 若没有nginx镜像,使用docker pull nginx命令拉取一个
[root@docker .docker]# docker push 192.168.108.30/cloud/nginx:latest
The push refers to repository [192.168.108.30/cloud/nginx]
38d44e06fd01: Pushed
388bb4cadb9e: Pushed
5f0d4d15245b: Pushed
fe0771a36433: Pushed
1e79db1a7c1e: Pushed
008ba900efa1: Pushed
70a290c5e58b: Pushed
latest: digest: sha256:33fcef672de1e09c9c1a2a58199de9cdcd14f5782feee6fc63eaae41828a80fa size: 1778
镜像格式:SERVER/PROJECT/PATH/TO/IMAGE/IMAGE:TAG
查看并检查镜像


[root@docker .docker]# docker pull 192.168.108.30/cloud/nginx@sha256:33fcef672de1e09c9c1a2a58199de9cdcd14f5782feee6fc63eaae41828a80fa
卸载harbor
清理容器
[root@docker ~]# cd /opt/harbor/
[root@docker harbor]# docker compose down
WARN[0000] /opt/harbor/docker-compose.yml: `version` is obsolete
[+] Running 10/10
✔ Container harbor-jobservice Removed 0.2s
✔ Container nginx Removed 0.2s
✔ Container registryctl Removed 0.1s
✔ Container harbor-portal Removed 0.1s
✔ Container harbor-core Removed 0.1s
✔ Container registry Removed 0.1s
✔ Container redis Removed 0.2s
✔ Container harbor-db Removed 0.1s
✔ Container harbor-log Removed 10.1s
✔ Network harbor_harbor Removed 0.1s
清理镜像
[root@docker harbor]# docker images | grep harbor | awk '{print $1":"$2}'
goharbor/redis-photon:v2.9.1
goharbor/harbor-registryctl:v2.9.1
goharbor/registry-photon:v2.9.1
goharbor/nginx-photon:v2.9.1
goharbor/harbor-log:v2.9.1
goharbor/harbor-jobservice:v2.9.1
goharbor/harbor-core:v2.9.1
goharbor/harbor-portal:v2.9.1
goharbor/harbor-db:v2.9.1
[root@docker harbor]# docker rmi $(docker images | grep harbor | awk '{print $1":"$2}')
清理harbor使用的目录/data,由prepare脚本定义
[root@docker harbor]# rm -rf /data/
删除软件包
[root@docker harbor]# cd
[root@docker ~]# rm -rf harbor-offline-installer-v2.9.1.tgz harbor/
镜像打包文件-save-load
保存本地镜像为文件-save
镜像存储在本地/var/lib/docker/overlay2,通过文件系统层面拷贝image,操作复杂。可以使用save命令,将本地镜像保存为单个文件,并分享给他人使用。
[root@docker ~]# docker info | grep 'Storage Driver'
Storage Driver: overlay2
# 先拉取一个httpd镜像
[root@docker ~]# docker pull httpd
[root@docker ~]# docker save httpd -o httpd.tar
[root@docker ~]# ls
httpd.tar
# 也可以一次性打包多个镜像 [root@docker ~]# docker pull nginx [root@docker ~]# docker save httpd nginx -o images.tar [root@docker ~]# ls httpd.tar images.tar
将本地镜像文件导入本地-load
先将本地镜像删除,并导入本地镜像文件
[root@docker ~]# docker rmi -f httpd nginx
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
[root@docker ~]# docker load -i images.tar
244c81761e3a: Loading layer [==================================================>] 2.56kB/2.56kB
5f70bf18a086: Loading layer [==================================================>] 1.024kB/1.024kB
5030a675e442: Loading layer [==================================================>] 6.02MB/6.02MB
5956e1b11f36: Loading layer [==================================================>] 33.11MB/33.11MB
b8382e28b2d3: Loading layer [==================================================>] 3.584kB/3.584kB
Loaded image: httpd:latest
008ba900efa1: Loading layer [==================================================>] 74.41MB/74.41MB
1e79db1a7c1e: Loading layer [==================================================>] 3.584kB/3.584kB
fe0771a36433: Loading layer [==================================================>] 4.608kB/4.608kB
5f0d4d15245b: Loading layer [==================================================>] 2.56kB/2.56kB
388bb4cadb9e: Loading layer [==================================================>] 5.12kB/5.12kB
38d44e06fd01: Loading layer [==================================================>] 7.168kB/7.168kB
Loaded image: nginx:latest
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
httpd latest 9e0cccc06e8d 2 days ago 117MB
nginx latest 60adc2e137e7 2 weeks ago 152MB
说明:如果本地镜像名与导入的镜像重名,则本地的镜像会被覆盖。
[root@docker ~]# docker rm -f $(docker ps -aq) #删除所有容器 [root@docker ~]# docker rmi -f $(docker images -aq) #删除所有镜像
总结
下面是镜像的常用操作子命令:
images 显示镜像列表
history 显示镜像构建历史
commit 从容器创建新镜像
build 从 Dockerfile 构建镜像
tag 给镜像打 tag
pull 从 registry 下载镜像
push 将 镜像 上传到 registry
rmi 删除 Docker host 中的镜像
search 搜索 Docker Hub 中的镜像
11万+

被折叠的 条评论
为什么被折叠?



