在【Docker(三)】通过Dockerfile定制镜像中,描述了镜像的分层构成逻辑,如何通过docker commit
和Dockerfile定制镜像;以及docker build
命令的基本用法。
本文接着对 Dockerfile 进行探究,学习她的命令及参数;大概看一下命令的概览,了解下,后面会进行详细介绍。
Dockerfile格式
下面是 Dockerfile 的格式:
# Comment 注释
INSTRUCTION arguments
- INSTRUCTION :指令,不区分大小写。但约定使用大写形式,以便更容易地与参数区分开来。
- arguments:命令行参数
- Comment:以井号开头的注释
FROM 基础镜像
FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
FROM 指令初始化一个新的构建阶段,并为后续指令设置基础镜像,该镜像可以是任何有效的镜像。举个例子:
FROM centos:7
...
需要注意的是:
- 一般来说,Dockerfile都是以
FROM
指令开始;但ARG
是Dockerfile中唯一可能先于FROM
的指令(具体参考下面的ARG介绍)。 -
FROM
可以在一个Dockerfile中出现多次,以创建多个映像或使用一个构建阶段作为另一个构建阶段的依赖项。只需在每个新的FROM
指令之前记录提交的最后一个图像ID输出。每个FROM
指令清除前面指令创建的任何状态。 -
[AS <name>]
参数可以添加在FROM
指令之后,来为新的构建阶段指定一个名称。该名称可以在后续的FROM
和COPY ——FROM =<name>
指令中使用,以引用在此阶段构建的映像。 -
tag
或digest
值是可选的。如果省略其中任何一个,构造器默认采用latest
标记。如果不能找到tag
值,构造器将返回一个错误。 -
--platform
标志可用于在FROM
引用多平台镜像的情况下指定镜像的平台。如:linux/amd64
,linux/arm64
, orwindows/amd64
- 除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为
scratch
。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。
MAINTAINER (废弃) 设置Author
MAINTAINER <name>
MAINTAINER
指令设置生成的镜像的Author字段,已经废弃使用。LABEL
指令是一个更灵活的版本,你、应该使用它,因为它可以设置所需要的任何元数据,并且可以很容易地通过docker inspect
查看。可以使用与MAINTAINER
字段相对应的标签:
LABEL org.opencontainers.image.authors="SvenDowideit@home.org.au"
在下面将会介绍LABEL
指令。
RUN 执行命令
RUN
指令将在当前镜像上的新层中执行任何命令并提交结果。生成的提交镜像将用于Dockerfile中的下一步。其格式有两种:
-
shell 格式:
RUN <命令>
,就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的RUN
指令就是这种格式。 -
exec 格式:
RUN ["可执行文件", "参数1", "参数2"]
,这更像是函数调用中的格式。
需要注意的是,Dockerfile 中每一个指令都会建立一层,RUN
也不例外。每一个 RUN
的行为,就会新建立一层,在其上执行这些命令,执行结束后,commit
这一层的修改,构成新的镜像。
在shell形式中,可以使用(反斜杠)来将单个RUN指令继续到下一行。例如这两行:
RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'
等价于
RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
COPY 复制文件
COPY [--chown=<user>:<group>] <源路径>... <目标路径>
COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]
和 RUN
指令一样,也有两种格式,一种类似于命令行,一种类似于函数调用。COPY
指令将从构建上下文目录中 <源路径>
的文件/目录复制到新的一层的镜像内的 <目标路径>
位置。比如:
COPY package.json /usr/src/app/
<源路径>
可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 filepath.Match
规则,如:
COPY hom* /mydir/
COPY hom?.txt /mydir/
<目标路径>
可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR
指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。
--chown=<user>:<group>
选项用来改变文件的所属用户及所属组。
COPY --chown=55:mygroup files* /mydir/
COPY --chown=bin files* /mydir/
COPY --chown=1 files* /mydir/
COPY --chown=10:11 files* /mydir/
注意:如果源路径为文件夹,复制的时候不是直接复制该文件夹,而是将文件夹中的内容复制到目标路径。
ADD 更高级的复制文件
ADD
指令和 COPY
的格式和性质基本一致。但是在 COPY
基础上增加了一些功能。
比如 <源路径>
可以是一个 URL
,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 <目标路径>
去。下载后的文件权限自动设置为 600
,如果这并不是想要的权限,那么还需要增加额外的一层 RUN
进行权限调整,另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层 RUN
指令进行解压缩。所以不如直接使用 RUN
指令,然后使用 wget
或者 curl
工具下载,处理权限、解压缩、然后清理无用文件更合理。因此,这个功能其实并不实用,而且不推荐使用。
如果 <源路径>
为一个 tar
压缩文件的话,压缩格式为 gzip
, bzip2
以及 xz
的情况下,ADD
指令将会自动解压缩这个压缩文件到 <目标路径>
去。
在某些情况下,这个自动解压缩的功能非常有用,比如官方镜像 ubuntu
中:
FROM scratch
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /
...
同样,在使用该指令的时候还可以加上 --chown=<user>:<group>
选项来改变文件的所属用户及所属组。
COPY
vs ADD
:
在 Docker 官方的 Dockerfile 最佳实践文档 中要求,尽可能的使用 COPY
,因为 COPY
的语义很明确,就是复制文件而已,而 ADD
则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD
的场合,就是所提及的需要自动解压缩的场合。
另外需要注意的是,ADD
指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。
因此在 COPY
和 ADD
指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY
指令,仅在需要自动解压缩的场合使用 ADD
。
CMD 容器启动命令
The CMD
指令的三种格式:
CMD ["executable","param1","param2"] (exec 格式, 首选推荐)
CMD ["param1","param2"] (在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数,下面介绍ENTRYPOINT指令)
CMD command param1 param2 (shell 格式)
Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD
指令就是用于指定默认的容器主进程的启动命令的。
在运行时可以指定新的命令来替代镜像设置中的这个默认命令,比如,ubuntu
镜像默认的 CMD
是 /bin/bash
,如果我们直接 docker run -it ubuntu
的话,会直接进入 bash
。我们也可以在运行时指定运行别的命令,如 docker run -it ubuntu cat /etc/os-release
。这就是用 cat /etc/os-release
命令替换了默认的 /bin/bash
命令了,输出了系统版本信息。
在指令格式上,一般推荐使用 exec
格式,如果使用 shell
格式的话,实际的命令会被包装为 sh -c
的参数的形式进行执行。比如:
CMD echo $HOME
在实际执行中,会将其变更为:
CMD [ "sh", "-c", "echo $HOME" ]
注意1:容器中应用应在前台执行;原因:Docker 不是虚拟机,容器就是进程。如下面Nginx容器启动的命令。
无效的命令(容器执行后就立即退出,原因容器主进程结束):
CMD service nginx start
有效命令(前台方式运行,不结束主进程):
CMD ["nginx", "-g", "daemon off;"]
注意2:一个Dockerfile应该仅有一个CMD
指令,当存在多个,仅最后一个生效(以最后一个为准)。
ENTRYPOINT 入口点
ENTRYPOINT
的格式和 RUN
指令格式一样,分为 exec
格式和 shell
格式。
ENTRYPOINT
的目的和 CMD
一样,都是在指定容器启动程序及参数。ENTRYPOINT
在运行时也可以替代,不过比 CMD
要略显繁琐,需要通过 docker run
的参数 --entrypoint
来指定。
当指定了 ENTRYPOINT
后,CMD
的含义就发生了改变,不再是直接的运行其命令,而是将 CMD
的内容作为参数传给 ENTRYPOINT
指令,换句话说实际执行时,将变为:
<ENTRYPOINT> "<CMD>"
那么有了 CMD
后,为什么还要有 ENTRYPOINT
呢?这种 <ENTRYPOINT> "<CMD>"
到底是什么意思,有什么好处?
举个例子