Dockerfile reference

预计阅读时间:82 分钟

Docker 可以通过读取Dockerfile中的指令来自动构建图像. Dockerfile是一个文本文档,其中包含用户可以在命令行上调用以组装图像的所有命令. 使用docker build用户可以创建一个连续执行多个命令行指令的自动构建.

此页面描述了您可以在Dockerfile中使用的命令. 阅读完本页后,请参阅Dockerfile最佳实践以获取面向技巧的指南.

Usage

docker build命令从Dockerfilecontext构建图像. 构建的上下文是指定位置PATHURL的文件集. PATH是本地文件系统上的目录. URL是 Git 存储库位置.

构建上下文是递归处理的. 因此, PATH包含所有子目录,而URL包含存储库及其子模块. 此示例显示了使用当前目录 ( . ) 作为构建上下文的构建命令:

$ docker build .

Sending build context to Docker daemon  6.51 MB
...

构建由 Docker 守护程序运行,而不是由 CLI 运行. 构建过程所做的第一件事是将整个上下文(递归)发送到守护进程. 在大多数情况下,最好从一个空目录作为上下文开始,并将 Dockerfile 保存在该目录中. 仅添加构建 Dockerfile 所需的文件.

Warning

不要将根目录/用作构建上下文的PATH ,因为它会导致构建将硬盘驱动器的全部内容传输到 Docker 守护程序.

要在构建上下文中使用文件, Dockerfile是指在指令中指定的文件,例如COPY指令. 要提高构建的性能,请通过将.dockerignore文件添加到上下文目录来排除文件和目录. 有关如何创建.dockerignore文件的信息,请参阅此页面上的文档.

传统上, Dockerfile称为Dockerfile ,位于上下文的根目录中. 您可以在 docker docker build中使用-f标志来指向文件系统中任何位置的 Dockerfile.

$ docker build -f /path/to/a/Dockerfile .

如果构建成功,您可以指定保存新图像的存储库和标记:

$ docker build -t shykes/myapp .

要在构建后将映像标记到多个存储库中,请在运行build命令时添加多个-t参数:

$ docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .

在 Docker 守护进程运行Dockerfile中的指令之前,它会对Dockerfile进行初步验证,如果语法不正确则返回错误:

$ docker build -t test/myapp .

[+] Building 0.3s (2/2) FINISHED
 => [internal] load build definition from Dockerfile                       0.1s
 => => transferring dockerfile: 60B                                        0.0s
 => [internal] load .dockerignore                                          0.1s
 => => transferring context: 2B                                            0.0s
error: failed to solve: rpc error: code = Unknown desc = failed to solve with frontend dockerfile.v0: failed to create LLB definition:
dockerfile parse error line 2: unknown instruction: RUNCMD

Docker 守护进程一个接一个地运行Dockerfile中的指令,如有必要,将每条指令的结果提交到新镜像,最后输出新镜像的 ID. Docker 守护进程将自动清理您发送的上下文.

请注意,每条指令都是独立运行的,并会创建一个新图像 - 因此RUN cd /tmp不会对下一条指令产生任何影响.

只要有可能,Docker 就会使用构建缓存来显着加速docker build过程. 这由控制台输出中的CACHED消息指示. (有关更多信息,请参阅Dockerfile最佳实践指南):

$ docker build -t svendowideit/ambassador .

[+] Building 0.7s (6/6) FINISHED
 => [internal] load build definition from Dockerfile                       0.1s
 => => transferring dockerfile: 286B                                       0.0s
 => [internal] load .dockerignore                                          0.1s
 => => transferring context: 2B                                            0.0s
 => [internal] load metadata for docker.io/library/alpine:3.2              0.4s
 => CACHED [1/2] FROM docker.io/library/alpine:3.2@sha256:e9a2035f9d0d7ce  0.0s
 => CACHED [2/2] RUN apk add --no-cache socat                              0.0s
 => exporting to image                                                     0.0s
 => => exporting layers                                                    0.0s
 => => writing image sha256:1affb80ca37018ac12067fa2af38cc5bcc2a8f09963de  0.0s
 => => naming to docker.io/svendowideit/ambassador                         0.0s

默认情况下,构建缓存基于您正在构建的机器上先前构建的结果. --cache-from选项还允许您使用通过映像注册表分发的构建缓存,请参阅docker build命令参考中的指定外部缓存源部分.

完成构建后,您就可以使用docker scan扫描您的镜像,并将您的镜像推送到 Docker Hub .

BuildKit

从版本 18.09 开始,Docker 支持一个新的后端来执行由moby/buildkit项目提供的构建. 与旧实现相比,BuildKit 后端提供了许多好处. 例如,BuildKit 可以:

  • 检测并跳过执行未使用的构建阶段
  • 并行构建独立的构建阶段
  • 在构建之间仅增量传输构建上下文中更改的文件
  • 在构建上下文中检测并跳过传输未使用的文件
  • 使用具有许多新功能的外部 Dockerfile 实现
  • 避免 API 的其余部分(中间图像和容器)产生副作用
  • 优先考虑构建缓存以进行自动修剪

要使用 BuildKit 后端,您需要在调用 docker docker build之前在 CLI 上设置环境变量DOCKER_BUILDKIT=1 .

要了解可用于基于 BuildKit 的构建的 Dockerfile 语法, 请参阅 BuildKit 存储库中的文档.

Format

这是Dockerfile的格式:

# Comment
INSTRUCTION arguments

该指令不区分大小写. 但是,约定是大写的,以便更容易地将它们与参数区分开来.

Docker 按顺序在Dockerfile中运行指令. Dockerfile必须以FROM指令开头. 这可能在解析器指令注释和全局范围的ARG之后. FROM指令指定您正在构建的父映像. FROM之前只能有一个或多个ARG指令,这些指令声明在DockerfileFROM行中使用的参数.

Docker 将以#开头的行视为注释,除非该行是有效的解析器指令. 行中任何其他位置的#标记都被视为参数. 这允许以下语句:

# Comment
RUN echo 'we are running some # of cool things'

注释行在执行 Dockerfile 指令之前被删除,这意味着下面示例中的注释不是由执行echo命令的 shell 处理的,下面两个示例是等价的:

RUN echo hello \
# comment
world
RUN echo hello \
world

注释中不支持换行符.

空格注意事项

