docker commit

docker commit 命令是创建新镜像最直接的方式,其过程包括:运行容器,修改容器,将容器保存为新的镜像。

举例

在 Ubuntu base 镜像中安装 vi 并保存为新镜像。

  1. docker run -it ubuntu
  2. //-it 以交互模式进入容器,并打开终端。
  3. apt-get install -y vim
  4. //安装 vim
  5. //可能会提示 E: Unable to locate package vim
  6. //输入 apt-get update
  7. //该命令会访问源列表中的每个网址,并读取软件列表,然后保存在本地。
  8. docker ps
  9. //查看容器名 keen_williamson
  10. docker commit keen_williamson ubuntu-with-vi
  11. //新的镜像命名为 ubuntu-with-vi
  12. docker images
  13. //可以看到新的名为 ubuntu-with-vi 的镜像,且相较于 ubuntu 镜像,此镜像的内存占用更多
  14. //ubuntu-with-vi latest d2041646583c 5 seconds ago 176MB
  15. //ubuntu latest ff0fea8310f3 9 days ago 72.8MB
  16. docker run -it ubuntu-with-vi
  17. vim
  18. //从新的镜像启动容器,验证 vim 已经可以使用

注:过程中要注意不要选错容器 name。

Docker 官方不建议这种方式构建镜像:

(1)这是一种手工创建镜像的方式,容易出错,效率低且可重复性弱。比如要在debian base 镜像中也加入 vi,还得重复前面的所有步骤。

(2)使用者并不知道镜像是如何创建出来的,里面是否有恶意程序。也就是说无法对镜像进行审计,存在安全隐患。

Docker 推荐的 dockerfile 构建镜像的方式,底层也是 docker commit 一层一层的创建新的镜像,学习 docker commit 有助于更加深入的理解构建过程和镜像的分层结构。

Dockerfile

实例

使用 Dockerfile 创建同样的 ubuntu-with-vi:

  1. cd /learndocker/unit3
  2. vim Dockerfile
  3. FROM ubuntu
  4. RUN apt-get update && apt-get install -y vim
  5. //保存并退出
  6. ls
  7. Dockerfile
  8. docker build -t ubuntu-with-vi-dockerfile .
  9. //-t 将新的镜像命名为 ubuntu-with-vi-dockerfile,命令末尾的 . 指明 build context 为
  10. //当前目录,Dockerfile 默认会从 build context 中查找 Dockerfile 文件,也可以通过 -f 参数指定文件位置
  11. //执行 RUN 安装 vim 时,首先以 ubuntu 为基础镜像,启动临时容器,在该容器中安装
  12. //vim 安装成功后,将容器保存为镜像,这一步使用的是类似于 docker commit 的命令,删除临时容器
  13. 执行 docker images 查看多了镜像
  14. ubuntu-with-vi-dockerfile latest dc38c2287ea0 4 minutes ago 176MB
  15. //镜像构建成功,其 size 大小与 ubuntu-with-vi 一样,且效果一致。

查看镜像分层结构

ubuntu-with-vi-dockerfile 是通过在 base 镜像的顶部添加一个新的镜像层得到的,这个新的镜像由 RUN apt-get update && apt-getinstall -y vim 生成,可以通过 docker history 命令查看。

root@Wangying:/learndocker/unit3# docker history ubuntu-with-vi-dockerfile
IMAGE          CREATED       CREATED BY                                      SIZE      COMMENT
dc38c2287ea0   2 hours ago   RUN /bin/sh -c apt-get update && apt-get ins…   103MB     buildkit.dockerfile.v0
<missing>      9 days ago    /bin/sh -c #(nop)  CMD ["bash"]                 0B
<missing>      9 days ago    /bin/sh -c #(nop) ADD file:1d3b09cf9e041d608…   72.8MB

镜像的缓存特征

Docker 会缓存已有镜像的镜像层,构建新镜像时,如果某镜像层已经存在,就直接使用,无须重新创建。

cd /learndocker/unit3
vim testfile
ls
Dockerfile testfile
//同目录下,创建一个文件 testfile,写入部分内容

//进入 Dockerfile 在最后一行加上 COPY testfile /
//由于之前已经运行过相同的 RUN 指令,这次直接使用了缓存中的镜像层,如果不想使用缓存,
//可以在 docker build 命令中加上 --no-cache 参数。
//执行 COPY 指令,启动临时容器,复制 testfile 提交新的镜像层,删除临时容器

image.png

Dockerfile 中的每一个指令创建以一个镜像层,上层是依赖于下层的,无论什么时候,只要某一层发生变化,其上面所有层的缓存都会失效。

FROM ubuntu
COPY testfile /
RUN apt-get update && apt-get vim
将 Dockerfile 修改,COPY 位置改变,最后生成的镜像虽然没有变化,但是由于分层的结构特征,Docker 必须重建受影响的镜像层。

调试 Dockerfile

Dockerfile 构建镜像的过程

(1) 从 base 镜像运行一个容器。

(2)执行一条指令,对容器做修改

(3)执行 docker commit 操作,生成一个新的镜像。

(4)Docker 基于刚刚提交的镜像运行一个新的容器。

(5)重复 2 - 4 步,直到 Dockerfile 中的所有指令执行完毕。

可见如果 Dockerfile 由于某钟原因执行到某个指令失败了,也能够得到前一个指令成功执行构建出来的镜像,有助于调试 Dockerfile,以运行最新的这个镜像定位指令失败的原因。

Dockerfile 常用指令

