Docker镜像与仓库(二)
Dockerfile指令(上)
指令格式
在Dockerfile中,注释以“#”开头,后面接上注释的内容,指令以大写的指令名开头,后面跟着指令的参数。
以前面用到的dockerfile为例,它包含第一行的一条注释,以及后面的五条指令:
schen@scvmu01:~/dockerfile/build_test$ cat Dockerfile
# First Dockerfile
FROM ubuntu:16.04
MAINTAINER Shichen "shichen@website.com"
RUN apt-get update
RUN apt-get install -y nginx
EXPOSE 80
schen@scvmu01:~/dockerfile/build_test$
这五条指令中,包含了FROM
、MAINTAINER
、RUN
和EXPOSE
这四种最常用的指令。
FROM
FROM
指令指定了基础镜像,它有两种格式:
- FROM <image>
- FROM <image>:<tag>
其中“image”必须是一个已经存在的镜像,后续指令都会基于这个镜像运行,因此它也被叫做“基础镜像”,而FROM
指令也必须是dockerfile中第一条非注释的指令。
MAINTAINER
MAINTAINER
指令通常包含了镜像的维护者及其联系方式,这与docker commit
命令中的-a
参数有着相同的作用。
RUN
RUN
指令用来指定构建镜像所需的命令,它有两种格式:
- RUN <command param1 param2>
(shell模式)
- RUN ["executable", "param1", "param2"]
(exec模式)
在“shell模式”下,RUN
指令是以/bin/sh -c command
的方式运行命令的;而在“exec模式”下,RUN
指令可以用指定的shell来运行指令。它们的区别就像是:/bin/sh -c echo hello
和RUN["/bin/bash", "-c", "echo hello"]
。
这个Dockerfile中包含了两条RUN
指令,我们知道镜像有分层的概念,每一条RUN
指令都会在当前镜像之上创建一个容器来运行指定的命令,然后再生成一个新的镜像。所以,当我们把这两条RUN
指令合并成为一条后,镜像构建过程中原有的两步构建就会合并成为一步,而中间层镜像也会少生成一个:
schen@scvmu01:~/dockerfile/build_test$ cat Dockerfile
# First Dockerfile
FROM ubuntu:16.04
MAINTAINER Shichen "shichen@website.com"
RUN apt-get update
RUN apt-get install -y nginx
EXPOSE 80
schen@scvmu01:~/dockerfile/build_test$
schen@scvmu01:~/dockerfile/build_test$ docker build -t="shichen/df_test1" .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:16.04
---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
---> Using cache
---> b8051df248d3
Step 3 : RUN apt-get update
---> Using cache
---> ed4c4835e691
Step 4 : RUN apt-get install -y nginx
---> Using cache
---> 989af64bca25
Step 5 : EXPOSE 80
---> Using cache
---> 38555e155ef6
Successfully built 38555e155ef6
schen@scvmu01:~/dockerfile/build_test$
schen@scvmu01:~/dockerfile/build_test$ vi Dockerfile
schen@scvmu01:~/dockerfile/build_test$
schen@scvmu01:~/dockerfile/build_test$ cat Dockerfile
# First Dockerfile
FROM ubuntu:16.04
MAINTAINER Shichen "shichen@website.com"
RUN apt-get update && apt-get install -y nginx
EXPOSE 80
schen@scvmu01:~/dockerfile/build_test$ docker build -t="shichen/df_test2" .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:16.04
---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
---> Using cache
---> b8051df248d3
Step 3 : RUN apt-get update && apt-get install -y nginx
---> Running in 52d52d5775f0
Get:1 http://archive.ubuntu.com/ubuntu xenial InRelease [247 kB]
Get:2 http://archive.ubuntu.com/ubuntu xenial-updates InRelease [95.7 kB]
......
Processing triggers for systemd (229-4ubuntu7) ...
---> 1706a4656c05
Removing intermediate container 52d52d5775f0
Step 4 : EXPOSE 80
---> Running in 7fa0ece5ed40
---> c4efda0154f7
Removing intermediate container 7fa0ece5ed40
Successfully built c4efda0154f7
schen@scvmu01:~/dockerfile/build_test$
从上面新的Dockerfile构建过程中,我们还注意到“Step 2”直接使用了之前构建好的中间层镜像,这就是我们会在后面详细介绍的“构建缓存”技术。
EXPOSE
通过EXPOSE
指令我们可以指定运行该镜像的容器所使用的端口,我们即可以在一条EXPOSE
指令中指定多个端口号,也可以在一个Dockerfile中使用多条EXPOSE
指令。
但要注意的是,虽然我们在镜像的构建过程中指定了容器所使用的端口号,但是我们仍然需要在容器运行时手动地指定端口映射(docker run -p 80
)。也就是说,EXPOSE
指令只是告诉Docker该容器内的应用将会使用哪些端口,但是出于安全考虑,Docker并不会自动打开它们,而是需要我们在docker run
命令中指定对应的端口号。
Dockerfile指令(下)
除了之前讲到的一些常用指令外,Dockerfile还支持一些其他指令来完成更加复杂的功能,它们是:
1. CMD
和ENTRYPOINT
指令用于设置容器运行时所执行的命令;
2. ADD
、COPY
和VOLUME
指令用于设置镜像的目录和文件;
3. WORKDIR
、ENV
和USER
指令用于进行镜像构建和容器运行时的环境设置;
4. ONBUILD
指令是一个类似于触发器的指令;
CMD
CMD
指令用于指定容器默认的启动命令,它有三种格式:
- CMD <command param1 param2>
(shell模式)
- CMD ["executable", "param1", "param2"]
(exec模式)
- CMD ["param1", "param2"]
(作为ENTRYPOINT
指令的默认参数)
与之前讲过的RUN
指令非常相似,它们都是执行一条命令,不同的是RUN
指令提供的命令是在镜像构建过程中执行的,而CMD
指令提供的命令则是在容器运行时执行的。并且,如果我们在运行容器时指定了命令,则CMD
指令提供的命令将被覆盖,不会被执行,也就是说CMD
指令是用来指定容器运行的默认行为。
我们仍然沿用之前的Dockerfile,只是在文件的末尾加上一行CMD
指令,让容器运行时默认启动nginx服务:
schen@scvmu01:~/dockerfile/df_test1$ cat Dockerfile
# First Dockerfile
FROM ubuntu:16.04
MAINTAINER Shichen "shichen@website.com"
RUN apt-get update
RUN apt-get install -y nginx
EXPOSE 80
CMD ["/usr/sbin/nginx", "-g", "daemon off;"]
schen@scvmu01:~/dockerfile/df_test1$
我们使用这个Dockerfile构建一个镜像并运行它:
schen@scvmu01:~/dockerfile/df_test1$ docker build -t="shichen/df_test1" .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:16.04
---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
---> Using cache
---> b8051df248d3
Step 3 : RUN apt-get update
---> Using cache
---> ed4c4835e691
Step 4 : RUN apt-get install -y nginx
---> Using cache
---> 989af64bca25
Step 5 : EXPOSE 80
---> Using cache
---> 38555e155ef6
Step 6 : CMD /usr/sbin/nginx -g daemon off;
---> Running in 939da67cdae7
---> d8b6e8741805
Removing intermediate container 939da67cdae7
Successfully built d8b6e8741805
schen@scvmu01:~/dockerfile/df_test1$
schen@scvmu01:~/dockerfile/df_test1$ docker run -p 80 --name cmd_test1 -d shichen/df_test1
439bd4ca203c040ac306e6bec33f513ca664071ab14d24482fe8dfdcecb61f4f
schen@scvmu01:~/dockerfile/df_test1$
schen@scvmu01:~/dockerfile/df_test1$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
439bd4ca203c shichen/df_test1 "/usr/sbin/nginx -g '" 7 seconds ago Up 4 seconds 0.0.0.0:32771->80/tcp cmd_test1
schen@scvmu01:~/dockerfile/df_test1$
注意我们并没有在docker run
命令的末尾指定启动容器后要执行的命令,这时Docker就会为我们执行CMD
指令提供的命令。因此,当使用docker top
命令查看容器中的进程时,我们可以发现Nginx正在运行。
schen@scvmu01:~/dockerfile/df_test1$ docker top cmd_test1
UID PID PPID C STIME TTY TIME CMD
root 2701 2673 0 20:20 ? 00:00:00 nginx: master process /usr/sbin/nginx -g daemon off;
www-data 2737 2701 0 20:21 ? 00:00:00 nginx: worker process
schen@scvmu01:~/dockerfile/df_test1$
schen@scvmu01:~/dockerfile/df_test1$ curl http://127.0.0.1:32771
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
schen@scvmu01:~/dockerfile/df_test1$
而当我们在docker run
命令中指定容器启动后需要运行的命令时,CMD
指令提供的命令就不会被执行:
schen@scvmu01:~/dockerfile/df_test1$ docker run -p 80 --name cmd_test2 -d shichen/df_test1 sleep 1m
5e0a8a1587f50d6c46613871abce2640d4dc166707043e448c60722d9cebdb15
schen@scvmu01:~/dockerfile/df_test1$
schen@scvmu01:~/dockerfile/df_test1$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5e0a8a1587f5 shichen/df_test1 "sleep 1m" 9 seconds ago Up 5 seconds 0.0.0.0:32772->80/tcp cmd_test2
439bd4ca203c shichen/df_test1 "/usr/sbin/nginx -g '" 45 minutes ago Up 45 minutes 0.0.0.0:32771->80/tcp cmd_test1
schen@scvmu01:~/dockerfile/df_test1$
ENTRYPOINT
ENTRYPOINT
指令用于指定容器默认的启动命令,它有两种格式:
- ENTRYPOINT <command param1 param2>
(shell模式)
- ENTRYPOINT ["executable", "param1", "param2"]
(exec模式)
ENTRYPOINT
指令与我们刚刚介绍的CMD
指令非常类似,唯一的不同就是ENTRYPOINT
指令提供的命令不会被docker run
命令中指定的启动命令所覆盖。如果要覆盖ENTRYPOINT
提供的命令,则需要在docker run
命令中使用--entrypoint
参数。
我们将刚才的Dockerfile稍作改动,用ENTRYPOINT
指令代替最后一行的CMD
指令:
schen@scvmu01:~/dockerfile/df_test2$ cat Dockerfile
# First Dockerfile
FROM ubuntu:16.04
MAINTAINER Shichen "shichen@website.com"
RUN apt-get update
RUN apt-get install -y nginx
EXPOSE 80
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]
schen@scvmu01:~/dockerfile/df_test2$
我们使用这个Dockerfile构建一个镜像并运行它:
schen@scvmu01:~/dockerfile/df_test2$ docker build -t="shichen/df_test2" .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:16.04
---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
---> Using cache
---> b8051df248d3
Step 3 : RUN apt-get update
---> Using cache
---> ed4c4835e691
Step 4 : RUN apt-get install -y nginx
---> Using cache
---> 989af64bca25
Step 5 : EXPOSE 80
---> Using cache
---> 38555e155ef6
Step 6 : ENTRYPOINT /usr/sbin/nginx -g daemon off;
---> Running in 2e2e75998bb0
---> 740efa125bc6
Removing intermediate container 2e2e75998bb0
Successfully built 740efa125bc6
schen@scvmu01:~/dockerfile/df_test2$
schen@scvmu01:~/dockerfile/df_test2$ docker run -p 80 --name ep_test1 -d shichen/df_test2 sleep 1m
459000a92dbef209633b05a5f431ba1c16488ffe38ebcb088b18c018c07eea25
schen@scvmu01:~/dockerfile/df_test2$
schen@scvmu01:~/dockerfile/df_test2$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
schen@scvmu01:~/dockerfile/df_test2$
schen@scvmu01:~/dockerfile/df_test2$ docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
459000a92dbe shichen/df_test2 "/usr/sbin/nginx -g '" 24 seconds ago Exited (1) 19 seconds ago ep_test1
schen@scvmu01:~/dockerfile/df_test2$
schen@scvmu01:~/dockerfile/df_test3$ docker logs ep_test1
nginx: invalid option: "sleep"
schen@scvmu01:~/dockerfile/df_test2$
注意我们在docker run
命令的末尾指定了启动容器后要执行的命令,但容器依旧执行的是ENTRYPOINT
提供的命令。也就是说,我们在docker run
命令中指定的sleep 1m
并没有当成命令被执行,这就是ENTRYPOINT
指令和CMD
指令的区别。
由于ENTRYPOINT
和CMD
各自的特点,我们通常会将它们组合使用,通过ENTRYPOINT
指定命令,再通过CMD
指定默认的参数:
schen@scvmu01:~/dockerfile/df_test3$ cat Dockerfile
# First Dockerfile
FROM ubuntu:16.04
MAINTAINER Shichen "shichen@website.com"
RUN apt-get update
RUN apt-get install -y nginx
EXPOSE 80
ENTRYPOINT ["/usr/sbin/nginx"]
CMD ["-h"]
schen@scvmu01:~/dockerfile/df_test3$
我们使用这个Dockerfile构建一个镜像并运行它:
schen@scvmu01:~/dockerfile/df_test3$ docker build -t="shichen/df_test3" .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:16.04
---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
---> Using cache
---> b8051df248d3
Step 3 : RUN apt-get update
---> Using cache
---> ed4c4835e691
Step 4 : RUN apt-get install -y nginx
---> Using cache
---> 989af64bca25
Step 5 : EXPOSE 80
---> Using cache
---> 38555e155ef6
Step 6 : ENTRYPOINT /usr/sbin/nginx
---> Running in eab87fcdc85f
---> 4cd7f1c9a201
Removing intermediate container eab87fcdc85f
Step 7 : CMD -h
---> Running in 30a75a7afe69
---> 73def694b2a9
Removing intermediate container 30a75a7afe69
Successfully built 73def694b2a9
schen@scvmu01:~/dockerfile/df_test3$
schen@scvmu01:~/dockerfile/df_test3$ docker run -p 80 --name ep_test2 -d shichen/df_test3 -g "daemon off;"
e75917bf7a853f077ad2fdba9046ff60919c54f9b3578d68e3980e091f68f70a
schen@scvmu01:~/dockerfile/df_test3$
schen@scvmu01:~/dockerfile/df_test3$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e75917bf7a85 shichen/df_test3 "/usr/sbin/nginx -g '" 5 seconds ago Up 1 seconds 0.0.0.0:32778->80/tcp ep_test2
schen@scvmu01:~/dockerfile/df_test3$
schen@scvmu01:~/dockerfile/df_test3$ docker top ep_test2
UID PID PPID C STIME TTY TIME CMD
root 4185 4159 1 22:37 ? 00:00:00 nginx: master process /usr/sbin/nginx -g daemon off;
www-data 4204 4185 0 22:37 ? 00:00:00 nginx: worker process
schen@scvmu01:~/dockerfile/df_test3$
我们写在docker run
命令末尾的-g "daemon off;"
覆盖了由CMD
提供的默认参数,传递给了由ENTRYPOINT
指定的/usr/sbin/nginx
命令,容器得以顺利运行。
ADD and COPY
ADD
和COPY
指令的格式完全一致,因此可以简单记为:
- ADD/COPY <src>...<dest>
- `ADD/COPY [“”…””] (适用于文件路径中有空格的情况)
这是两条非常相似的指令,它们都是用来将文件或目录复制到使用Dockerfile构建的镜像当中,并且它们都支持两种参数:来源地址和目标地址。
文件或目录的来源可以是本地地址,也可以是远程URL。如果是本地地址,则必须是构建目录中的相对地址。对于远程URL,Docker并不推荐使用,而是更建议使用curl
或wget
之类的命令来获取文件。而目标地址则需要指定为镜像中的绝对路径。
这两条指令的区别在于ADD
包含类似tar
的解压功能,在安装一些使用tar
压缩的软件包时会有所帮助。而如果只是单纯地复制文件,Docker更推荐使用COPY
指令。
作为演示,我们将之前的Dockerfile稍加改动,在安装完Nginx之后,使用构建目录中的网页替换掉Nginx的默认页面:
schen@scvmu01:~/dockerfile/df_test4$ cat Dockerfile
# First Dockerfile
FROM ubuntu:16.04
MAINTAINER Shichen "shichen@website.com"
RUN apt-get update
RUN apt-get install -y nginx
COPY index.html /var/www/html
EXPOSE 80
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]
schen@scvmu01:~/dockerfile/df_test4$
schen@scvmu01:~/dockerfile/df_test4$ cat index.html
<html>
<head>
<title>Nginx in Docker</title>
</head>
<body>
<h1>Hello, I'm website in df_test4!</h1>
</body>
</html>
schen@scvmu01:~/dockerfile/df_test4$
schen@scvmu01:~/dockerfile/df_test4$ ls
Dockerfile index.html
schen@scvmu01:~/dockerfile/df_test4$
我们使用这个Dockerfile构建一个镜像并运行它:
schen@scvmu01:~/dockerfile/df_test4$ docker build -t="shichen/df_test4" .
Sending build context to Docker daemon 3.072 kB
Step 1 : FROM ubuntu:16.04
---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
---> Using cache
---> b8051df248d3
Step 3 : RUN apt-get update
---> Using cache
---> ed4c4835e691
Step 4 : RUN apt-get install -y nginx
---> Using cache
---> 989af64bca25
Step 5 : COPY index.html /var/www/html
---> a4f2026f3619
Removing intermediate container d187a7639151
Step 6 : EXPOSE 80
---> Running in 1a342bc29284
---> c6175193a094
Removing intermediate container 1a342bc29284
Step 7 : ENTRYPOINT /usr/sbin/nginx -g daemon off;
---> Running in 52e744f99e46
---> 2d71b8743a26
Removing intermediate container 52e744f99e46
Successfully built 2d71b8743a26
schen@scvmu01:~/dockerfile/df_test4$
schen@scvmu01:~/dockerfile/df_test4$ docker run -p 80 --name cp_test -d shichen/df_test4
c9cdb455f3d91552509d685d9a543368cc76ba8ae447be40783567319397893f
schen@scvmu01:~/dockerfile/df_test4$
schen@scvmu01:~/dockerfile/df_test4$ docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c9cdb455f3d9 shichen/df_test4 "/usr/sbin/nginx -g '" 12 seconds ago Up 7 seconds 0.0.0.0:32785->80/tcp cp_test
schen@scvmu01:~/dockerfile/df_test4$
schen@scvmu01:~/dockerfile/df_test4$ curl http://127.0.0.1:32785
<html>
<head>
<title>Nginx in Docker</title>
</head>
<body>
<h1>Hello, I'm website in df_test4!</h1>
</body>
</html>
schen@scvmu01:~/dockerfile/df_test4$
VOLUME
VOLUME
指令用于向镜像创建的容器添加卷,一个卷是可以存在于一个或多个容器的特定目录,这个目录可以绕过联合文件系统,并提供如共享数据或数据持久化等功能。关于卷的概念,我们在后续介绍容器的相关章节中再来做详细说明。这里仅给出其参数的基本格式:
- VOLUME ["/data"]
WORKDIR
WORKDIR
指令用于设置工作目录,其参数格式为:
- WORKDIR /path/to/workdir
这条指令通常用于在容器启动时设置工作目录,从而让CMD
和ENTRYPOINT
指令指定的命令在特定的目录下执行;它还可以用于镜像构建过程中,为后续的指令指定工作目录。
需要注意的是,我们通常会使用全路径作为WORKDIR
指令的参数,如果指定的是相对路径,那么工作路径会一直传递下去,即每次设定工作目录时,都会基于上次的WORKDIR
指令来计算路径。
ENV
ENV
指令用于设定环境变量,其参数格式为:
- ENV <key> <value>
- ENV <key>=<value>
…
与WORKDIR
指令类似,ENV
指令既可以用于容器启动时的环境变量设置,也可以用于镜像构建过程中,为后续的指令设定环境变量。
USER
USER
指令用于设定用户身份,其参数格式为:
- USER daemon
与WORKDIR
指令类似,USER
指令既可以用于容器启动时的用户身份设置,也可以用于镜像构建过程中,为后续的指令指定用户身份。
USER
指令的参数可以是用户名、用户ID、组名、组ID甚至是它们的各种组合。在指定用户之前,Docker会一直使用root作为默认用户。
ONBUILD
ONBUILD
指令用于为镜像添加触发器,其参数格式为:
- ONBUILD [INSTRUCTION]
当一个镜像被其他镜像用作基础镜像时,这个触发器将被执行,触发器所包含的指令将被插入到“其他镜像”的构建过程中。从效果上看,就好像是在“其他镜像”的Dockerfile中,FROM
语句的后面直接嵌入了这个触发器所包含的指令。
继续沿用我们之前的Dockerfile,只是这次我们将COPY
指令放到了ONBUILD
指令中,这时触发器所包含的指令为COPY index.html /var/www/html
:
schen@scvmu01:~/dockerfile/df_test5$ cat Dockerfile
# First Dockerfile
FROM ubuntu:16.04
MAINTAINER Shichen "shichen@website.com"
RUN apt-get update
RUN apt-get install -y nginx
ONBUILD COPY index.html /var/www/html
EXPOSE 80
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]
schen@scvmu01:~/dockerfile/df_test5$
schen@scvmu01:~/dockerfile/df_test5$ cat index.html
<html>
<head>
<title>Nginx in Docker</title>
</head>
<body>
<h1>Hello, I'm website in df_test5!</h1>
</body>
</html>
schen@scvmu01:~/dockerfile/df_test5$
schen@scvmu01:~/dockerfile/df_test5$ ls
Dockerfile index.html
schen@scvmu01:~/dockerfile/df_test5$
我们使用这个Dockerfile构建一个镜像并运行它:
schen@scvmu01:~/dockerfile/df_test5$ docker build -t="shichen/df_test5" .
Sending build context to Docker daemon 3.072 kB
Step 1 : FROM ubuntu:16.04
---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
---> Using cache
---> b8051df248d3
Step 3 : RUN apt-get update
---> Using cache
---> ed4c4835e691
Step 4 : RUN apt-get install -y nginx
---> Using cache
---> 989af64bca25
Step 5 : ONBUILD copy index.html /var/www/html
---> Running in 251ccfb03bc9
---> e15a26b3b376
Removing intermediate container 251ccfb03bc9
Step 6 : EXPOSE 80
---> Running in 8f9b102b1bb5
---> 5faf5a1cbff0
Removing intermediate container 8f9b102b1bb5
Step 7 : ENTRYPOINT /usr/sbin/nginx -g daemon off;
---> Running in d79dcd30cfbd
---> ba60f4a95da8
Removing intermediate container d79dcd30cfbd
Successfully built ba60f4a95da8
schen@scvmu01:~/dockerfile/df_test5$
schen@scvmu01:~/dockerfile/df_test5$ docker run -p 80 --name ob_test1 -d shichen/df_test5
3e4fe100ca4d1f71f7258b6983081b6ab69bcd52836c60680be7ca8f9d856729
schen@scvmu01:~/dockerfile/df_test5$
schen@scvmu01:~/dockerfile/df_test5$ docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3e4fe100ca4d shichen/df_test5 "/usr/sbin/nginx -g '" 7 seconds ago Up 3 seconds 0.0.0.0:32786->80/tcp ob_test1
schen@scvmu01:~/dockerfile/df_test5$
schen@scvmu01:~/dockerfile/df_test5$ curl http://127.0.0.1:32786
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
schen@scvmu01:~/dockerfile/df_test5$
我们注意到镜像构建过程中“Step 5”执行的就是ONBUILD
指令,但从结果来看,触发器所包含的指令并没有被执行。而当我们新建一个Dockerfile并指定当前镜像为我们的基础镜像时,结果就会有所不同:
schen@scvmu01:~/dockerfile/df_test6$ cat Dockerfile
# First Dockerfile
FROM shichen/df_test5
MAINTAINER Shichen "shichen@website.com"
EXPOSE 80
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]
schen@scvmu01:~/dockerfile/df_test6$
schen@scvmu01:~/dockerfile/df_test6$ cat index.html
<html>
<head>
<title>Nginx in Docker</title>
</head>
<body>
<h1>Hello, I'm website in df_test6!</h1>
</body>
</html>
schen@scvmu01:~/dockerfile/df_test6$
schen@scvmu01:~/dockerfile/df_test6$ ls
Dockerfile index.html
schen@scvmu01:~/dockerfile/df_test6$
我们使用这个Dockerfile构建一个镜像并运行它:
schen@scvmu01:~/dockerfile/df_test6$ docker build -t="shichen/df_test6" .
Sending build context to Docker daemon 3.072 kB
Step 1 : FROM shichen/df_test5
# Executing 1 build trigger...
Step 1 : COPY index.html /var/www/html
---> 0aed0a28670b
Removing intermediate container d4f664d78b2d
Step 2 : MAINTAINER Shichen "shichen@website.com"
---> Running in af9301f38a8c
---> 5c84c8bffff7
Removing intermediate container af9301f38a8c
Step 3 : EXPOSE 80
---> Running in 6b79f6587f77
---> a6d2f16d6e32
Removing intermediate container 6b79f6587f77
Step 4 : ENTRYPOINT /usr/sbin/nginx -g daemon off;
---> Running in 3619ea94eee7
---> 047dfbddc117
Removing intermediate container 3619ea94eee7
Successfully built 047dfbddc117
schen@scvmu01:~/dockerfile/df_test6$
schen@scvmu01:~/dockerfile/df_test6$ docker run -p 80 --name ob_test2 -d shichen/df_test6
b6fd0526e9231310f77082dbe55f8c49eac78d22cbf5c62c9ec614ab4eac0c78
schen@scvmu01:~/dockerfile/df_test6$
schen@scvmu01:~/dockerfile/df_test6$ docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b6fd0526e923 shichen/df_test6 "/usr/sbin/nginx -g '" 8 seconds ago Up 5 seconds 0.0.0.0:32787->80/tcp ob_test2
schen@scvmu01:~/dockerfile/df_test6$
schen@scvmu01:~/dockerfile/df_test6$ curl http://127.0.0.1:32787
<html>
<head>
<title>Nginx in Docker</title>
</head>
<body>
<h1>Hello, I'm website in df_test6!</h1>
</body>
</html>
schen@scvmu01:~/dockerfile/df_test6$
我们不难发现,在镜像构建的一开始,紧接着FROM
指令,触发器所包含的那条COPY
指令便被执行了。从最后的结果来看,我们也印证了这个事实。这就是ONBUILD
指令的用武之地,它可以将特定的指令延迟到“子镜像”构建时再去执行。
小结
更多关于Dockerfile指令的说明,请参考官方文档或通过man dockerfile
查看。
Dockerfile构建过程
构建过程
从Dockerfile构建镜像的过程可以分为如下几个步骤:
1. 从基础镜像运行一个容器
2. 执行一条指令,对容器做出修改
3. 执行类似docker commit
的操作,提交一个新的镜像层
4. 再基于刚提交的镜像运行一个新容器
5. 执行Dockerfile中的下一条指令,直到所有指令执行完毕
为了进行演示,我们对之前的Dockerfile进行修改,为其添加了一条ENV
指令:
schen@scvmu01:~/dockerfile/df_test7$ cat Dockerfile
# First Dockerfile
FROM ubuntu:16.04
MAINTAINER Shichen "shichen@website.com"
ENV REFRESH_DATE 2016-10-27
RUN apt-get update
RUN apt-get install -y nginx
COPY index.html /var/www/html
EXPOSE 80
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]
schen@scvmu01:~/dockerfile/df_test7$
我们使用这个Dockerfile构建一个镜像,并注意它的构建过程:
schen@scvmu01:~/dockerfile/df_test7$ docker build -t="shichen/df_test7" .
Sending build context to Docker daemon 3.072 kB
Step 1 : FROM ubuntu:16.04
---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
---> Using cache
---> b8051df248d3
Step 3 : ENV REFRESH_DATE 2016-10-27
---> Running in b2ce8f95e9fb
---> cae2e21fcad8
Removing intermediate container b2ce8f95e9fb
Step 4 : RUN apt-get update
---> Running in 2401276b0a02
Get:1 http://archive.ubuntu.com/ubuntu xenial InRelease [247 kB]
Get:2 http://archive.ubuntu.com/ubuntu xenial-updates InRelease [95.7 kB]
......
Reading package lists...
---> ccf581514525
Removing intermediate container 2401276b0a02
Step 5 : RUN apt-get install -y nginx
---> Running in 8f18912c1e8f
Reading package lists...
Building dependency tree...
......
Processing triggers for systemd (229-4ubuntu7) ...
---> bced4541e253
Removing intermediate container 8f18912c1e8f
Step 6 : COPY index.html /var/www/html
---> 81e30a820046
Removing intermediate container be18c88aa241
Step 7 : EXPOSE 80
---> Running in 70fc7da8c099
---> 54adf020bb9b
Removing intermediate container 70fc7da8c099
Step 8 : ENTRYPOINT /usr/sbin/nginx -g daemon off;
---> Running in 53f62d5e26a4
---> 138a059c7372
Removing intermediate container 53f62d5e26a4
Successfully built 138a059c7372
schen@scvmu01:~/dockerfile/df_test7$
以“Step 5”为例,“Running in”后面的“8f18912c1e8f”为执行apt-get install -y nginx
命令所在的容器ID,它是由“Step 4”生成的中间层镜像“ccf581514525”运行而来的。下面紧接着的是执行命令时的输出,当命令执行完毕后Docker会执行一个类似docker commit
的操作,将改动后的容器提交成为新的镜像,并将其ID“bced4541e253”显示出来。最后,Docker会将执行命令的容器“8f18912c1e8f”删除掉。
如此反复这个过程,直到最后一条Dockerfile中的指令执行完毕,就得到了我们通过Dockerfile构建的镜像。而其中每次在容器中执行命令,都会改变容器的内容,并生成一个中间层镜像。Docker并不会为我们删除这些中间层镜像,这样做的目的有两个:1.保留中间层镜像,方便我们运行镜像以检查命令的执行结果是否符合预期;2.提供镜像的构建缓存,这样就不必每次都重新构建Dockerfile中的所有指令。
例如,在“Step 5”中Docker执行了安装Nginx的指令并为我们生成了中间层镜像“bced4541e253”,当我们需要验证这条指令的执行结果时,就可以方便地启动一个容器来进行调试:
schen@scvmu01:~/dockerfile/df_test7$ docker run -it bced4541e253 /bin/bash
root@b3de1a9b6053:/# whereis nginx
nginx: /usr/sbin/nginx /etc/nginx /usr/share/nginx
root@b3de1a9b6053:/#
我们知道,在Dockerfile的构建过程中,难免会遇到各种错误,而有了中间层镜像,我们就可以逐一排查错误产生的位置。另外,保留中间层镜像还为Docker提供了另一个重要的特性——构建缓存。
构建缓存
由于每一步的构建过程都会将结果提交为镜像,所以Docker的构建过程就可以非常地聪明,它会将之前的镜像看作缓存,这里我们再次对刚才的Dockerfile执行构建命令:
schen@scvmu01:~/dockerfile/df_test7$ docker build -t="shichen/df_test7_2" .
Sending build context to Docker daemon 3.072 kB
Step 1 : FROM ubuntu:16.04
---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
---> Using cache
---> b8051df248d3
Step 3 : ENV REFRESH_DATE 2016-10-27
---> Using cache
---> cae2e21fcad8
Step 4 : RUN apt-get update
---> Using cache
---> ccf581514525
Step 5 : RUN apt-get install -y nginx
---> Using cache
---> bced4541e253
Step 6 : COPY index.html /var/www/html
---> Using cache
---> 81e30a820046
Step 7 : EXPOSE 80
---> Using cache
---> 54adf020bb9b
Step 8 : ENTRYPOINT /usr/sbin/nginx -g daemon off;
---> Using cache
---> 138a059c7372
Successfully built 138a059c7372
schen@scvmu01:~/dockerfile/df_test7$
schen@scvmu01:~/dockerfile/df_test7$ docker images shichen/df_test7*
REPOSITORY TAG IMAGE ID CREATED SIZE
shichen/df_test7_2 latest 138a059c7372 About an hour ago 222.6 MB
shichen/df_test7 latest 138a059c7372 About an hour ago 222.6 MB
schen@scvmu01:~/dockerfile/df_test7$
我们没有经历像第一次构建时那样缓慢的过程,而且每一步构建时都有“Using cache”字样,也就是使用了构建缓存,这就使我们的构建过程十分高效。
但是,有些时候我们并不想使用构建缓存,比如构建指令中包含apt-get update
这样的命令时,我们希望每次Docker都会去刷新apt包的缓存,这样就能使用最新版本的软件。要想跳过构建缓存,我们可以使用docker build --no-cache
的方式构建镜像:
schen@scvmu01:~/dockerfile/df_test7$ docker build --no-cache -t="shichen/df_test7_3" .
Sending build context to Docker daemon 3.072 kB
Step 1 : FROM ubuntu:16.04
---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
---> Running in b1c2946903c1
---> 494ce9041153
Removing intermediate container b1c2946903c1
Step 3 : ENV REFRESH_DATE 2016-10-27
---> Running in d8f5313941dc
---> d928c21480b8
Removing intermediate container d8f5313941dc
Step 4 : RUN apt-get update
---> Running in a4003d11e3ba
Get:1 http://archive.ubuntu.com/ubuntu xenial InRelease [247 kB]
Get:2 http://archive.ubuntu.com/ubuntu xenial-updates InRelease [95.7 kB]
......
Reading package lists...
---> 17bee3d7a390
Removing intermediate container a4003d11e3ba
Step 5 : RUN apt-get install -y nginx
---> Running in 5de4d1774ff1
Reading package lists...
Building dependency tree...
......
Processing triggers for systemd (229-4ubuntu7) ...
---> 18167e6cba98
Removing intermediate container 5de4d1774ff1
Step 6 : COPY index.html /var/www/html
---> 780b18cee97a
Removing intermediate container 94004594e37d
Step 7 : EXPOSE 80
---> Running in 52541b20eb29
---> 0b4fb2b14e70
Removing intermediate container 52541b20eb29
Step 8 : ENTRYPOINT /usr/sbin/nginx -g daemon off;
---> Running in 1604eb009770
---> b4718b916d77
Removing intermediate container 1604eb009770
Successfully built b4718b916d77
schen@scvmu01:~/dockerfile/df_test7$
schen@scvmu01:~/dockerfile/df_test7$ docker images shichen/df_test7*
REPOSITORY TAG IMAGE ID CREATED SIZE
shichen/df_test7_3 latest b4718b916d77 12 minutes ago 222.6 MB
shichen/df_test7_2 latest 138a059c7372 2 hours ago 222.6 MB
shichen/df_test7 latest 138a059c7372 2 hours ago 222.6 MB
schen@scvmu01:~/dockerfile/df_test7$
我们发现整个构建过程都没有使用缓存。
除了使用--no-cache
选项,我们还有一个技巧可以用来刷新缓存。在刚才的Dockerfile中,我们使用ENV
指令设置了一个环境变量,现在我们只需要更新这个环境变量的值,就可以为后续的指令刷新缓存:
schen@scvmu01:~/dockerfile/df_test7$ docker build -t="shichen/df_test7_4" .
Sending build context to Docker daemon 3.072 kB
Step 1 : FROM ubuntu:16.04
---> bd3d4369aebc
Step 2 : MAINTAINER Shichen "shichen@website.com"
---> Using cache
---> 494ce9041153
Step 3 : ENV REFRESH_DATE 2016-10-28
---> Running in d76582ccdf5b
---> b753392cdab6
Removing intermediate container d76582ccdf5b
Step 4 : RUN apt-get update
---> Running in 3466d109a816
Get:1 http://archive.ubuntu.com/ubuntu xenial InRelease [247 kB]
Get:2 http://archive.ubuntu.com/ubuntu xenial-updates InRelease [95.7 kB]
......
Reading package lists...
---> fd29f631b5f6
Removing intermediate container 3466d109a816
Step 5 : RUN apt-get install -y nginx
---> Running in 4290bc5c06ad
Reading package lists...
Building dependency tree...
......
Processing triggers for systemd (229-4ubuntu7) ...
---> 26e16a96f797
Removing intermediate container 4290bc5c06ad
Step 6 : COPY index.html /var/www/html
---> 58616a333072
Removing intermediate container 47a4bf55d694
Step 7 : EXPOSE 80
---> Running in 4e95ddfc9560
---> c027d8c2c946
Removing intermediate container 4e95ddfc9560
Step 8 : ENTRYPOINT /usr/sbin/nginx -g daemon off;
---> Running in 499cc2e11c4c
---> 358324ff3288
Removing intermediate container 499cc2e11c4c
Successfully built 358324ff3288
schen@scvmu01:~/dockerfile/df_test7$
schen@scvmu01:~/dockerfile/df_test7$ docker images shichen/df_test7*
REPOSITORY TAG IMAGE ID CREATED SIZE
shichen/df_test7_4 latest 358324ff3288 8 hours ago 222.6 MB
shichen/df_test7_3 latest b4718b916d77 8 hours ago 222.6 MB
shichen/df_test7_2 latest 138a059c7372 10 hours ago 222.6 MB
shichen/df_test7 latest 138a059c7372 10 hours ago 222.6 MB
schen@scvmu01:~/dockerfile/df_test7$
查看镜像的构建过程
通过docker history
命令可以查看一个镜像的构建过程,它的命令格式为:
docker history [OPTIONS] IMAGE
schen@scvmu01:~/dockerfile/df_test7$ docker history shichen/df_test7
IMAGE CREATED CREATED BY SIZE COMMENT
138a059c7372 11 hours ago /bin/sh -c #(nop) ENTRYPOINT ["/usr/sbin/ngi 0 B
54adf020bb9b 11 hours ago /bin/sh -c #(nop) EXPOSE 80/tcp 0 B
81e30a820046 11 hours ago /bin/sh -c #(nop) COPY file:f5ebd8ab6dfdea8bd 181 B
bced4541e253 11 hours ago /bin/sh -c apt-get install -y nginx 56.81 MB
ccf581514525 11 hours ago /bin/sh -c apt-get update 39.19 MB
cae2e21fcad8 11 hours ago /bin/sh -c #(nop) ENV REFRESH_DATE=2016-10-2 0 B
b8051df248d3 7 weeks ago /bin/sh -c #(nop) MAINTAINER Shichen "shiche 0 B
bd3d4369aebc 9 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0 B
<missing> 9 weeks ago /bin/sh -c mkdir -p /run/systemd && echo 'doc 7 B
<missing> 9 weeks ago /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$/ 1.895 kB
<missing> 9 weeks ago /bin/sh -c rm -rf /var/lib/apt/lists/* 0 B
<missing> 9 weeks ago /bin/sh -c set -xe && echo '#!/bin/sh' > /u 745 B
<missing> 9 weeks ago /bin/sh -c #(nop) ADD file:902bff94e00fb3d9eb 126.6 MB
schen@scvmu01:~/dockerfile/df_test7$