For backward compatibility, leading whitespace before comments (#) and instructions (such as RUN) are ignored, but discouraged. Leading whitespace is not preserved in these cases, and the following examples are therefore equivalent:

        # this is a comment-line
    RUN echo hello
RUN echo world
# this is a comment-line
RUN echo hello
RUN echo world

但是请注意,指令参数中的空格(例如RUN之后的命令)被保留,因此以下示例打印 `hello world` 并带有指定的前导空格:

RUN echo "\
     hello\
     world"

Parser directives

解析器指令是可选的,并且会影响Dockerfile中后续行的处理方式. 解析器指令不会将层添加到构建中,并且不会显示为构建步骤. 解析器指令以# directive=value形式编写为一种特殊类型的注释. 一个指令只能使用一次.

一旦处理了注释、空行或构建器指令,Docker 就不再寻找解析器指令. 相反,它将任何格式化为解析器指令的内容视为注释,并且不会尝试验证它是否可能是解析器指令. 因此,所有解析器指令都必须位于Dockerfile的最顶部.

解析器指令不区分大小写. 但是,约定是小写的. 约定也是在任何解析器指令之后包含一个空行. 解析器指令不支持换行符.

Due to these rules, the following examples are all invalid:

由于行继续而无效:

# direc \
tive=value

由于出现两次而无效:

# directive=value1
# directive=value2

FROM ImageName

由于出现在构建器指令之后,因此被视为注释:

FROM ImageName
# directive=value

由于出现在不是解析器指令的注释之后,因此被视为注释:

# About my dockerfile
# directive=value
FROM ImageName

由于未被识别,未知指令被视为注释. 此外,由于出现在不是解析器指令的注释之后,已知指令被视为注释.

# unknowndirective=value
# knowndirective=value

解析器指令中允许使用非换行空格. 因此,以下行都被同等对待:

#directive=value
# directive =value
#	directive= value
# directive = value
#	  dIrEcTiVe=value

支持以下解析器指令:

  • syntax
  • escape

syntax

# syntax=[remote image reference]

例如:

# syntax=docker/dockerfile:1
# syntax=docker.io/docker/dockerfile:1
# syntax=example.com/user/repo:tag@sha256:abcdef...

此功能仅在使用BuildKit后端时可用,在使用经典构建器后端时被忽略.

语法指令定义用于构建 Dockerfile 的 Dockerfile 语法的位置. BuildKit 后端允许无缝使用作为 Docker 镜像分发并在容器沙箱环境中执行的外部实现.

自定义 Dockerfile 实现允许您:

  • 在不更新 Docker 守护进程的情况下自动获取错误修复
  • 确保所有用户都使用相同的实现来构建您的 Dockerfile
  • 无需更新 Docker 守护程序即可使用最新功能
  • 在将新功能或第三方功能集成到 Docker 守护程序之前试用它们
  • Use alternative build definitions, or create your own

Official releases

Docker 分发官方版本的镜像,可用于在 Docker Hub 上的 docker docker/dockerfile存储库下构建 Dockerfile. 有两个发布新镜像的渠道: stablelabs .

稳定频道遵循语义版本控制. 例如:

  • docker/dockerfile:1 - 使用最新的1.xx次要版本补丁版本进行更新
  • docker/dockerfile:1.2 - 使用最新的1.2.x补丁版本进行更新,并在1.3.0版本发布后停止接收更新.
  • docker/dockerfile:1.2.1 - 不可变:从未更新

我们建议使用docker/dockerfile:1 ,它始终指向版本 1 语法的最新稳定版本,并接收版本 1 发布周期的"次要"和"补丁"更新. BuildKit 在执行构建时会自动检查语法更新,确保您使用的是最新版本.

If a specific version is used, such as 1.2 or 1.2.1, the Dockerfile needs to be updated manually to continue receiving bugfixes and new features. Old versions of the Dockerfile remain compatible with the new versions of the builder.

实验室频道

"实验室"频道提供对稳定频道中尚不可用的 Dockerfile 功能的早期访问. Labs 频道映像与稳定版本一起发布,并遵循带有-labs后缀的相同版本,例如:

  • docker/dockerfile:labs - 实验室频道的最新版本
  • docker/dockerfile:1-labs - 与稳定频道中的dockerfile:1相同,启用了实验室功能
  • docker/dockerfile:1.2-labs - same as dockerfile:1.2 in the stable channel, with labs features enabled
  • docker/dockerfile:1.2.1-labs - 不可变:从未更新. 与稳定频道中的dockerfile:1.2.1相同,启用实验室功能

选择最适合您需求的渠道; 如果您想从新功能中受益,请使用实验室频道. labs 频道中的图像提供了 stable 频道中的特征的超集; 请注意,实验室频道图像中的stable功能遵循语义版本控制,但"实验室"功能不遵循,并且较新的版本可能无法向后兼容,因此建议使用不可变的完整版本变体.

有关"实验室"功能、主构建和夜间功能发布的文档,请参阅GitHub 上的 BuildKit 源存储库中的描述. 有关可用映像的完整列表,请访问Docker Hub 上的映像存储库,以及用于开发构建的docker/dockerfile-upstream 映像存储库.

escape

# escape=\ (backslash)

Or

# escape=` (backtick)

escape指令设置用于转义Dockerfile中的字符的字符. 如果未指定,则默认转义字符为\ .

转义字符既用于转义一行中的字符,也用于转义换行符. 这允许Dockerfile指令跨越多行. 请注意,无论escape解析器指令是否包含在Dockerfile中,都不会在RUN命令中执行转义,除非在行尾.

将转义字符设置为`Windows上特别有用,其中\是目录路径分隔符. `Windows PowerShell一致.

考虑以下示例,它会在Windows上以不明显的方式失败. 第二行末尾的第二个\将被解释为换行符的转义,而不是第一个\的转义目标. 类似地,第三行末尾的\会,假设它实际上是作为指令处理的,会导致它被视为行继续. 这个 dockerfile 的结果是第二行和第三行被认为是一条指令:

FROM microsoft/nanoserver
COPY testfile.txt c:\\
RUN dir c:\

结果是:

PS E:\myproject> docker build -t cmd .

Sending build context to Docker daemon 3.072 kB
Step 1/2 : FROM microsoft/nanoserver
 ---> 22738ff49c6d
Step 2/2 : COPY testfile.txt c:\RUN dir c:
GetFileAttributesEx c:RUN: The system cannot find the file specified.
PS E:\myproject>

上述问题的一种解决方案是使用/作为COPY指令和dir的目标. 但是,这种语法充其量是令人困惑的,因为它对于Windows上的路径并不自然,而且在最坏的情况下,由于并非Windows上的所有命令都支持/作为路径分隔符,因此容易出错.

通过添加escape解析器指令,以下Dockerfile使用自然平台语义在Windows上的文件路径按预期成功:

# escape=`

FROM microsoft/nanoserver
COPY testfile.txt c:\
RUN dir c:\

结果是:

PS E:\myproject> docker build -t succeeds --no-cache=true .

Sending build context to Docker daemon 3.072 kB
Step 1/3 : FROM microsoft/nanoserver
 ---> 22738ff49c6d
Step 2/3 : COPY testfile.txt c:\
 ---> 96655de338de
Removing intermediate container 4db9acbb1682
Step 3/3 : RUN dir c:\
 ---> Running in a2c157f842f5
 Volume in drive C has no label.
 Volume Serial Number is 7E6D-E0F7

 Directory of c:\

10/05/2016  05:04 PM             1,894 License.txt
10/05/2016  02:22 PM    <DIR>          Program Files
10/05/2016  02:14 PM    <DIR>          Program Files (x86)
10/28/2016  11:18 AM                62 testfile.txt
10/28/2016  11:20 AM    <DIR>          Users
10/28/2016  11:20 AM    <DIR>          Windows
           2 File(s)          1,956 bytes
           4 Dir(s)  21,259,096,064 bytes free
 ---> 01c7f3bef04f
Removing intermediate container a2c157f842f5
Successfully built 01c7f3bef04f
PS E:\myproject>

Environment replacement

环境变量(用ENV语句声明)也可以在某些指令中用作要由Dockerfile解释的变量. 还处理转义以将类似变量的语法包含在字面上的语句中.

环境变量在Dockerfile中用$variable_name${variable_name} . 它们被同等对待,大括号语法通常用于解决没有空格的变量名的问题,例如${foo}_bar .

${variable_name}语法还支持一些标准bash修饰符,如下所示:

  • ${variable:-word}表示如果设置了variable ,那么结果就是那个值. 如果未设置variable ,则结果将是word .
  • ${variable:+word}表示如果设置了variable ,则结果为word ,否则为空字符串.

在所有情况下, word都可以是任何字符串,包括附加的环境变量.

通过在变量前添加\可以进行转义:例如\$foo\${foo}将分别转换为$foo${foo}文字.

示例(解析后的表示显示在#之后):

FROM busybox
ENV FOO=/bar
WORKDIR ${FOO}   # WORKDIR /bar
ADD . $FOO       # ADD . /bar
COPY \$FOO /quux # COPY $FOO /quux

Dockerfile中的以下指令列表支持环境变量:

  • ADD
  • COPY
  • ENV
  • EXPOSE
  • FROM
  • LABEL
  • STOPSIGNAL
  • USER
  • VOLUME
  • WORKDIR
  • ONBUILD (与上述支持的指令之一结合使用时)

环境变量替换将在整个指令中为每个变量使用相同的值. 换句话说,在这个例子中:

ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc

将导致def的值为hello ,而不是bye . 但是, ghi将具有bye值,因为它不是将abc设置为bye的同一指令的一部分.

.dockerignore file

在 docker CLI 将上下文发送到 docker 守护进程之前,它会在上下文的根目录中查找名为.dockerignore的文件. 如果此文件存在,CLI 会修改上下文以排除与其中的模式匹配的文件和目录. 这有助于避免不必要地将大型或敏感文件和目录发送到守护程序,并可能使用ADDCOPY将它们添加到图像中.

CLI 将.dockerignore文件解释为以换行符分隔的模式列表,类似于 Unix shell 的文件 glob. 为了匹配的目的,上下文的根被认为是工作目录和根目录. 例如,模式/foo/barfoo/bar都排除了PATHfoo子目录或位于URL的 git 存储库的根目录中名为bar的文件或目录. 都不排除其他任何东西.

如果.dockerignore文件中的一行在第 1 列中以#开头,则该行被视为注释,并在 CLI 解释之前被忽略.

这是一个示例.dockerignore文件:

# comment
*/temp*
*/*/temp*
temp?

此文件会导致以下构建行为:

Rule Behavior
# comment Ignored.
*/temp* 在根的任何直接子目录中排除名称以temp开头的文件和目录. 例如,普通文件/somedir/temporary.txt被排除在外,目录/somedir/temp也是如此.
*/*/temp* 从根以下两级的任何子目录中排除以temp开头的文件和目录. 例如, /somedir/subdir/temporary.txt被排除在外.
temp? 排除根目录中名称为temp的单字符扩展名的文件和目录. 例如, /tempa/tempb被排除在外.

匹配是使用 Go 的filepath.Match规则完成的. 预处理步骤删除前导和尾随空格并消除...使用 Go 的filepath.Clean的元素. 预处理后空白的行将被忽略.

除了 Go 的 filepath.Match 规则之外,Docker 还支持一个特殊的通配符字符串**匹配任意数量的目录(包括零). 例如, **/*.go将排除在所有目录中找到的所有以.go结尾的文件,包括构建上下文的根目录.

Lines starting with ! (exclamation mark) can be used to make exceptions to exclusions. The following is an example .dockerignore file that uses this mechanism:

*.md
!README.md

README.md之外的所有降价文件都从上下文中排除.

The placement of ! exception rules influences the behavior: the last line of the .dockerignore that matches a particular file determines whether it is included or excluded. Consider the following example:

*.md
!README*.md
README-secret.md

除了README-secret.md以外的 README 文件,上下文中不包含任何降价文件.

现在考虑这个例子:

*.md
README-secret.md
!README*.md

所有的自述文件都包括在内. 中间行无效,因为!README*.md匹配README-secret.md并且排在最后.

您甚至可以使用.dockerignore文件来排除Dockerfile.dockerignore文件. 这些文件仍然被发送到守护进程,因为它需要它们来完成它的工作. 但是ADDCOPY指令不会将它们复制到图像中.

Finally, you may want to specify which files to include in the context, rather than which to exclude. To achieve this, specify * as the first pattern, followed by one or more ! exception patterns.

Note

由于历史原因,该模式. 被忽略.

FROM

FROM [--platform=<platform>] <image> [AS <name>]

Or

FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]

Or

FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]

