【Docker(四)】Dockerfile命令参数详解

【Docker(四)】Dockerfile命令参数详解

【Docker(三)】通过Dockerfile定制镜像中,描述了镜像的分层构成逻辑,如何通过docker commit和Dockerfile定制镜像;以及docker build命令的基本用法。

本文接着对 Dockerfile 进行探究,学习她的命令及参数;大概看一下命令的概览,了解下,后面会进行详细介绍。

【Docker(四)】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指令之后,来为新的构建阶段指定一个名称。该名称可以在后续的FROMCOPY ——FROM =<name>指令中使用,以引用在此阶段构建的映像。
  • tagdigest值是可选的。如果省略其中任何一个,构造器默认采用latest标记。如果不能找到tag值,构造器将返回一个错误。
  • --platform标志可用于在FROM引用多平台镜像的情况下指定镜像的平台。如:linux/amd64, linux/arm64, or windows/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 指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。

因此在 COPYADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 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>" 到底是什么意思,有什么好处?

举个例子

上一篇:Docker镜像的基本知识


下一篇:Docker入门系列之三:十二个Dockerfile指令