FROM                    指定基础镜像
MAINTAINER         设置镜像作者,任意字符串
COPY                     将文件从 build context 复制到镜像,注意 src 只能指定 build context 中的内容
ADD                      与 COPY 类似,不同点是如果 src 是归档文件(tar,zip,tgz,xz)等,文件会被自动解压到 dest。
ENV                        设置环境变量,环境变量可被后边的指令使用。
EXPOSE                指定容器中的进程监听某个端口,Docker 可以暴露其端口。
VOLUME                将文件或目录声明为 volume,有关容器存储。
WORKDIR             为后面的 RUN,CMD,ENTRYPOINT,ADD,COPY 指令设置镜像中的当前工作目录,即进入容器之后的目录。
RUN                     在容器中运行指定的命令。
CMD                        容器启动时运行指定的命令,Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效,CMD 可以被 docker run 之后的参数替换。
ENTRYPOINT        设置容器启动时运行的命令,同 CMD,有多个时只有最后一个生效,CMD 或 docker run 之后的参数会被当做参数传递给 ENTRYPOINT

举例

FROM busybox
MAINTAINER Wangying
WORKDIR /testdir
RUN touch tmpfile1
COPY tmpfile2 .              //注意这里的 . 代表着前边定义的 WORKDIR
ADD bunch.tar.gz .
ENV WELCOME "You are in my container, welcome"

(1)构建前确保 build context 中存在需要的文件。

(2)WORKDIR 中保存了我们希望的文件和目录。

(3)进入容器,当前目录即为设置的 WORKDIR。如果 WORKDIR 不存在,Docker 会自动创建。

(4)COPY 和 ADD 的使用区别,ADD 指令复制归档文件自动解压。

RUN && COM && ENTRYPOINT

RUN                        执行命令并创建新的镜像层,常用于安装软件包
CMD                        设置容器启动后默认执行的命令及参数,但 CMD 能够被 docker run 之后的命令行参数替换。
ENTRYPOINT        配置容器启动时运行的命令。

Shell 和 Exec 格式

<instruction> <command>
例如:
RUN apt-get install golang
CMD echo "hello world"
ENTRYPOINT echo "hello world"
当执行指令时,shell 格式会调用 /bin/sh -c [commmand] 

FROM my-image 已有镜像,必写
ENV  name Wangying
ENV  word hello
ENTRYPOINT echo "hello , $name , $word"
#docker build -t en .
#docker run -it en 
#hello , Wangying , hello
<instruction> ["executable","param1","param2",...]
例如
RUN ["apt-get", "install", "golang"]
CMD ["/bin/echo", "Hello world"]
ENTRYPOINT ["/bin/echo", "Hello world"]
当指令执行时,会直接调用 [command] 不会被 shell 解析。

From my-image
ENV name Wangying
ENTRYPOINT ["/bin/echo", "Hello $name"]
#Hello $name

From my-image
ENV name Wangying
ENTRYPOINT ["/bin/sh", "-c", "echo Hello $name"]
#Hello $name

RUN

该指令常用于安装应用和软件包,RUN 在当前镜像的顶部执行命令,并创建新的镜像层,Dockerfile 中常包含多个 RUN 指令。
Shell 格式:RUN
Exec 格式:RUN["executable", "param1", "param2"]
例如:
RUN apt-get update && apt-get install -y \bzr\cvs\git\vim
#-y 给所有安装时的询问直接 yes
注意:apt-get update 和 install 放在一个 RUN 中执行,能够保证每次安装的都是最新的包,如果 apt-get install 在单独的 RUN 中运行,则会使用 apt-get update 创建镜像层,而这一层可能时很久以前缓存的。(见上文:镜像的缓存特征)

CMD

指令允许用户指定容器的默认执行的命令,该命令会在容器启动 docker run 没有指定其他命令时运行。多个时只最后一个有效。
三种格式:
Exec 格式:CMD ["executable", "param1", "param2"] --推荐
CMD ["param1", "param2"] 为 ENTRYPOINT 提供额外的参数,此时 ENTRYPOINT 必须使用 Exec 格式,用途时为 ENTRYPOINT 设置默认参数。
Shell 格式:CMD command param1 param2

示例:
FROM my-image
CMD echo "hello world"
#docker build -t c .
docker run -it c
#hello world
docker run -it [image] /bin/bash
#CMD 被忽略掉,命令 bash 将被执行

ENTRYPOINT

指令可以让容器以应用程序或者服务的形式运行。
ENTRYPOINT 和 CMD 都可以执行指定的命令以及其参数,不同点在于 ENTRYPOINT 不会被忽略,一定会被执行,即使 docker run 指定了其他命令。
两种格式:
Exec 格式:ENTRYPOINT ["executable", "param1","param2"] --推荐
Shell 格式:ENTRYPOINT command param1param2
两种格式差别很大,使用要小心。
用于设置要执行的命令及其参数,同时可以通过 CMD 提供额外的参数。
ENTRYPOINT 中的参数始终会被使用,而 CMD 提供的参数可以在容器启动时动态替换掉。
示例:
ENTRYPOINT ["/bin/echo", "Hello"]
CMD ["world"]
#docker run -it [image]
#Hello world 
#docker run -it [image] param1 param2
#Hello param1 param2
ENTRYPOINT 的 Shell 格式会忽略任何 CMD 或 docker run 提供的参数。

小结

(1)使用 RUN 指令安装应用和软件包,构建镜像。

(2)如果 Docker 镜像的用途是运行应用或服务,应该使用 ENTRYPOINT 指令,CMD 可为 ENTRYPOINT 提供参数,同时可以利用 docker run 命令替换默认参数。

(3)为容器设置默认的启动命令,可使用 CMD 指令,用户可以在 docker run 命令中修改此默认命令。