FROM指令初始化一个新的构建阶段并为后续指令设置基本映像. 因此,有效的Dockerfile必须以FROM指令开头. 图像可以是任何有效的图像——从公共存储库提取图像特别容易.

  • ARGDockerfile中唯一可以在FROM之前的指令. 请参阅了解 ARG 和 FROM 如何交互.
  • FROM可以在单个Dockerfile中多次出现以创建多个映像或使用一个构建阶段作为另一个构建阶段的依赖项. 只需在每个新的FROM指令之前记下提交的最后一个图像 ID 输出. 每个FROM指令都会清除先前指令创建的任何状态.
  • 可选地,可以通过将AS name添加到FROM指令来为新的构建阶段指定名称. 该名称可以在后续的FROMCOPY --from=<name>指令中使用,以引用此阶段构建的映像.
  • tagdigest值是可选的. 如果您省略其中任何一个,则构建器默认采用latest标签. 如果无法找到tag值,构建器将返回错误.

如果FROM引用多平台映像,可选的--platform标志可用于指定映像的平台. 例如, linux/amd64linux/arm64windows/amd64 . 默认情况下,使用构建请求的目标平台. 全局构建参数可以用在这个标志的值中,例如自动平台 ARGs允许你强制一个阶段到本地构建平台( --platform=$BUILDPLATFORM ),并使用它来交叉编译到目标平台内阶段.

Understand how ARG and FROM interact

FROM指令支持在第一个FROM之前出现的任何ARG指令声明的变量.

ARG  CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD  /code/run-app

FROM extras:${CODE_VERSION}
CMD  /code/run-extras

FROM之前声明的ARG不在构建阶段,因此它不能在FROM之后的任何指令中使用. 要使用在第一个FROM之前声明的ARG的默认值,请使用在构建阶段内没有值的ARG指令:

ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version

RUN

RUN 有两种形式:

  • RUN <command>shell形式,命令在shell中运行,Linux默认为/bin/sh -c ,Windows默认为cmd /S /C
  • RUN ["executable", "param1", "param2"] (exec form)

RUN指令将在当前图像之上的新层中执行任何命令并提交结果. 生成的提交图像将用于Dockerfile中的下一步.

RUN指令分层并生成提交符合 Docker 的核心概念,其中提交很便宜,并且可以从映像历史的任何时间点创建容器,就像源代码控制一样.

exec形式可以避免 shell 字符串修改,并使用不包含指定 shell 可执行文件的基本映像RUN命令.

可以使用SHELL命令更改shell形式的默认 shell.

shell形式中,您可以使用\ (反斜杠)将单个 RUN 指令继续到下一行. 例如,考虑以下两行:

RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'

它们一起等同于这一行:

RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'

要使用"/bin/sh"以外的其他 shell,请使用传入所需 shell 的exec形式. 例如:

RUN ["/bin/bash", "-c", "echo hello"]

Note

exec形式被解析为 JSON 数组,这意味着您必须在单词周围使用双引号 (") 而不是单引号 (').

shell形式不同, exec形式不调用命令 shell. 这意味着不会发生正常的外壳处理. 例如, RUN [ "echo", "$HOME" ]不会对$HOME进行变量替换. 如果你想要 shell 处理,那么要么使用shell形式,要么直接执行 shell,例如: RUN [ "sh", "-c", "echo $HOME" ] . 当使用 exec 形式并直接执行 shell 时,与 shell 形式一样,是 shell 进行环境变量扩展,而不是 docker.

Note

JSON格式中,需要对反斜杠进行转义. 这在反斜杠是路径分隔符的 Windows 上尤为重要. 由于不是有效的 JSON,以下行将被视为shell形式,并以意想不到的方式失败:

RUN ["c:\windows\system32\tasklist.exe"]

此示例的正确语法是:

RUN ["c:\\windows\\system32\\tasklist.exe"]

RUN指令的缓存不会在下一次构建期间自动失效. 像RUN apt-get dist-upgrade -y这样的指令的缓存将在下一次构建期间重复使用. 可以使用--no-cache标志使RUN指令的缓存无效,例如docker build --no-cache .

有关更多信息,请参阅Dockerfile最佳实践指南.

RUN指令的缓存可以通过ADDCOPY指令无效.

Known issues (RUN)

  • 第 783 期 is about file permissions problems that can occur when using the AUFS file system. You might notice it during an attempt to rm a file, for example.

    对于具有最新 aufs 版本的系统(即可以设置dirperm1挂载选项),docker 将尝试通过使用dirperm1选项挂载层来自动修复问题. 有关dirperm1选项的更多详细信息,请参见aufs手册页

    如果您的系统不支持dirperm1 ,则该问题描述了一种解决方法.

CMD

CMD指令有三种形式:

  • CMD ["executable","param1","param2"]执行形式,这是首选形式)
  • CMD ["param1","param2"] (as ENTRYPOINT 的默认参数)
  • CMD command param1 param2 (shell form)

一个Dockerfile中只能有一个CMD指令. 如果您列出多个CMD ,则只有最后一个CMD才会生效.

CMD的主要目的是为执行容器提供默认值. 这些默认值可以包括可执行文件,也可以省略可执行文件,在这种情况下,您还必须指定ENTRYPOINT指令.

如果CMD用于为ENTRYPOINT指令提供默认参数,则CMDENTRYPOINT指令都应使用 JSON 数组格式指定.

Note

exec形式被解析为 JSON 数组,这意味着您必须在单词周围使用双引号 (") 而不是单引号 (').

shell形式不同, exec形式不调用命令 shell. 这意味着不会发生正常的外壳处理. 例如, CMD [ "echo", "$HOME" ]不会对$HOME进行变量替换. 如果你想要 shell 处理,那么要么使用shell形式,要么直接执行 shell,例如: CMD [ "sh", "-c", "echo $HOME" ] . 当使用 exec 形式并直接执行 shell 时,与 shell 形式一样,是 shell 进行环境变量扩展,而不是 docker.

在 shell 或 exec 格式中使用时, CMD指令设置在运行映像时要执行的命令.

如果您使用CMDshell形式,则<command>将在/bin/sh -c中执行:

FROM ubuntu
CMD echo "This is a test." | wc -

如果您想在没有 shell的情况下运行<command> ,则必须将命令表示为 JSON 数组并提供可执行文件的完整路径. 这种数组形式是CMD的首选格式. 任何附加参数必须单独表示为数组中的字符串:

FROM ubuntu
CMD ["/usr/bin/wc","--help"]

如果您希望您的容器每次都运行相同的可执行文件,那么您应该考虑将ENTRYPOINTCMD结合使用. 请参阅入口点.

如果用户为docker run指定参数,那么他们将覆盖CMD中指定的默认值.

Note

不要将RUNCMD混淆. RUN实际运行一个命令并提交结果; CMD在构建时不会执行任何操作,但会为映像指定预期的命令.

LABEL

LABEL <key>=<value> <key>=<value> <key>=<value> ...

LABEL指令将元数据添加到图像. LABEL是键值对. 要在LABEL值中包含空格,请像在命令行解析中一样使用引号和反斜杠. 几个使用示例:

LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."

一张图片可以有多个标签. 您可以在一行中指定多个标签. 在 Docker 1.10 之前,这减小了最终映像的大小,但现在不再如此. 您仍然可以通过以下两种方式之一选择在一条指令中指定多个标签:

LABEL multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \
      multi.label2="value2" \
      other="value3"

包含在基础或父图像( FROM行中的图像)中的标签由您的图像继承. 如果标签已存在但具有不同的值,则最近应用的值将覆盖任何先前设置的值.

要查看图像的标签,请使用docker image inspect命令. 您可以使用--format选项仅显示标签;

$ docker image inspect --format='' myimage
{
  "com.example.vendor": "ACME Incorporated",
  "com.example.label-with-value": "foo",
  "version": "1.0",
  "description": "This text illustrates that label-values can span multiple lines.",
  "multi.label1": "value1",
  "multi.label2": "value2",
  "other": "value3"
}

MAINTAINER (deprecated)

MAINTAINER <name>

MAINTAINER指令设置生成图像的作者字段. LABEL指令是一个更灵活的版本,您应该使用它,因为它可以设置您需要的任何元数据,并且可以轻松查看,例如使用docker inspect . 要设置与MAINTAINER字段对应的标签,您可以使用:

LABEL org.opencontainers.image.authors="SvenDowideit@home.org.au"

这将在docker inspect和其他标签中可见.

EXPOSE

EXPOSE <port> [<port>/<protocol>...]

EXPOSE指令通知 Docker 容器在运行时侦听指定的网络端口. 可以指定端口监听 TCP 还是 UDP,如果不指定协议,则默认为 TCP.

EXPOSE指令实际上并不发布端口. 它充当构建映像的人和运行容器的人之间的一种文档类型,关于打算发布哪些端口. 要在运行容器时实际发布端口,请使用docker run上的-p标志来发布和映射一个或多个端口,或者使用-P标志来发布所有暴露的端口并将它们映射到高阶端口.

默认情况下, EXPOSE假定 TCP. 您还可以指定 UDP:

EXPOSE 80/udp

要同时在 TCP 和 UDP 上公开,请包括两行:

EXPOSE 80/tcp
EXPOSE 80/udp

在这种情况下,如果将-Pdocker run一起使用,则该端口将为 TCP 公开一次,为 UDP 公开一次. 请记住, -P使用主机上的临时高位主机端口,因此 TCP 和 UDP 的端口将不同.

无论EXPOSE设置如何,您都可以在运行时使用-p标志覆盖它们. 例如

$ docker run -p 80:80/tcp -p 80:80/udp ...

要在主机系统上设置端口重定向,请参阅使用 -P 标志. docker network命令支持为容器之间的通信创建网络,而无需暴露或发布特定的端口,因为连接到网络的容器可以通过任何端口相互通信. 有关详细信息,请参阅此功能的概述.

ENV

ENV <key>=<value> ...

ENV指令将环境变量<key>设置为值<value> . 该值将在构建阶段的所有后续指令的环境中,也可以在许多中内联替换. 该值将被解释为其他环境变量,因此如果没有转义引号字符将被删除. 与命令行解析一样,引号和反斜杠可用于在值中包含空格.

Example:

ENV MY_NAME="John Doe"
ENV MY_DOG=Rex\ The\ Dog
ENV MY_CAT=fluffy

ENV指令允许一次设置多个<key>=<value> ...变量,下面的示例将在最终图像中产生相同的净结果:

ENV MY_NAME="John Doe" MY_DOG=Rex\ The\ Dog \
    MY_CAT=fluffy

当容器从生成的图像运行时,使用ENV设置的环境变量将保持不变. 您可以使用 docker docker inspect查看值,并使用docker run --env <key>=<value>更改它们.

环境变量持久性可能会导致意想不到的副作用. 例如,设置ENV DEBIAN_FRONTEND=noninteractive会改变apt-get的行为,并且可能会混淆图像的用户.

如果仅在构建期间需要环境变量,而不是在最终映像中,请考虑为单个命令设置一个值:

RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y ...

或者使用ARG ,它不会保留在最终图像中:

ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y ...

Alternative syntax

ENV指令还允许使用替代语法ENV <key> <value> ,省略= . 例如:

ENV MY_VAR my-value

此语法不允许在单个ENV指令中设置多个环境变量,并且可能会造成混淆. 例如,以下设置一个值为"TWO= THREE=world"的环境变量 ( ONE ):

ENV ONE TWO= THREE=world

支持替代语法是为了向后兼容,但由于上述原因不鼓励使用,并且可能会在未来的版本中删除.

ADD

ADD has two forms:

ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

包含空格的路径需要后一种形式.

Note

--chown功能仅在用于构建 Linux 容器的 Dockerfile 上受支持,不适用于 Windows 容器. 由于用户和组所有权概念不能在 Linux 和 Windows 之间转换,因此使用/etc/passwd/etc/group将用户名和组名转换为 ID 会限制此功能仅适用于基于 Linux 操作系统的容器.

ADD指令从<src>复制新文件、目录或远程文件 URL,并将它们添加到路径<dest>的图像文件系统中.

可以指定多个<src>资源,但如果它们是文件或目录,则它们的路径被解释为相对于构建上下文的源.

Each <src> may contain wildcards and matching will be done using Go’s filepath.Match rules. For example:

添加所有以"hom"开头的文件:

ADD hom* /mydir/

在下面的例子中, ? 替换为任何单个字符,例如"home.txt".

ADD hom?.txt /mydir/

<dest>是绝对路径,或相对于WORKDIR的路径,源将在目标容器内复制到其中.

下面的示例使用相对路径,并将"test.txt"添加到<WORKDIR>/relativeDir/

ADD test.txt relativeDir/

而此示例使用绝对路径,并将"test.txt"添加到/absoluteDir/

ADD test.txt /absoluteDir/

添加包含特殊字符(例如[] )的文件或目录时,您需要按照 Golang 规则对这些路径进行转义,以防止它们被视为匹配模式. 例如,要添加名为arr[0].txt的文件,请使用以下内容;

ADD arr[[]0].txt /mydir/

所有新文件和目录都使用 0 的 UID 和 GID 创建,除非可选的--chown标志指定给定的用户名、组名或 UID/GID 组合以请求添加内容的特定所有权. --chown标志的格式允许用户名和组名字符串或直接整数 UID 和 GID 的任意组合. 提供不带组名的用户名或不带 GID 的 UID 将使用与 GID 相同的数字 UID. 如果提供了用户名或组名,则容器的根文件系统/etc/passwd/etc/group文件将分别用于执行从名称到整数 UID 或 GID 的转换. 以下示例显示了--chown标志的有效定义:

ADD --chown=55:mygroup files* /somedir/
ADD --chown=bin files* /somedir/
ADD --chown=1 files* /somedir/
ADD --chown=10:11 files* /somedir/

如果容器根文件系统不包含/etc/passwd/etc/group文件,并且--chown标志中使用了用户名或组名,则构建将在ADD操作时失败. 使用数字 ID 不需要查找,并且不依赖于容器根文件系统的内容.

<src>是远程文件 URL 的情况下,目标将具有 600 的权限.如果正在检索的远程文件具有 HTTP Last-Modified标头,则该标头中的时间戳将用于设置目标上的mtime文件. 但是,与在ADD期间处理的任何其他文件一样,在确定文件是否已更改以及是否应更新缓存时,不会包括mtime .

Note

如果通过 STDIN ( docker docker build - < somefile ) 传递Dockerfile进行构建,则没有构建上下文,因此Dockerfile只能包含基于 URL 的ADD指令. 您还可以通过 STDIN 传递压缩存档:( docker docker build - < archive.tar.gz ),存档根目录下的Dockerfile和存档的其余部分将用作构建的上下文.

如果您的 URL 文件使用身份验证进行保护,则需要使用RUN wgetRUN curl或使用容器内的其他工具,因为ADD指令不支持身份验证.

Note

如果<src>的内容已更改,则第一个遇到的ADD指令将使 Dockerfile 中所有后续指令的缓存无效. 这包括使RUN指令的缓存无效. 有关更多信息,请参阅Dockerfile最佳实践指南 - 利用构建缓存.

ADD遵循以下规则:

  • <src>路径必须在构建的上下文中; 您不能ADD ../something /something ,因为 docker docker build的第一步是将上下文目录(和子目录)发送到 docker 守护进程.

  • 如果<src>是 URL 并且<dest>不以斜杠结尾,则从 URL 下载文件并复制到<dest> .

  • 如果<src>是 URL 并且<dest>确实以斜杠结尾,则从 URL 推断文件名并将文件下载到<dest>/<filename> . 例如, ADD http://example.com/foobar /将创建文件/foobar . URL 必须有一个重要的路径,以便在这种情况下可以找到适当的文件名( http://example.com将不起作用).

  • 如果<src>是目录,则复制目录的全部内容,包括文件系统元数据.

Note

目录本身没有被复制,只是它的内容.

  • 如果<src>是可识别压缩格式(identity、gzip、bzip2 或 xz)的本地tar 存档,则将其解压缩为目录. 来自远程URL 的资源不会被解压缩. 当一个目录被复制或解压时,它与tar -x具有相同的行为,结果是并集:

    1. 无论目标路径上存在什么,
    2. 源代码树的内容,冲突已解决,有利于"2". 逐个文件.

    Note

    文件是否被识别为可识别的压缩格式完全取决于文件的内容,而不是文件的名称. 例如,如果一个空文件碰巧以.tar.gz结尾,这将不会被识别为压缩文件,也不会生成任何类型的解压缩错误消息,而是将文件简单地复制到目标位置.

  • 如果<src>是任何其他类型的文件,它会与元数据一起单独复制. 在这种情况下,如果<dest>以斜杠/结尾,它将被视为一个目录,并且<src>的内容将写入<dest>/base(<src>) .

  • 如果直接指定了多个<src>资源,或者由于使用了通配符,则<dest>必须是目录,并且必须以斜杠/结尾.

  • 如果<dest>不以斜杠结尾,它将被视为常规文件,并且<src>的内容将写入<dest> .

  • 如果<dest>不存在,它会连同其路径中所有缺失的目录一起创建.

COPY

COPY有两种形式:

COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

包含空格的路径需要后一种形式

Note

--chown功能仅在用于构建 Linux 容器的 Dockerfile 上受支持,不适用于 Windows 容器. 由于用户和组所有权概念不能在 Linux 和 Windows 之间转换,因此使用/etc/passwd/etc/group将用户名和组名转换为 ID 会限制此功能仅适用于基于 Linux 操作系统的容器.

COPY指令从<src>复制新文件或目录,并将它们添加到路径<dest>的容器文件系统中.

可以指定多个<src>资源,但文件和目录的路径将被解释为相对于构建上下文的源.

每个<src>可能包含通配符,匹配将使用 Go 的filepath.Match规则完成. 例如:

添加所有以"hom"开头的文件:

COPY hom* /mydir/

在下面的例子中, ? 替换为任何单个字符,例如"home.txt".

COPY hom?.txt /mydir/

<dest>是绝对路径,或相对于WORKDIR的路径,源将在目标容器内复制到其中.

下面的示例使用相对路径,并将"test.txt"添加到<WORKDIR>/relativeDir/

COPY test.txt relativeDir/

而此示例使用绝对路径,并将"test.txt"添加到/absoluteDir/

COPY test.txt /absoluteDir/

在复制包含特殊字符(例如[] )的文件或目录时,您需要按照 Golang 规则对这些路径进行转义,以防止它们被视为匹配模式. 例如,要复制名为arr[0].txt的文件,请使用以下命令;

COPY arr[[]0].txt /mydir/

所有新文件和目录都使用 0 的 UID 和 GID 创建,除非可选的--chown标志指定给定的用户名、组名或 UID/GID 组合以请求复制内容的特定所有权. --chown标志的格式允许用户名和组名字符串或直接整数 UID 和 GID 的任意组合. 提供不带组名的用户名或不带 GID 的 UID 将使用与 GID 相同的数字 UID. 如果提供了用户名或组名,则容器的根文件系统/etc/passwd/etc/group文件将分别用于执行从名称到整数 UID 或 GID 的转换. 以下示例显示了--chown标志的有效定义:

COPY --chown=55:mygroup files* /somedir/
COPY --chown=bin files* /somedir/
COPY --chown=1 files* /somedir/
COPY --chown=10:11 files* /somedir/

如果容器根文件系统不包含/etc/passwd/etc/group文件,并且--chown标志中使用了用户名或组名,则构建将在COPY操作中失败. 使用数字 ID 不需要查找并且不依赖于容器根文件系统内容.

Note

如果您使用 STDIN ( docker build - < somefile ) 构建,则没有构建上下文,因此无法使用COPY .

COPY可以选择接受一个标志--from=<name> ,该标志可用于将源位置设置为先前的构建阶段(使用FROM .. AS <name>创建),该阶段将用于代替用户发送的构建上下文. 如果找不到具有指定名称的构建阶段,则尝试使用具有相同名称的图像.

COPY遵循以下规则:

  • <src>路径必须在构建的上下文中; 你不能COPY ../something /something ,因为 docker docker build的第一步是将上下文目录(和子目录)发送到 docker 守护进程.

  • If <src> is a directory, the entire contents of the directory are copied, including filesystem metadata.

Note

目录本身没有被复制,只是它的内容.

  • 如果<src>是任何其他类型的文件,它会与元数据一起单独复制. 在这种情况下,如果<dest>以斜杠/结尾,它将被视为一个目录,并且<src>的内容将写入<dest>/base(<src>) .

  • 如果直接指定了多个<src>资源,或者由于使用了通配符,则<dest>必须是目录,并且必须以斜杠/结尾.

  • 如果<dest>不以斜杠结尾,它将被视为常规文件,并且<src>的内容将写入<dest> .

  • 如果<dest>不存在,它会连同其路径中所有缺失的目录一起创建.

Note

如果<src>的内容已更改,则第一个遇到的COPY指令将使 Dockerfile 中所有后续指令的缓存无效. 这包括使RUN指令的缓存无效. 有关更多信息,请参阅Dockerfile最佳实践指南 - 利用构建缓存.

ENTRYPOINT

ENTRYPOINT 有两种形式:

The exec form, which is the preferred form:

ENTRYPOINT ["executable", "param1", "param2"]

The shell form:

ENTRYPOINT command param1 param2

ENTRYPOINT允许您配置将作为可执行文件运行的容器.

例如,以下内容使用其默认内容启动 nginx,侦听端口 80:

$ docker run -i -t --rm -p 80:80 nginx

docker docker run <image>的命令行参数将附加在exec表单ENTRYPOINT中的所有元素之后,并将覆盖使用CMD指定的所有元素. 这允许将参数传递给入口点,即docker run <image> -d会将-d参数传递给入口点. 您可以使用 docker docker run --entrypoint标志覆盖ENTRYPOINT指令.

shell形式阻止使用任何CMDrun命令行参数,但缺点是您的ENTRYPOINT将作为/bin/sh -c的子命令启动,它不传递信号. 这意味着可执行文件将不是容器的PID 1 - 并且不会接收 Unix 信号 - 因此您的可执行文件将不会收到来自docker stop <container>SIGTERM .

只有 Dockerfile 中的最后一条ENTRYPOINT指令Dockerfile生效.

Exec form ENTRYPOINT example

您可以使用ENTRYPOINTexec形式来设置相当稳定的默认命令和参数,然后使用任一形式的CMD来设置更可能更改的其他默认值.

FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]

运行容器时,可以看到top是唯一的进程:

$ docker run -it --rm --name test  top -H

top - 08:25:00 up  7:27,  0 users,  load average: 0.00, 0.01, 0.05
Threads:   1 total,   1 running,   0 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.1 us,  0.1 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:   2056668 total,  1616832 used,   439836 free,    99352 buffers
KiB Swap:  1441840 total,        0 used,  1441840 free.  1324440 cached Mem

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
    1 root      20   0   19744   2336   2080 R  0.0  0.1   0:00.04 top

要进一步检查结果,您可以使用docker exec

$ docker exec -it test ps aux

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  2.6  0.1  19752  2352 ?        Ss+  08:24   0:00 top -b -H
root         7  0.0  0.1  15572  2164 ?        R+   08:25   0:00 ps aux

您可以使用docker stop test优雅地请求top关闭.

以下Dockerfile显示使用ENTRYPOINT在前台运行 Apache(即,作为PID 1 ):

FROM debian:stable
RUN apt-get update && apt-get install -y --force-yes apache2
EXPOSE 80 443
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

如果您需要为单个可执行文件编写启动脚本,您可以使用execgosu命令确保最终的可执行文件接收到 Unix 信号:

#!/usr/bin/env bash
set -e

if [ "$1" = 'postgres' ]; then
    chown -R postgres "$PGDATA"

    if [ -z "$(ls -A "$PGDATA")" ]; then
        gosu postgres initdb
    fi

    exec gosu postgres "$@"
fi

exec "$@"

最后,如果您需要在关闭时进行一些额外的清理(或与其他容器通信),或者协调多个可执行文件,您可能需要确保ENTRYPOINT脚本接收 Unix 信号,传递它们,然后做更多的工作:

#!/bin/sh
# Note: I've written this using sh so it works in the busybox container too

# USE the trap if you need to also do manual cleanup after the service is stopped,
#     or need to start multiple services in the one container
trap "echo TRAPed signal" HUP INT QUIT TERM

# start service in background here
/usr/sbin/apachectl start

echo "[hit enter key to exit] or run 'docker stop <container>'"
read

# stop service and clean up here
echo "stopping apache"
/usr/sbin/apachectl stop

echo "exited $0"

如果您使用docker run -it --rm -p 80:80 --name test apache运行此映像,则可以使用 docker docker execdocker top检查容器的进程,然后要求脚本停止 Apache:

$ docker exec -it test ps aux

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.1  0.0   4448   692 ?        Ss+  00:42   0:00 /bin/sh /run.sh 123 cmd cmd2
root        19  0.0  0.2  71304  4440 ?        Ss   00:42   0:00 /usr/sbin/apache2 -k start
www-data    20  0.2  0.2 360468  6004 ?        Sl   00:42   0:00 /usr/sbin/apache2 -k start
www-data    21  0.2  0.2 360468  6000 ?        Sl   00:42   0:00 /usr/sbin/apache2 -k start
root        81  0.0  0.1  15572  2140 ?        R+   00:44   0:00 ps aux

$ docker top test

PID                 USER                COMMAND
10035               root                {run.sh} /bin/sh /run.sh 123 cmd cmd2
10054               root                /usr/sbin/apache2 -k start
10055               33                  /usr/sbin/apache2 -k start
10056               33                  /usr/sbin/apache2 -k start

$ /usr/bin/time docker stop test

test
real	0m 0.27s
user	0m 0.03s
sys	0m 0.03s

Note

您可以使用--entrypoint覆盖ENTRYPOINT设置,但这只能将二进制文件设置为exec (不会使用sh -c ).

Note

exec形式被解析为 JSON 数组,这意味着您必须在单词周围使用双引号 (") 而不是单引号 (').

shell形式不同, exec形式不调用命令 shell. 这意味着不会发生正常的外壳处理. 例如, ENTRYPOINT [ "echo", "$HOME" ]不会对$HOME进行变量替换. 如果你想要 shell 处理,那么要么使用shell形式,要么直接执行 shell,例如: ENTRYPOINT [ "sh", "-c", "echo $HOME" ] . 当使用 exec 形式并直接执行 shell 时,与 shell 形式一样,是 shell 进行环境变量扩展,而不是 docker.

Shell form ENTRYPOINT example

您可以为ENTRYPOINT指定一个纯字符串,它将在/bin/sh -c中执行. 此表单将使用 shell 处理来替换 shell 环境变量,并将忽略任何CMDdocker run命令行参数. 为确保 docker docker stop将正确发出任何长时间运行的ENTRYPOINT可执行文件的信号,您需要记住使用exec启动它:

FROM ubuntu
ENTRYPOINT exec top -b

运行此映像时,您将看到单个PID 1进程:

$ docker run -it --rm --name test top

Mem: 1704520K used, 352148K free, 0K shrd, 0K buff, 140368121167873K cached
CPU:   5% usr   0% sys   0% nic  94% idle   0% io   0% irq   0% sirq
Load average: 0.08 0.03 0.05 2/98 6
  PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
    1     0 root     R     3164   0%   0% top -b

docker stop上干净地退出:

$ /usr/bin/time docker stop test

test
real	0m 0.20s
user	0m 0.02s
sys	0m 0.04s

如果您忘记将exec添加到ENTRYPOINT的开头:

FROM ubuntu
ENTRYPOINT top -b
CMD -- --ignored-param1

然后您可以运行它(为下一步命名):

$ docker run -it --name test top --ignored-param2

top - 13:58:24 up 17 min,  0 users,  load average: 0.00, 0.00, 0.00
Tasks:   2 total,   1 running,   1 sleeping,   0 stopped,   0 zombie
%Cpu(s): 16.7 us, 33.3 sy,  0.0 ni, 50.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   1990.8 total,   1354.6 free,    231.4 used,    404.7 buff/cache
MiB Swap:   1024.0 total,   1024.0 free,      0.0 used.   1639.8 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
    1 root      20   0    2612    604    536 S   0.0   0.0   0:00.02 sh
    6 root      20   0    5956   3188   2768 R   0.0   0.2   0:00.00 top

top的输出可以看出,指定的ENTRYPOINT不是PID 1 .

如果您随后运行docker stop test ,容器将不会干净地退出 - stop命令将在超时后强制发送SIGKILL

$ docker exec -it test ps waux

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.4  0.0   2612   604 pts/0    Ss+  13:58   0:00 /bin/sh -c top -b --ignored-param2
root         6  0.0  0.1   5956  3188 pts/0    S+   13:58   0:00 top -b
root         7  0.0  0.1   5884  2816 pts/1    Rs+  13:58   0:00 ps waux

$ /usr/bin/time docker stop test

test
real	0m 10.19s
user	0m 0.04s
sys	0m 0.03s

Understand how CMD and ENTRYPOINT interact

CMDENTRYPOINT指令都定义了运行容器时执行的命令. 很少有规则描述他们的合作.

  1. Dockerfile 应至少指定CMDENTRYPOINT命令之一.

  2. 将容器用作可执行文件时应定义ENTRYPOINT .

  3. CMD应该用作为ENTRYPOINT命令或在容器中执行临时命令定义默认参数的一种方式.

  4. 当使用替代参数运行容器时, CMD将被覆盖.

下表显示了针对不同ENTRYPOINT / CMD组合执行的命令:

  没有入口点 入口点 exec_entry p1_entry 入口点 ["exec_entry","p1_entry"]
没有命令 错误,不允许 /bin/sh -c exec_entry p1_entry exec_entry p1_entry
CMD ["exec_cmd","p1_cmd"] exec_cmd p1_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry exec_cmd p1_cmd
CMD ["p1_cmd","p2_cmd"] p1_cmd p2_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry p1_cmd p2_cmd
CMD exec_cmd p1_cmd /bin/sh -c exec_cmd p1_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd

Note

如果CMD是从基础映像定义的,则设置ENTRYPOINT会将CMD重置为空值. 在这种情况下,必须在当前图像中定义CMD才能具有值.

VOLUME

VOLUME ["/data"]

VOLUME指令创建一个具有指定名称的挂载点,并将其标记为保存来自本机主机或其他容器的外部挂载卷. 该值可以是 JSON 数组、 VOLUME ["/var/log/"]或具有多个参数的纯字符串,例如VOLUME /var/logVOLUME /var/log /var/db . 有关通过 Docker 客户端的更多信息/示例和安装说明,请参阅通过卷文档共享目录.

docker run命令使用基本映像中指定位置存在的任何数据初始化新创建的卷. 例如,考虑以下 Dockerfile 片段:

FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol

此 Dockerfile 生成一个图像,该图像导致 docker docker run/myvol创建一个新的挂载点,并将greeting文件复制到新创建的卷中.

Notes about specifying volumes

请记住以下有关Dockerfile中的卷的事项.

  • 基于 Windows 的容器上的卷:使用基于 Windows 的容器时,容器内卷的目标必须是以下之一:

    • 一个不存在或空的目录
    • C:
  • 从 Dockerfile 中更改卷:如果任何构建步骤在声明卷后更改了卷中的数据,则这些更改将被丢弃.

  • JSON 格式:列表被解析为 JSON 数组. 您必须用双引号 ( " ) 而不是单引号 ( ' ) 将单词括起来.

  • 主机目录在容器运行时声明:主机目录(挂载点)本质上是依赖于主机的. 这是为了保持图像的可移植性,因为不能保证给定的主机目录在所有主机上都可用. 因此,您无法从 Dockerfile 中挂载主机目录. VOLUME指令不支持指定host-dir参数. 您必须在创建或运行容器时指定挂载点.

USER

USER <user>[:<group>]

or

USER <UID>[:<GID>]

USER指令设置用户名(或 UID)和可选的用户组(或 GID),以在运行映像时以及 Dockerfile 中跟随它的任何RUNCMDENTRYPOINT指令Dockerfile .

请注意,在为用户指定组时,用户将具有指定的组成员身份. 任何其他配置的组成员资格都将被忽略.

Warning

当用户没有主组时,图像(或下一条指令)将与root组一起运行.

在 Windows 上,如果用户不是内置帐户,则必须先创建用户. 这可以通过调用作为 Dockerfile 一部分的net user命令来完成.

FROM microsoft/windowsservercore
# Create Windows user in the container
RUN net user /add patrick
# Set it for subsequent commands
USER patrick

WORKDIR

WORKDIR /path/to/workdir

WORKDIR指令为Dockerfile中任何RUNCMDENTRYPOINTCOPYADD指令设置工作目录. 如果WORKDIR不存在,即使它没有在任何后续Dockerfile指令中使用,它也会被创建.

WORKDIR指令可以在Dockerfile中多次使用. 如果提供了相对路径,它将相对于前一个WORKDIR指令的路径. 例如:

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

Dockerfile中最终pwd命令的输出将是/a/b/c .

WORKDIR指令可以解析以前使用ENV设置的环境变量. 您只能使用在Dockerfile中明确设置的环境变量. 例如:

ENV DIRPATH=/path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd

Dockerfile中最终pwd命令的输出将是/path/$DIRNAME

如果未指定,则默认工作目录为/ . 在实践中,如果您不是从头开始构建 Dockerfile ( FROM scratch ),则WORKDIR可能由您使用的基础映像设置.

因此,为避免在未知目录中进行意外操作,最佳实践是明确设置您的WORKDIR .

ARG

ARG <name>[=<default value>]

ARG指令定义了一个变量,用户可以在构建时通过docker build命令使用--build-arg <varname>=<value>标志将其传递给构建器. 如果用户指定了未在 Dockerfile 中定义的构建参数,则构建会输出警告.

[Warning] One or more build-args [foo] were not consumed.

一个 Dockerfile 可能包含一个或多个ARG指令. 例如,以下是一个有效的 Dockerfile:

FROM busybox
ARG user1
ARG buildno
# ...

Warning:

不建议使用构建时变量来传递 github 密钥、用户凭据等机密信息.使用docker history命令的图像的任何用户都可以看到构建时变量值.

请参阅"使用 BuildKit 构建镜像"部分,了解构建镜像时使用机密的安全方法.

Default values

ARG指令可以选择包含默认值:

FROM busybox
ARG user1=someuser
ARG buildno=1
# ...

如果ARG指令具有默认值,并且在构建时没有传递任何值,则构建器将使用默认值.

Scope

ARG变量定义从它在Dockerfile中定义的行开始生效,而不是从参数在命令行或其他地方的使用开始生效. 例如,考虑这个 Dockerfile:

FROM busybox
USER ${user:-some_user}
ARG user
USER $user
# ...

用户通过调用来构建此文件:

$ docker build --build-arg user=what_user .

第 2 行的USER评估为some_user ,因为user变量在随后的第 3 行中定义.第 4 行的USER评估为what_user ,因为user已定义,并且what_user值已在命令行上传递. 在通过ARG指令定义之前,对变量的任何使用都会导致空字符串.

ARG指令在定义它的构建阶段结束时超出范围. 要在多个阶段中使用 arg,每个阶段都必须包含ARG指令.

FROM busybox
ARG SETTINGS
RUN ./run/setup $SETTINGS

FROM busybox
ARG SETTINGS
RUN ./run/other $SETTINGS

Using ARG variables

您可以使用ARGENV指令来指定可用于RUN指令的变量. 使用ENV指令定义的环境变量总是覆盖同名的ARG指令. 考虑这个带有ENVARG指令的 Dockerfile.

FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=v1.0.0
RUN echo $CONT_IMG_VER

然后,假设这个镜像是用这个命令构建的:

$ docker build --build-arg CONT_IMG_VER=v2.0.1 .

在这种情况下, RUN指令使用v1.0.0而不是用户传递的ARG设置: v2.0.1这种行为类似于 shell 脚本,其中局部范围的变量会覆盖作为参数传递或从环境继承的变量,从其定义点.

使用上面的示例但使用不同的ENV规范,您可以在ARGENV指令之间创建更有用的交互:

FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=${CONT_IMG_VER:-v1.0.0}
RUN echo $CONT_IMG_VER

ARG指令不同, ENV值始终保留在构建的映像中. 考虑一个没有--build-arg标志的 docker build:

$ docker build .

使用此 Dockerfile 示例, CONT_IMG_VER仍保留在映像中,但其值为v1.0.0 ,因为它是ENV指令在第 3 行中设置的默认值.

此示例中的变量扩展技术允许您从命令行传递参数,并通过利用ENV指令将它们保存在最终图像中. 仅有限的一组 Dockerfile 指令支持变量扩展.

Predefined ARGs

Docker 有一组预定义的ARG变量,您可以在 Dockerfile 中没有相应的ARG指令的情况下使用这些变量.

  • HTTP_PROXY
  • http_proxy
  • HTTPS_PROXY
  • https_proxy
  • FTP_PROXY
  • ftp_proxy
  • NO_PROXY
  • no_proxy

要使用这些,请使用--build-arg标志在命令行上传递它们,例如:

$ docker build --build-arg HTTPS_PROXY=https://my-proxy.example.com .

默认情况下,这些预定义变量会从docker history的输出中排除. 排除它们可以降低在HTTP_PROXY变​​量中意外泄露敏感身份验证信息的风险.

例如,考虑使用--build-arg HTTP_PROXY=http://user:pass@proxy.lon.example.com

FROM ubuntu
RUN echo "Hello World"

在这种情况下, HTTP_PROXY变​​量的值在docker history中不可用,也不会被缓存. 如果您要更改位置,并且您的代理服务器更改为http://user:pass@proxy.sfo.example.com ,则后续构建不会导致缓存未命中.

如果您需要覆盖此行为,则可以通过在 Dockerfile 中添加ARG语句来实现,如下所示:

FROM ubuntu
ARG HTTP_PROXY
RUN echo "Hello World"

构建此 Dockerfile 时, HTTP_PROXY保留在 docker docker history中,更改其值会使构建缓存无效.

Automatic platform ARGs in the global scope

此功能仅在使用BuildKit后端时可用.

Docker 预定义了一组ARG变量,其中包含有关执行构建的节点平台(构建平台)和生成图像的平台(目标平台)的信息. 可以使用 docker docker build上的--platform标志指定目标平台.

自动设置以下ARG变量:

  • TARGETPLATFORM - 构建结果的平台. 例如linux/amd64linux/arm/v7windows/amd64 .
  • TARGETOS - TARGETPLATFORM 的操作系统组件
  • TARGETARCH - TARGETPLATFORM 的架构组件
  • TARGETVARIANT - TARGETPLATFORM 的变体组件
  • BUILDPLATFORM - 执行构建的节点平台.
  • BUILDOS - BUILDPLATFORM 的操作系统组件
  • BUILDARCH - BUILDPLATFORM 的架构组件
  • BUILDVARIANT - BUILDPLATFORM 的变体组件

这些参数是在全局范围内定义的,因此在构建阶段或您的RUN命令中不会自动使用. 要在构建阶段公开这些参数之一,请重新定义它而没有价值.

例如:

FROM alpine
ARG TARGETPLATFORM
RUN echo "I'm building for $TARGETPLATFORM"

Impact on build caching

ARG变量不会像ENV变量那样持久化到构建的映像中. 但是, ARG变量确实以类似的方式影响构建缓存. 如果 Dockerfile 定义了一个ARG变量,其值与以前的构建不同,那么在第一次使用时会发生"缓存未命中",而不是定义. 特别是,在ARG指令之后的所有RUN指令都隐式使用ARG变量(作为环境变量),因此可能导致缓存未命中. 除非Dockerfile中有匹配的ARG语句,否则所有预定义的ARG变量都免于缓存.

例如,考虑这两个 Dockerfile:

FROM ubuntu
ARG CONT_IMG_VER
RUN echo $CONT_IMG_VER
FROM ubuntu
ARG CONT_IMG_VER
RUN echo hello

如果在命令行中指定--build-arg CONT_IMG_VER=<value> ,在这两种情况下,第 2 行的规范都不会导致缓存未命中; 第 3 行确实会导致缓存未命中. ARG CONT_IMG_VER导致 RUN 行被识别为与运行CONT_IMG_VER=<value> echo hello相同,因此如果<value>更改,我们会收到缓存未命中.

考虑同一命令行下的另一个示例:

FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=$CONT_IMG_VER
RUN echo $CONT_IMG_VER

在此示例中,缓存未命中发生在第 3 行.未命中是因为ENV中的变量值引用了ARG变量,并且该变量通过命令行进行了更改. 在此示例中, ENV命令使图像包含该值.

如果ENV指令覆盖了同名的ARG指令,例如这个 Dockerfile:

FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=hello
RUN echo $CONT_IMG_VER

第 3 行不会导致缓存未命中,因为CONT_IMG_VER的值是一个常量 ( hello ). 因此,在RUN (第 4 行)上使用的环境变量和值在构建之间不会发生变化.

ONBUILD

ONBUILD <INSTRUCTION>

ONBUILD指令将触发指令添加到映像中,以便稍后执行,此时映像用作另一个构建的基础. 触发器将在下游构建的上下文中执行,就好像它是在下游Dockerfile中的FROM指令之后立即插入的一样.

任何构建指令都可以注册为触发器.

如果您正在构建将用作构建其他镜像的基础的镜像,例如应用程序构建环境或可以使用用户特定配置自定义的守护程序,这将非常有用.

例如,如果您的图像是一个可重用的 Python 应用程序构建器,则需要将应用程序源代码添加到特定目录中,然后可能需要调用构建脚本. 您现在不能只调用ADDRUN ,因为您还没有访问应用程序源代码的权限,而且每个应用程序构建都会有所不同. 您可以简单地为应用程序开发人员提供样板Dockerfile以将其复制粘贴到他们的应用程序中,但这效率低下、容易出错且难以更新,因为它与特定于应用程序的代码混合在一起.

解决方案是使用ONBUILD注册高级指令,以便稍后在下一个构建阶段运行.

以下是它的工作原理:

  1. 当遇到ONBUILD指令时,构建器将触发器添加到正在构建的图像的元数据中. 该指令不会影响当前的构建.
  2. 在构建结束时,所有触发器的列表存储在映像清单中,位于键OnBuild下. 可以使用docker inspect命令检查它们.
  3. Later the image may be used as a base for a new build, using the FROM instruction. As part of processing the FROM instruction, the downstream builder looks for ONBUILD triggers, and executes them in the same order they were registered. If any of the triggers fail, the FROM instruction is aborted which in turn causes the build to fail. If all triggers succeed, the FROM instruction completes and the build continues as usual.
  4. 触发器在执行后会从最终图像中清除. 换句话说,它们不会被"孙子"构建继承.

例如,您可以添加如下内容:

ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src

Warning

不允许使用ONBUILD ONBUILD链接ONBUILD指令.

Warning

ONBUILD指令可能不会触发FROMMAINTAINER指令.

STOPSIGNAL

STOPSIGNAL signal

STOPSIGNAL指令设置系统调用信号,该信号将被发送到容器以退出. 该信号可以是格式为SIG<NAME>的信号名称,例如SIGKILL ,也可以是匹配内核系统调用表中某个位置的无符号数字,例如9 . 如果未定义,则默认为SIGTERM .

可以使用docker rundocker create上的--stop-signal标志来覆盖每个容器的映像的默认停止信号.

HEALTHCHECK

HEALTHCHECK指令有两种形式:

  • HEALTHCHECK [OPTIONS] CMD command (通过在容器内运行命令检查容器运行状况)
  • HEALTHCHECK NONE (禁用从基础映像继承的任何运行状况检查)

HEALTHCHECK指令告诉 Docker 如何测试容器以检查它是否仍在工作. 这可以检测诸如 Web 服务器陷入无限循环并且无法处理新连接的情况,即使服务器进程仍在运行.

当容器指定了健康检查时,除了正常状态外,它还具有健康状态. 此状态最初为starting . 每当健康检查通过时,它就会变得healthy (无论它以前处于什么状态). 在连续失败一定次数后,它变得unhealthy .

The options that can appear before CMD are:

  • --interval=DURATION (default: 30s)
  • --timeout=DURATION (default: 30s)
  • --start-period=DURATION (default: 0s)
  • --retries=N (default: 3)

健康检查将首先在容器启动后运行间隔秒,然后在每次之前的检查完成后再次运行间隔秒.

如果单次运行检查花费的时间超过timeout秒,则认为检查失败.

容器需要重试健康检查的连续失败才能被视为unhealthy .

start period为需要时间引导的容器提供初始化时间. 在此期间探测失败将不计入最大重试次数. 但是,如果在启动期间健康检查成功,则认为容器已启动,所有连续失败将计入最大重试次数.

Dockerfile 中只能有一条HEALTHCHECK指令. 如果您列出多个,则只有最后一个HEALTHCHECK才会生效.

CMD关键字后面的命令可以是 shell 命令(例如HEALTHCHECK CMD /bin/check-running )或exec数组(与其他 Dockerfile 命令一样;有关详细信息,请参见例如ENTRYPOINT ).

该命令的退出状态指示容器的健康状态. 可能的值是:

  • 0:成功 - 容器健康且可以使用
  • 1:不健康 - 容器工作不正常
  • 2:保留 - 不要使用此退出代码

例如,每隔五分钟左右检查一次网络服务器是否能够在三秒内为网站的主页提供服务:

HEALTHCHECK --interval=5m --timeout=3s \
  CMD curl -f http://localhost/ || exit 1

为了帮助调试失败的探针,该命令在 stdout 或 stderr 上写入的任何输出文本(UTF-8 编码)都将存储在健康状态中,并且可以使用docker inspect进行查询. 此类输出应保持简短(当前仅存储前 4096 个字节).

当容器的健康状态发生变化时,会生成一个带有新状态的health_status事件.

SHELL

SHELL ["executable", "parameters"]

SHELL指令允许覆盖用于命令的shell形式的默认 shell. Linux 上的默认 shell 是["/bin/sh", "-c"] ,Windows 上是["cmd", "/S", "/C"] . SHELL指令必须以 JSON 格式写入 Dockerfile.

SHELL指令在 Windows 上特别有用,其中有两个常用且完全不同的原生 shell: cmdpowershell ,以及可用的备用 shell,包括sh .

SHELL指令可以出现多次. 每个SHELL指令都会覆盖所有先前的SHELL指令,并影响所有后续指令. 例如:

FROM microsoft/windowsservercore

# Executed as cmd /S /C echo default
RUN echo default

# Executed as cmd /S /C powershell -command Write-Host default
RUN powershell -command Write-Host default

# Executed as powershell -command Write-Host hello
SHELL ["powershell", "-command"]
RUN Write-Host hello

# Executed as cmd /S /C echo hello
SHELL ["cmd", "/S", "/C"]
RUN echo hello

在 Dockerfile 中使用它们的shell形式时, SHELL指令会影响以下指令: RUNCMDENTRYPOINT .

以下示例是在 Windows 上发现的常见模式,可以使用SHELL指令对其进行简化:

RUN powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"

docker调用的命令将是:

cmd /S /C powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"

由于两个原因,这是低效的. 首先,调用了一个不必要的 cmd.exe 命令处理器(又名 shell). 其次, shell形式的每条RUN指令都需要一个额外的powershell -command作为命令的前缀.

为了提高效率,可以采用两种机制之一. 一种是使用 JSON 形式的 RUN 命令,例如:

RUN ["powershell", "-command", "Execute-MyCmdlet", "-param1 \"c:\\foo.txt\""]

虽然 JSON 格式是明确的并且不使用不必要的 cmd.exe,但它确实需要通过双引号和转义来增加详细信息. 另一种机制是使用SHELL指令和shell形式,为 Windows 用户提供更自然的语法,尤其是与escape解析器指令结合使用时:

# escape=`

FROM microsoft/nanoserver
SHELL ["powershell","-command"]
RUN New-Item -ItemType Directory C:\Example
ADD Execute-MyCmdlet.ps1 c:\example\
RUN c:\example\Execute-MyCmdlet -sample 'hello world'

导致:

PS E:\myproject> docker build -t shell .

Sending build context to Docker daemon 4.096 kB
Step 1/5 : FROM microsoft/nanoserver
 ---> 22738ff49c6d
Step 2/5 : SHELL powershell -command
 ---> Running in 6fcdb6855ae2
 ---> 6331462d4300
Removing intermediate container 6fcdb6855ae2
Step 3/5 : RUN New-Item -ItemType Directory C:\Example
 ---> Running in d0eef8386e97


    Directory: C:\


Mode         LastWriteTime              Length Name
----         -------------              ------ ----
d-----       10/28/2016  11:26 AM              Example


 ---> 3f2fbf1395d9
Removing intermediate container d0eef8386e97
Step 4/5 : ADD Execute-MyCmdlet.ps1 c:\example\
 ---> a955b2621c31
Removing intermediate container b825593d39fc
Step 5/5 : RUN c:\example\Execute-MyCmdlet 'hello world'
 ---> Running in be6d8e63fe75
hello world
 ---> 8e559e9bf424
Removing intermediate container be6d8e63fe75
Successfully built 8e559e9bf424
PS E:\myproject>

SHELL指令也可用于修改 shell 的操作方式. 例如,在 Windows 上使用SHELL cmd /S /C /V:ON|OFF ,可以修改延迟的环境变量扩展语义.

如果需要备用 shell,例如zshcshtcsh等,也可以在 Linux 上使用SHELL指令.

Dockerfile examples

有关 Dockerfile 的示例,请参阅:

builder, docker, Dockerfile, automation, 图像创作

by  icopy.site