Dockerfile 参考

Docker可以通过从Dockerfile中读取指令自动构建镜像。Dockerfile是一个文本文档,包含了用户在命令行中调用的所有命令来组装镜像。本页描述了您可以在Dockerfile中使用的命令。

概述

Dockerfile 支持以下指令:

InstructionDescription
ADDAdd local or remote files and directories.
ARGUse build-time variables.
CMDSpecify default commands.
COPYCopy files and directories.
ENTRYPOINTSpecify default executable.
ENVSet environment variables.
EXPOSEDescribe which ports your application is listening on.
FROMCreate a new build stage from a base image.
HEALTHCHECKCheck a container's health on startup.
LABELAdd metadata to an image.
MAINTAINERSpecify the author of an image.
ONBUILDSpecify instructions for when the image is used in a build.
RUNExecute build commands.
SHELLSet the default shell of an image.
STOPSIGNALSpecify the system call signal for exiting a container.
USERSet user and group ID.
VOLUMECreate volume mounts.
WORKDIRChange working directory.

格式

以下是Dockerfile的格式:

# Comment
INSTRUCTION arguments

指令不区分大小写。然而,按照惯例,它们通常是大写的,以便更容易与参数区分开来。

Docker 按照 Dockerfile 中的指令顺序运行。Dockerfile 必须以 FROM 指令开头。这可能是在 解析器指令注释 和全局范围的 ARGs 之后。FROM 指令指定了 基础镜像,你将从该镜像开始构建。FROM 指令之前只能有一个或多个 ARG 指令,这些指令声明了在 Dockerfile 的 FROM 行中使用的参数。

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

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

在执行Dockerfile指令之前,注释行会被移除。 在以下示例中,注释会在shell执行echo命令之前被移除。

RUN echo hello \
# comment
world

以下示例是等效的。

RUN echo hello \
world

注释不支持行继续字符。

注意

关于空格的注意事项

为了向后兼容,注释(#)和指令(如RUN)前的空白字符会被忽略,但不推荐这样做。在这些情况下,前导空白字符不会被保留,因此以下示例是等价的:

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

然而,指令参数中的空白不会被忽略。 以下示例打印带有指定前导空格的hello world

RUN echo "\
     hello\
     world"

解析器指令

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

支持以下解析器指令:

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

解析器指令键,如syntaxcheck,不区分大小写,但按照惯例它们是小写的。指令的值是区分大小写的,并且必须按照指令的适当大小写书写。例如,#check=skip=jsonargsrecommended是无效的,因为检查名称必须使用帕斯卡命名法,而不是小写。按照惯例,任何解析器指令后都应包含一个空行。解析器指令中不支持行继续字符。

由于这些规则,以下示例都是无效的:

由于行继续无效:

# direc \
tive=value

由于出现两次而无效:

# directive=value1
# directive=value2

FROM ImageName

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

FROM ImageName
# directive=value

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

# About my dockerfile
# directive=value
FROM ImageName

以下unknowndirective被视为注释,因为它未被识别。已知的syntax指令被视为注释,因为它出现在一个不是解析器指令的注释之后。

# unknowndirective=value
# syntax=value

解析器指令中允许存在非换行空白符。因此,以下行都被视为相同:

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

语法

使用syntax解析器指令来声明用于构建的Dockerfile语法版本。如果未指定,BuildKit将使用捆绑的Dockerfile前端版本。声明语法版本可以让你自动使用最新的Dockerfile版本,而无需升级BuildKit或Docker Engine,甚至可以使用自定义的Dockerfile实现。

大多数用户会希望将此解析器指令设置为docker/dockerfile:1,这会导致BuildKit在构建之前拉取最新稳定版本的Dockerfile语法。

# syntax=docker/dockerfile:1

有关解析器指令如何工作的更多信息,请参阅 自定义Dockerfile语法

escape

# escape=\

或者

# escape=`

escape 指令设置用于在 Dockerfile 中转义字符的字符。如果未指定,默认的转义字符是 \

转义字符用于转义行中的字符以及转义换行符。这使得Dockerfile指令可以跨越多行。请注意,无论Dockerfile中是否包含escape解析器指令,除了在行尾之外,在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>

check

# check=skip=<checks|all>
# check=error=<boolean>

check 指令用于配置如何评估 构建检查 。默认情况下,所有检查都会运行,失败被视为警告。

您可以使用#check=skip=来禁用特定的检查。要指定多个要跳过的检查,请用逗号分隔它们:

# check=skip=JSONArgsRecommended,StageNameCasing

要禁用所有检查,请使用 #check=skip=all

默认情况下,即使有警告,构建失败的构建检查也会以零状态码退出。要使构建在警告时失败,请设置#check=error=true

# check=error=true

注意

当使用check指令时,带有error=true选项,建议将 Dockerfile语法固定到特定版本。否则,当未来版本中添加新检查时,您的构建可能会开始失败。

要结合使用skiperror选项,请使用分号将它们分开:

# check=skip=JSONArgsRecommended;error=true

要查看所有可用的检查,请参阅 构建检查参考。 请注意,可用的检查取决于Dockerfile语法版本。为了确保您获得最新的检查,请使用 syntax 指令将Dockerfile语法版本指定为最新的稳定版本。

环境替换

环境变量(使用 the ENV statement声明)也可以在某些指令中用作由Dockerfile解释的变量。转义也用于将类似变量的语法包含到语句中。

环境变量在Dockerfile中用$variable_name${variable_name}表示。它们的处理方式是等价的,通常使用大括号语法来解决变量名中没有空格的问题,例如${foo}_bar

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

  • ${variable:-word} 表示如果 variable 已设置,则结果将是该值。如果 variable 未设置,则结果将是 word
  • ${variable:+word} 表示如果 variable 被设置,则 word 将是结果,否则结果为空字符串。

在Dockerfile语法的预发布版本中,当在Dockerfile中使用# syntax=docker/dockerfile-upstream:master语法指令时,支持以下变量替换:

  • ${variable#pattern}variable 的开头开始查找,并移除与 pattern 最短匹配的部分。

    str=foobarbaz echo ${str#f*b}     # arbaz
  • ${variable##pattern}variable 中移除最长的 pattern 匹配,从字符串的开头开始查找。

    str=foobarbaz echo ${str##f*b}    # az
  • ${variable%pattern}variable 中移除与 pattern 最短匹配的部分,从字符串的末尾向前搜索。

    string=foobarbaz echo ${string%b*}    # foobar
  • ${variable%%pattern}variable 中移除最长的 pattern 匹配,从字符串的末尾向前搜索。

    string=foobarbaz echo ${string%%b*}   # foo
  • ${variable/pattern/replacement}variable 中第一个出现的 pattern 替换为 replacement

    string=foobarbaz echo ${string/ba/fo}  # fooforbaz
  • ${variable//pattern/replacement}variable 中所有出现的 pattern 替换为 replacement

    string=foobarbaz echo ${string//ba/fo}  # fooforfoz

在所有情况下,word 可以是任何字符串,包括额外的环境变量。

pattern 是一个全局模式,其中 ? 匹配任何单个字符,* 匹配任意数量的字符(包括零个)。要匹配字面的 ?*,请使用反斜杠转义:\?\*

你可以通过在变量前添加\来转义整个变量名:例如,\$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(当与上述支持的一个指令结合使用时)

你也可以在RUNCMDENTRYPOINT指令中使用环境变量,但在这些情况下,变量替换是由命令shell处理的,而不是构建器。请注意,使用exec形式的指令不会自动调用命令shell。参见变量替换

环境变量替换在整个指令中对每个变量使用相同的值。更改变量的值仅在后续指令中生效。考虑以下示例:

ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc
  • def 的值变为 hello
  • ghi 的值变为 bye

.dockerignore 文件

你可以使用.dockerignore文件来从构建上下文中排除文件和目录。更多信息,请参见.dockerignore文件

Shell 和 exec 形式

RUNCMDENTRYPOINT 指令都有两种可能的形式:

  • INSTRUCTION ["executable","param1","param2"] (执行形式)
  • INSTRUCTION command param1 param2 (shell 形式)

exec 形式使得可以避免 shell 字符串处理,并使用特定的命令 shell 或其他可执行文件来调用命令。它使用 JSON 数组语法,其中数组中的每个元素都是一个命令、标志或参数。

shell形式更为宽松,强调易用性、灵活性和可读性。shell形式自动使用命令shell,而exec形式则不使用。

执行形式

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

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

exec 形式最适合用于指定 ENTRYPOINT 指令,结合 CMD 来设置可以在运行时覆盖的默认参数。有关更多信息,请参阅 ENTRYPOINT

变量替换

使用exec形式不会自动调用命令shell。这意味着正常的shell处理,如变量替换,不会发生。例如,RUN [ "echo", "$HOME" ]不会处理$HOME的变量替换。

如果你想要进行shell处理,那么要么使用shell形式,要么直接使用exec形式执行一个shell,例如:RUN [ "sh", "-c", "echo $HOME" ]。当使用exec形式并直接执行shell时,就像shell形式的情况一样,是shell在进行环境变量替换,而不是构建器。

反斜杠

在 exec 形式中,你必须转义反斜杠。这在 Windows 上尤其重要,因为反斜杠是路径分隔符。否则,以下行由于不是有效的 JSON 而被视为 shell 形式,并以意外的方式失败:

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

这个例子的正确语法是:

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

Shell 形式

与exec形式不同,使用shell形式的指令总是使用命令shell。shell形式不使用JSON数组格式,而是使用常规字符串。shell形式的字符串允许您使用转义字符(默认为反斜杠)来转义换行符,以便将单个指令延续到下一行。这使得在处理较长的命令时更容易使用,因为它允许您将它们拆分为多行。例如,考虑以下两行:

RUN source $HOME/.bashrc && \
echo $HOME

它们等同于以下行:

RUN source $HOME/.bashrc && echo $HOME

你也可以使用heredocs与shell形式来分解支持的命令。

RUN <<EOF
source $HOME/.bashrc && \
echo $HOME
EOF

有关heredocs的更多信息,请参阅 Here-documents

使用不同的shell

你可以使用SHELL命令来更改默认的shell。例如:

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

欲了解更多信息,请参阅 SHELL

FROM

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

或者

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

或者

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

FROM 指令初始化一个新的构建阶段并设置后续指令的基础镜像。因此,一个有效的 Dockerfile 必须以 FROM 指令开始。镜像可以是任何有效的镜像。

  • ARG 是 Dockerfile 中唯一可以在 FROM 之前出现的指令。 参见 了解 ARG 和 FROM 如何交互
  • FROM 可以在单个 Dockerfile 中多次出现,以创建多个镜像或使用一个构建阶段作为另一个构建阶段的依赖。只需在每个新的 FROM 指令之前记录由提交输出的最后一个镜像 ID。每个 FROM 指令都会清除之前指令创建的任何状态。
  • 可以选择通过向FROM指令添加AS name来为新构建阶段命名。该名称可以在后续的FROM COPY --from=RUN --mount=type=bind,from=指令中使用,以引用在此阶段构建的镜像。
  • tagdigest 的值是可选的。如果您省略其中任何一个,构建器默认会使用 latest 标签。如果构建器找不到 tag 值,它将返回一个错误。

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

理解ARG和FROM如何交互

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 指令将执行任何命令以在当前镜像之上创建一个新层。添加的层将在 Dockerfile 的下一步中使用。RUN 有两种形式:

# Shell form:
RUN [OPTIONS] <command> ...
# Exec form:
RUN [OPTIONS] [ "<command>", ... ]

有关这两种形式之间差异的更多信息,请参见 shell 或 exec 形式

shell形式是最常用的,它允许你将较长的指令分成多行,可以使用换行符 转义,或者使用 heredocs

RUN <<EOF
apt-get update
apt-get install -y curl
EOF

可用的[OPTIONS]对于RUN指令是:

OptionMinimum Dockerfile version
--mount1.2
--network1.3
--security1.1.2-labs

RUN指令的缓存失效

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

查看 Dockerfile 最佳实践 指南 以获取更多信息。

RUN 指令的缓存可能会被 ADDCOPY 指令失效。

RUN --mount

RUN --mount=[type=<TYPE>][,option=<value>[,option=<value>]...]

RUN --mount 允许你创建构建可以访问的文件系统挂载。 这可以用于:

  • 创建绑定挂载到主机文件系统或其他构建阶段
  • 访问构建机密或ssh-agent套接字
  • 使用持久化的包管理缓存来加速您的构建

支持的挂载类型有:

TypeDescription
bind (default)Bind-mount context directories (read-only).
cacheMount a temporary directory to cache directories for compilers and package managers.
tmpfsMount a tmpfs in the build container.
secretAllow the build container to access secure files such as private keys without baking them into the image or build cache.
sshAllow the build container to access SSH keys via SSH agents, with support for passphrases.

运行 --mount=type=bind

这种挂载类型允许将文件或目录绑定到构建容器。默认情况下,绑定挂载是只读的。

OptionDescription
target, dst, destination1Mount path.
sourceSource path in the from. Defaults to the root of the from.
fromBuild stage, context, or image name for the root of the source. Defaults to the build context.
rw,readwriteAllow writes on the mount. Written data will be discarded.

运行 --mount=type=cache

这种挂载类型允许构建容器缓存编译器和包管理器的目录。

OptionDescription
idOptional ID to identify separate/different caches. Defaults to value of target.
target, dst, destination1Mount path.
ro,readonlyRead-only if set.
sharingOne of shared, private, or locked. Defaults to shared. A shared cache mount can be used concurrently by multiple writers. private creates a new mount if there are multiple writers. locked pauses the second writer until the first one releases the mount.
fromBuild stage, context, or image name to use as a base of the cache mount. Defaults to empty directory.
sourceSubpath in the from to mount. Defaults to the root of the from.
modeFile mode for new cache directory in octal. Default 0755.
uidUser ID for new cache directory. Default 0.
gidGroup ID for new cache directory. Default 0.

缓存目录的内容在构建器调用之间保持不变,不会使指令缓存失效。缓存挂载应仅用于提高性能。您的构建应该能够处理缓存目录的任何内容,因为另一个构建可能会覆盖文件,或者如果需要更多存储空间,GC可能会清理它。

示例:缓存 Go 包

# syntax=docker/dockerfile:1
FROM golang
RUN --mount=type=cache,target=/root/.cache/go-build \
  go build ...

示例:缓存 apt 包

# syntax=docker/dockerfile:1
FROM ubuntu
RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
  --mount=type=cache,target=/var/lib/apt,sharing=locked \
  apt update && apt-get --no-install-recommends install -y gcc

Apt需要独占访问其数据,因此缓存使用选项sharing=locked,这将确保使用相同缓存挂载的多个并行构建会相互等待,而不会同时访问相同的缓存文件。如果您希望在这种情况下每个构建都创建另一个缓存目录,也可以使用sharing=private

运行 --mount=type=tmpfs

这种挂载类型允许在构建容器中挂载 tmpfs

OptionDescription
target, dst, destination1Mount path.
sizeSpecify an upper limit on the size of the filesystem.

RUN --mount=type=secret

这种挂载类型允许构建容器访问秘密值,例如令牌或私钥,而无需将它们烘焙到镜像中。

默认情况下,密钥以文件形式挂载。您也可以通过设置env选项将密钥作为环境变量挂载。

OptionDescription
idID of the secret. Defaults to basename of the target path.
target, dst, destinationMount the secret to the specified path. Defaults to /run/secrets/ + id if unset and if env is also unset.
envMount the secret to an environment variable instead of a file, or both. (since Dockerfile v1.10.0)
requiredIf set to true, the instruction errors out when the secret is unavailable. Defaults to false.
modeFile mode for secret file in octal. Default 0400.
uidUser ID for secret file. Default 0.
gidGroup ID for secret file. Default 0.

示例:访问S3

# syntax=docker/dockerfile:1
FROM python:3
RUN pip install awscli
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials \
  aws s3 cp s3://... ...
$ docker buildx build --secret id=aws,src=$HOME/.aws/credentials .

示例:作为环境变量挂载

以下示例将密钥 API_KEY 挂载为同名的环境变量。

# syntax=docker/dockerfile:1
FROM alpine
RUN --mount=type=secret,id=API_KEY,env=API_KEY \
    some-command --token-from-env $API_KEY

假设在构建环境中设置了API_KEY环境变量,您可以使用以下命令进行构建:

$ docker buildx build --secret id=API_KEY .

运行 --mount=type=ssh

这种挂载类型允许构建容器通过SSH代理访问SSH密钥,并支持密码短语。

OptionDescription
idID of SSH agent socket or key. Defaults to "default".
target, dst, destinationSSH agent socket path. Defaults to /run/buildkit/ssh_agent.${N}.
requiredIf set to true, the instruction errors out when the key is unavailable. Defaults to false.
modeFile mode for socket in octal. Default 0600.
uidUser ID for socket. Default 0.
gidGroup ID for socket. Default 0.

示例:访问 GitLab

# syntax=docker/dockerfile:1
FROM alpine
RUN apk add --no-cache openssh-client
RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan gitlab.com >> ~/.ssh/known_hosts
RUN --mount=type=ssh \
  ssh -q -T git@gitlab.com 2>&1 | tee /hello
# "Welcome to GitLab, @GITLAB_USERNAME_ASSOCIATED_WITH_SSHKEY" should be printed here
# with the type of build progress is defined as `plain`.
$ eval $(ssh-agent)
$ ssh-add ~/.ssh/id_rsa
(Input your passphrase here)
$ docker buildx build --ssh default=$SSH_AUTH_SOCK .

你也可以直接在主机上指定一个路径到*.pem文件,而不是使用$SSH_AUTH_SOCK。 但是,不支持带有密码短语的pem文件。

RUN --network

RUN --network=<TYPE>

RUN --network 允许控制命令在哪个网络环境中运行。

支持的网络类型有:

TypeDescription
default (default)Run in the default network.
noneRun with no network access.
hostRun in the host's network environment.

运行 --network=默认

相当于根本不提供标志,命令在构建的默认网络中运行。

RUN --network=none

该命令在没有网络访问的情况下运行(lo 仍然可用,但仅限于此进程)

示例:隔离外部影响

# syntax=docker/dockerfile:1
FROM python:3.6
ADD mypackage.tgz wheels/
RUN --network=none pip install --find-links wheels mypackage

pip 只能安装 tarfile 中提供的包,这些包可以通过早期的构建阶段进行控制。

RUN --network=host

该命令在主机的网络环境中运行(类似于 docker build --network=host,但是基于每个指令)

警告

使用--network=host受到network.host权限的保护, 在启动buildkitd守护进程时需要使用 --allow-insecure-entitlement network.host标志或在 buildkitd配置中启用, 并且在构建请求中使用 --allow network.host标志

RUN --security

注意

尚未在稳定语法中提供,请使用 docker/dockerfile:1-labs 版本。

RUN --security=<sandbox|insecure>

默认的安全模式是sandbox。 使用--security=insecure时,构建器在不安全的模式下运行命令,没有沙箱,这允许运行需要提升权限的流程(例如containerd)。 这相当于运行docker run --privileged

警告

为了访问此功能,应在启动buildkitd守护进程时启用权限security.insecure,使用--allow-insecure-entitlement security.insecure标志或在buildkitd配置中启用,并在构建请求中使用--allow security.insecure标志

默认的沙盒模式可以通过--security=sandbox激活,但这没有实际效果。

示例:检查权限

# syntax=docker/dockerfile:1-labs
FROM ubuntu
RUN --security=insecure cat /proc/self/status | grep CapEff
#84 0.093 CapEff:	0000003fffffffff

CMD

CMD 指令设置了从镜像运行容器时要执行的命令。

你可以使用shell或exec形式来指定CMD指令:

  • CMD ["executable","param1","param2"] (执行形式)
  • CMD ["param1","param2"] (执行形式,作为ENTRYPOINT的默认参数)
  • CMD command param1 param2 (shell 形式)

在Dockerfile中只能有一个CMD指令。如果你列出了多个CMD,只有最后一个会生效。

CMD 的目的是为执行中的容器提供默认值。这些默认值可以包括一个可执行文件,或者它们可以省略可执行文件,在这种情况下,您还必须指定一个 ENTRYPOINT 指令。

如果您希望容器每次运行相同的可执行文件,那么您应该考虑使用ENTRYPOINTCMD结合使用。参见ENTRYPOINT。如果用户为docker run指定了参数,那么它们将覆盖CMD中指定的默认值,但仍将使用默认的ENTRYPOINT

如果使用CMDENTRYPOINT指令提供默认参数,则CMDENTRYPOINT指令都应在exec形式中指定。

注意

不要混淆RUNCMDRUN实际上是运行一个命令并提交结果;CMD在构建时不会执行任何操作,但指定了镜像的预期命令。

标签

LABEL <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"

注意

请务必使用双引号而不是单引号。特别是在使用字符串插值时(例如LABEL example="foo-$ENV_VAR"),单引号会将字符串原样处理,而不会解析变量的值。

基础镜像中包含的标签(在FROM行中的镜像)会被你的镜像继承。如果标签已经存在但具有不同的值,则最近应用的值将覆盖任何先前设置的值。

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

$ docker image inspect --format='{{json .Config.Labels}}' 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 (已弃用)

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

在这种情况下,如果你在docker run中使用-P,端口将分别暴露给TCP和UDP。请记住,-P使用主机上的临时高位端口,因此TCP和UDP不会使用相同的端口。

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

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

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

ENV

ENV <key>=<value> [<key>=<value>...]

ENV 指令将环境变量 设置为值 。该值将在构建阶段的所有后续指令中存在于环境中,并且可以在许多情况下进行内联替换。该值将用于解释其他环境变量,因此如果未转义,引号字符将被删除。与命令行解析类似,引号和反斜杠可用于在值中包含空格。

示例:

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

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

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

使用ENV设置的环境变量在从生成的镜像运行容器时将保持不变。您可以使用docker inspect查看这些值,并使用docker run --env =更改它们。

一个阶段会继承其父阶段或任何祖先阶段使用ENV设置的任何环境变量。有关更多信息,请参阅手册中的多阶段构建部分

环境变量的持久化可能会导致意外的副作用。例如,设置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 ...

注意

替代语法

ENV 指令还允许使用另一种语法 ENV ,省略 =。例如:

ENV MY_VAR my-value

这种语法不允许在单个ENV指令中设置多个环境变量,并且可能会引起混淆。例如,以下代码设置了一个环境变量(ONE),其值为"TWO= THREE=world"

ENV ONE TWO= THREE=world

为了向后兼容,支持替代语法,但由于上述原因不推荐使用,并可能在未来的版本中移除。

ADD

ADD 有两种形式。 对于包含空格的路径,需要使用后一种形式。

ADD [OPTIONS] <src> ... <dest>
ADD [OPTIONS] ["<src>", ... "<dest>"]

可用的 [OPTIONS] 有:

OptionMinimum Dockerfile version
--keep-git-dir1.1
--checksum1.6
--chown
--chmod1.2
--link1.4
--exclude1.7-labs

ADD 指令从 复制新文件或目录,并将它们添加到镜像文件系统的 路径中。文件和目录可以从构建上下文、远程URL或Git仓库中复制。

ADDCOPY 指令在功能上相似,但用途略有不同。 了解更多关于 ADDCOPY 之间的区别

来源

您可以使用ADD指定多个源文件或目录。最后一个参数必须始终是目标。例如,要将构建上下文中的两个文件file1.txtfile2.txt添加到构建容器中的/usr/src/things/目录:

ADD file1.txt file2.txt /usr/src/things/

如果您指定多个源文件,无论是直接指定还是使用通配符,那么目标必须是一个目录(必须以斜杠/结尾)。

要从远程位置添加文件,您可以指定URL或Git仓库的地址作为源。例如:

ADD https://example.com/archive.zip /usr/src/things/
ADD git@github.com:user/repo.git /usr/src/things/

BuildKit 检测 的类型并相应地处理它。

从构建上下文中添加文件

任何不以http://https://git@协议前缀开头的相对或本地路径都被视为本地文件路径。本地文件路径相对于构建上下文。例如,如果构建上下文是当前目录,ADD file.txt /会将./file.txt文件添加到构建容器中文件系统的根目录。

当从构建上下文中添加源文件时,它们的路径被解释为相对于上下文根目录的路径。如果您指定了一个导致构建上下文之外的相对路径,例如ADD ../something /something,父目录路径将自动被剥离。在此示例中,有效的源路径变为ADD something /something

如果源是一个目录,则复制目录的内容,包括文件系统元数据。目录本身不会被复制,只会复制其内容。如果它包含子目录,这些子目录也会被复制,并与目标位置的任何现有目录合并。任何冲突都会以添加的内容为准,逐文件解决,除非你试图将一个目录复制到现有文件上,这种情况下会引发错误。

如果源是一个文件,文件及其元数据将被复制到目标位置。文件权限将被保留。如果源是一个文件,并且在目标位置存在一个同名的目录,则会引发错误。

如果你通过标准输入传递一个Dockerfile来构建(docker build - < Dockerfile),则没有构建上下文。在这种情况下,你只能使用ADD指令来复制远程文件。你也可以通过标准输入传递一个tar归档文件:(docker build - < archive.tar),归档文件根目录下的Dockerfile和归档文件的其余部分将用作构建的上下文。

模式匹配

对于本地文件,每个可能包含通配符,并且将使用Go的filepath.Match规则进行匹配。

例如,要添加构建上下文根目录中所有以.png结尾的文件和目录:

ADD *.png /dest/

在以下示例中,? 是一个单字符通配符,匹配例如 index.jsindex.ts

ADD index.?s /dest/

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

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

添加本地tar存档

当使用本地tar存档作为ADD的源时,如果存档是识别的压缩格式(gzipbzip2xz,或未压缩的),存档将被解压缩并提取到指定的目标位置。只有本地tar存档会被提取。如果tar存档是远程URL,存档不会被提取,而是被下载并放置在目标位置。

当提取一个目录时,它的行为与tar -x相同。 结果是以下内容的联合:

  1. 目标路径上存在的任何内容,以及
  2. 源树的内容,在逐个文件的基础上,以添加的内容为准解决冲突。

注意

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

从URL添加文件

在源文件是远程文件URL的情况下,目标文件将具有600的权限。如果HTTP响应包含Last-Modified头,则来自该头的时间戳将用于设置目标文件的mtime。然而,与ADD期间处理的任何其他文件一样,mtime不包括在确定文件是否已更改以及缓存是否应更新的过程中。

如果目标路径以斜杠结尾,则文件名将从URL路径推断出来。例如,ADD http://example.com/foobar / 将创建文件 /foobar。URL必须具有非平凡的路径,以便可以发现适当的文件名(http://example.com 不起作用)。

如果目标路径不以斜杠结尾,目标路径将成为从URL下载的文件名。例如,ADD http://example.com/foo /bar 创建文件 /bar

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

从Git仓库添加文件

要将Git仓库用作ADD的源,您可以将仓库的HTTP或SSH地址引用为源。仓库将被克隆到镜像中的指定目标位置。

ADD https://github.com/user/repo.git /mydir/

您可以使用URL片段来指定特定的分支、标签、提交或子目录。例如,要添加buildkit仓库的v0.14.1标签的docs目录:

ADD git@github.com:moby/buildkit.git#v0.14.1:docs /buildkit-docs

有关 Git URL 片段的更多信息,请参阅 URL 片段

从Git仓库添加时,文件的权限位为644。如果仓库中的文件设置了可执行位,则其权限将设置为755。目录的权限设置为755。

当使用Git仓库作为源时,仓库必须可以从构建上下文中访问。要通过SSH添加仓库,无论是公共还是私有的,您必须传递一个SSH密钥进行身份验证。例如,给定以下Dockerfile:

# syntax=docker/dockerfile:1
FROM alpine
ADD git@git.example.com:foo/bar.git /bar

要构建这个Dockerfile,请将--ssh标志传递给docker build,以便将SSH代理套接字挂载到构建中。例如:

$ docker build --ssh default .

有关使用秘密构建的更多信息,请参阅 构建秘密

目的地

如果目标路径以正斜杠开头,它将被解释为绝对路径,并且源文件将被复制到相对于当前构建阶段根目录的指定目标位置。

# create /abs/test.txt
ADD test.txt /abs/

尾随斜杠是重要的。例如,ADD test.txt /abs/abs 创建一个文件,而 ADD test.txt /abs/ 创建 /abs/test.txt

如果目标路径不是以斜杠开头,它将被解释为相对于构建容器的工作目录。

WORKDIR /usr/src/app
# create /usr/src/app/rel/test.txt
ADD test.txt rel/

如果目标不存在,则会创建它,并创建其路径中所有缺失的目录。

如果源是一个文件,并且目标路径不以斜杠结尾,源文件将被写入目标路径作为一个文件。

ADD --keep-git-dir

ADD [--keep-git-dir=<boolean>] <src> ... <dir>

是远程 Git 仓库的 HTTP 或 SSH 地址时, BuildKit 默认将 Git 仓库的内容添加到镜像中, 不包括 .git 目录。

--keep-git-dir=true 标志允许你保留 .git 目录。

# syntax=docker/dockerfile:1
FROM alpine
ADD --keep-git-dir=true https://github.com/moby/buildkit.git#v0.10.1 /buildkit

ADD --checksum

ADD [--checksum=<hash>] <src> ... <dir>

--checksum 标志允许您验证远程资源的校验和。校验和的格式为 :。支持的算法有 sha256sha384sha512

ADD --checksum=sha256:24454f830cdb571e2c4ad15481119c43b3cafd48dd869a9b2945d1036d1dc68d https://mirrors.edge.kernel.org/pub/linux/kernel/Historic/linux-0.01.tar.gz /

--checksum 标志仅支持 HTTP(S) 源。

ADD --chown --chmod

参见 COPY --chown --chmod.

参见 COPY --link

ADD --exclude

参见 COPY --exclude

COPY

COPY 有两种形式。 后者形式适用于包含空格的路径。

COPY [OPTIONS] <src> ... <dest>
COPY [OPTIONS] ["<src>", ... "<dest>"]

可用的 [OPTIONS] 有:

OptionMinimum Dockerfile version
--from
--chown
--chmod1.2
--link1.4
--parents1.7-labs
--exclude1.7-labs

COPY 指令从 复制新文件或目录,并将它们添加到镜像文件系统的 路径中。文件和目录可以从构建上下文、构建阶段、命名上下文或镜像中复制。

ADDCOPY 指令在功能上相似,但用途略有不同。 了解更多关于 ADDCOPY 之间的区别

来源

您可以使用COPY指定多个源文件或目录。最后一个参数必须始终是目标。例如,要将两个文件file1.txtfile2.txt从构建上下文复制到构建容器中的/usr/src/things/目录:

COPY file1.txt file2.txt /usr/src/things/

如果您指定多个源文件,无论是直接指定还是使用通配符,那么目标必须是一个目录(必须以斜杠/结尾)。

COPY 接受一个标志 --from=,允许你指定源位置为构建阶段、上下文或镜像。以下示例从名为 build 的阶段复制文件:

FROM golang AS build
WORKDIR /app
RUN --mount=type=bind,target=. go build -o /myapp ./cmd

COPY --from=build /myapp /usr/bin/

有关从命名源复制的更多信息,请参阅 --from 标志

从构建上下文中复制

从构建上下文中复制源文件时,它们的路径被解释为相对于上下文根目录的路径。如果您指定了一个导致构建上下文之外的相对路径,例如COPY ../something /something,父目录路径将自动被剥离。在这个例子中,有效的源路径变为COPY something /something

如果源是一个目录,则复制目录的内容,包括文件系统元数据。目录本身不会被复制,只会复制其内容。如果它包含子目录,这些子目录也会被复制,并与目标位置的任何现有目录合并。任何冲突都会以添加的内容为准,逐文件解决,除非你试图将一个目录复制到现有文件上,这种情况下会引发错误。

如果源是一个文件,文件及其元数据将被复制到目标位置。文件权限将被保留。如果源是一个文件,并且在目标位置存在一个同名的目录,则会引发错误。

如果你通过标准输入传递一个Dockerfile来构建(docker build - < Dockerfile),则没有构建上下文。在这种情况下,你只能使用 COPY指令从其他阶段、命名上下文或镜像中复制文件, 使用 --from 标志。你也可以通过标准输入传递一个tar归档文件: (docker build - < archive.tar),归档文件根目录中的Dockerfile和归档文件的其余部分将用作构建的上下文。

当使用Git仓库作为构建上下文时,复制的文件的权限位为644。如果仓库中的文件设置了可执行位,则其权限将设置为755。目录的权限设置为755。

模式匹配

对于本地文件,每个可能包含通配符,并且将使用Go的filepath.Match规则进行匹配。

例如,要添加构建上下文根目录中所有以.png结尾的文件和目录:

COPY *.png /dest/

在以下示例中,? 是一个单字符通配符,匹配例如 index.jsindex.ts

COPY index.?s /dest/

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

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

目的地

如果目标路径以正斜杠开头,它将被解释为绝对路径,并且源文件将被复制到相对于当前构建阶段根目录的指定目标位置。

# create /abs/test.txt
COPY test.txt /abs/

尾随斜杠是重要的。例如,COPY test.txt /abs/abs 创建一个文件,而 COPY test.txt /abs/ 则创建 /abs/test.txt

如果目标路径不是以斜杠开头,它将被解释为相对于构建容器的工作目录。

WORKDIR /usr/src/app
# create /usr/src/app/rel/test.txt
COPY test.txt rel/

如果目标不存在,则会创建它,并创建其路径中所有缺失的目录。

如果源是一个文件,并且目标路径不以斜杠结尾,源文件将被写入目标路径作为一个文件。

COPY --from

默认情况下,COPY 指令从构建上下文中复制文件。COPY --from 标志允许您从镜像、构建阶段或命名上下文中复制文件。

COPY [--from=<image|stage|context>] <src> ... <dest>

要从多阶段构建中的构建阶段复制,请指定您要从中复制的阶段的名称。您可以使用AS关键字与FROM指令来指定阶段名称。

# syntax=docker/dockerfile:1
FROM alpine AS build
COPY . .
RUN apk add clang
RUN clang -o /hello hello.c

FROM scratch
COPY --from=build /hello /

你也可以直接从命名的上下文(使用--build-context =指定)或镜像中复制文件。以下示例从官方的Nginx镜像中复制了一个nginx.conf文件。

COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf

COPY --from 的源路径总是从你指定的镜像或阶段的文件系统根目录解析。

COPY --chown --chmod

注意

目前仅支持八进制表示法。非八进制的支持在moby/buildkit#1951中跟踪。

COPY [--chown=<user>:<group>] [--chmod=<perms> ...] <src> ... <dest>

--chown--chmod 功能仅支持用于构建 Linux 容器的 Dockerfiles,并且在 Windows 容器上不起作用。由于用户和组所有权的概念在 Linux 和 Windows 之间无法转换,使用 /etc/passwd/etc/group 将用户和组名称转换为 ID 的功能限制了此功能仅适用于基于 Linux 操作系统的容器。

从构建上下文中复制的所有文件和目录都使用0的UID和GID创建,除非可选的--chown标志指定了给定的用户名、组名或UID/GID组合来请求复制内容的特定所有权。--chown标志的格式允许使用用户名和组名字符串或直接使用整数UID和GID的任何组合。提供用户名而不提供组名或提供UID而不提供GID将使用与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/
COPY --chown=myuser:mygroup --chmod=644 files* /somedir/

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

使用 Dockerfile 语法版本 1.10.0 及更高版本时, --chmod 标志支持变量插值, 这允许您使用构建参数定义权限位:

# syntax=docker/dockerfile:1.10
FROM alpine
WORKDIR /src
ARG MODE=440
COPY --chmod=$MODE . .
COPY [--link[=<boolean>]] <src> ... <dest>

COPYADD命令中启用此标志,允许您以增强的语义复制文件,使您的文件保持在自己的层上独立,并且在前一层上的命令更改时不会失效。

当使用--link时,您的源文件会被复制到一个空的目标目录中。该目录将变成一个层,链接到您之前的状态之上。

# syntax=docker/dockerfile:1
FROM alpine
COPY --link /foo /bar

相当于进行两次构建:

FROM alpine

FROM scratch
COPY /foo /bar

并将两个图像的所有层合并在一起。

使用 --link 在后续构建中重用已经构建的层,即使之前的层已经发生了变化,也可以与 --cache-from 一起使用。这对于多阶段构建尤其重要,因为在同一阶段中,如果之前的任何命令发生了变化,COPY --from 语句之前会失效,导致需要重新构建中间阶段。使用 --link 时,之前构建生成的层会被重用并合并到新的层之上。这也意味着当基础镜像收到更新时,你可以轻松地重新调整你的镜像,而不需要再次执行整个构建过程。在支持的后端中,BuildKit 可以在客户端和注册表之间无需推送或拉取任何层的情况下执行此重新调整操作。BuildKit 会检测到这种情况,并仅创建包含新层和旧层按正确顺序排列的新镜像清单。

当使用--link且没有其他需要访问基础镜像中文件的命令时,BuildKit可以避免拉取基础镜像的相同行为也可能发生。在这种情况下,BuildKit只会为COPY命令构建层,并将它们直接推送到基础镜像的层之上的注册表中。

--link=false 的不兼容性

当使用--link时,COPY/ADD命令不允许从先前的状态读取任何文件。这意味着如果在先前的状态中目标目录是一个包含符号链接的路径,COPY/ADD将无法跟随它。在最终镜像中,使用--link创建的目标路径将始终是一个仅包含目录的路径。

如果您不依赖于目标路径中符号链接的行为,建议始终使用--link--link的性能等同于或优于默认行为,并且它为缓存重用创造了更好的条件。

COPY --parents

注意

尚未在稳定语法中提供,请使用 docker/dockerfile:1.7-labs 版本。

COPY [--parents[=<boolean>]] <src> ... <dest>

--parents 标志保留 src 条目的父目录。此标志默认为 false

# syntax=docker/dockerfile:1-labs
FROM scratch

COPY ./x/a.txt ./y/a.txt /no_parents/
COPY --parents ./x/a.txt ./y/a.txt /parents/

# /no_parents/a.txt
# /parents/x/a.txt
# /parents/y/a.txt

这种行为类似于 Linux cp 工具的 --parentsrsync --relative 标志。

与Rsync一样,可以通过在源路径中插入一个点和一个斜杠(./)来限制保留哪些父目录。如果存在这样的点,则只有它之后的父目录才会被保留。这在阶段之间的复制中可能特别有用,特别是当使用--from时,源路径需要是绝对的。

# syntax=docker/dockerfile:1-labs
FROM scratch

COPY --parents ./x/./y/*.txt /parents/

# Build context:
# ./x/y/a.txt
# ./x/y/b.txt
#
# Output:
# /parents/y/a.txt
# /parents/y/b.txt

请注意,如果没有指定--parents标志,任何文件名冲突都会导致Linux的cp操作失败,并显示明确的错误消息(cp: will not overwrite just-created './x/a.txt' with './y/a.txt'),而Buildkit会默默地覆盖目标文件。

虽然可以保留仅包含一个src条目的COPY指令的目录结构,但通常更有利的是尽可能减少结果镜像中的层数。因此,使用--parents标志,Buildkit能够将多个COPY指令打包在一起,同时保持目录结构完整。

COPY --exclude

注意

尚未在稳定语法中提供,请使用 docker/dockerfile:1.7-labs 版本。

COPY [--exclude=<path> ...] <src> ... <dest>

--exclude 标志允许您指定要排除的文件的路径表达式。

路径表达式遵循与相同的格式,支持通配符并使用Go的filepath.Match规则进行匹配。例如,要添加所有以“hom”开头的文件,排除扩展名为.txt的文件:

# syntax=docker/dockerfile:1-labs
FROM scratch

COPY --exclude=*.txt hom* /mydir/

你可以为一个COPY指令多次指定--exclude选项。 多个--excludes是指不复制与其模式匹配的文件, 即使文件路径匹配中指定的模式。 要添加所有以"hom"开头的文件,排除扩展名为.txt.md的文件:

# syntax=docker/dockerfile:1-labs
FROM scratch

COPY --exclude=*.txt --exclude=*.md hom* /mydir/

ENTRYPOINT

一个ENTRYPOINT允许你配置一个将作为可执行文件运行的容器。

ENTRYPOINT 有两种可能的形式:

  • exec 形式,这是首选形式:

    ENTRYPOINT ["executable", "param1", "param2"]
  • shell 形式:

    ENTRYPOINT command param1 param2

有关不同形式的更多信息,请参阅 Shell 和 exec 形式

以下命令从nginx启动一个容器,使用其默认内容,监听端口80:

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

命令行参数传递给 docker run 将会附加在执行形式 ENTRYPOINT 的所有元素之后,并且会覆盖使用 CMD 指定的所有元素。

这允许将参数传递给入口点,即docker run -d会将-d参数传递给入口点。您可以使用docker run --entrypoint标志覆盖ENTRYPOINT指令。

ENTRYPOINT 的 shell 形式会阻止使用任何 CMD 命令行参数。它还会将你的 ENTRYPOINT 作为 /bin/sh -c 的子命令启动,这不会传递信号。这意味着可执行文件不会是容器的 PID 1,也不会接收 Unix 信号。在这种情况下,你的可执行文件不会从 docker stop 接收到 SIGTERM

只有Dockerfile中的最后一个ENTRYPOINT指令会生效。

Exec 形式 ENTRYPOINT 示例

你可以使用ENTRYPOINT的exec形式来设置相当稳定的默认命令和参数,然后使用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

你可以优雅地请求 top 使用 docker stop test 来关闭。

以下 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 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

注意

你可以使用--entrypoint来覆盖ENTRYPOINT设置, 但这只能设置要执行的二进制文件(不会使用sh -c)。

Shell 形式的 ENTRYPOINT 示例

你可以为ENTRYPOINT指定一个普通字符串,它将在/bin/sh -c中执行。 这种形式将使用shell处理来替换shell环境变量, 并且会忽略任何CMDdocker run命令行参数。 为了确保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

如果你忘记在ENTRYPOINT的开头添加exec

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

理解CMD和ENTRYPOINT如何交互

无论是CMD还是ENTRYPOINT指令,都定义了在运行容器时执行的命令。有一些规则描述了它们的协作方式。

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

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

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

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

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

No ENTRYPOINTENTRYPOINT exec_entry p1_entryENTRYPOINT ["exec_entry", "p1_entry"]
No CMDerror, not allowed/bin/sh -c exec_entry p1_entryexec_entry p1_entry
CMD ["exec_cmd", "p1_cmd"]exec_cmd p1_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry exec_cmd p1_cmd
CMD exec_cmd p1_cmd/bin/sh -c exec_cmd p1_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry /bin/sh -c exec_cmd p1_cmd

注意

如果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 run/myvol创建一个新的挂载点,并将greeting文件复制到新创建的卷中。

关于指定卷的注意事项

请记住以下关于Dockerfile中卷的事项。

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

    • a non-existing or empty directory
    • a drive other than C:
  • 在Dockerfile中更改卷:如果在声明卷后有任何构建步骤更改了卷中的数据,那么在使用旧版构建器时,这些更改将被丢弃。当使用Buildkit时,这些更改将被保留。

  • JSON格式化: 列表被解析为JSON数组。 你必须用双引号(")而不是单引号(')括住单词。

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

用户

USER <user>[:<group>]

USER <UID>[:<GID>]

USER 指令设置用户名(或 UID),并可选择设置用户组(或 GID),作为当前阶段剩余部分的默认用户和组。指定的用户用于 RUN 指令,并在运行时运行相关的 ENTRYPOINTCMD 命令。

请注意,当为用户指定组时,用户将拥有指定的组成员身份。任何其他配置的组成员身份将被忽略。

警告

当用户没有主要组时,图像(或接下来的指令)将以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>] [<name>[=<default value>]...]

ARG 指令定义了一个变量,用户可以在构建时通过 docker build 命令使用 --build-arg = 标志传递给构建器。

警告

不建议使用构建参数来传递用户凭证、API令牌等秘密信息。构建参数在docker history命令和max模式来源证明中可见,如果您使用Buildx GitHub Actions并且您的GitHub仓库是公开的,这些证明会默认附加到镜像上。

请参考 RUN --mount=type=secret 部分, 了解在构建镜像时使用密钥的安全方法。

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

FROM busybox
ARG user1
ARG buildno
# ...

默认值

一个 ARG 指令可以选择性地包含一个默认值:

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

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

范围

一个ARG变量从它在Dockerfile中声明的那一行开始生效。例如,考虑这个Dockerfile:

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

用户通过调用以下内容构建此文件:

$ docker build --build-arg username=what_user .
  • 第2行的USER指令评估为some_user回退值, 因为username变量尚未声明。
  • 在第3行声明了username变量,并且从那时起可以在Dockerfile指令中引用。
  • 第4行的USER指令评估为what_user,因为在那个时刻,username参数的值为what_user,这是在命令行中传递的。在通过ARG指令定义之前,任何变量的使用都会导致一个空字符串。

在构建阶段内声明的ARG变量会自动被基于该阶段的其他阶段继承。不相关的构建阶段无法访问该变量。要在多个不同的阶段中使用参数,每个阶段必须包含ARG指令,或者它们都必须基于在同一个Dockerfile中声明变量的共享基础阶段。

欲了解更多信息,请参考 变量作用域

使用ARG变量

你可以使用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构建:

$ docker build .

使用这个Dockerfile示例,CONT_IMG_VER仍然保留在镜像中,但其值将是v1.0.0,因为这是第3行由ENV指令设置的默认值。

在这个例子中,变量扩展技术允许你从命令行传递参数,并通过利用ENV指令将它们持久化到最终镜像中。变量扩展仅支持有限的Dockerfile指令集。

预定义的参数

Docker 有一组预定义的 ARG 变量,你可以在 Dockerfile 中无需相应的 ARG 指令即可使用。

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

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

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

默认情况下,这些预定义的变量被排除在docker history的输出之外。排除它们可以减少在HTTP_PROXY变量中意外泄露敏感认证信息的风险。

例如,考虑使用以下Dockerfile构建 --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 history中,更改其值会使构建缓存失效。

全局范围内的自动平台参数

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

BuildKit 支持一组预定义的 ARG 变量,这些变量包含有关执行构建的节点平台(构建平台)和生成的镜像平台(目标平台)的信息。目标平台可以通过 docker build 上的 --platform 标志来指定。

以下 ARG 变量是自动设置的:

  • TARGETPLATFORM - 构建结果的平台。例如 linux/amd64, linux/arm/v7, windows/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"

BuildKit 内置构建参数

ArgTypeDescription
BUILDKIT_CACHE_MOUNT_NSStringSet optional cache ID namespace.
BUILDKIT_CONTEXT_KEEP_GIT_DIRBoolTrigger Git context to keep the .git directory.
BUILDKIT_INLINE_CACHE2BoolInline cache metadata to image config or not.
BUILDKIT_MULTI_PLATFORMBoolOpt into deterministic output regardless of multi-platform output or not.
BUILDKIT_SANDBOX_HOSTNAMEStringSet the hostname (default buildkitsandbox)
BUILDKIT_SYNTAXStringSet frontend image
SOURCE_DATE_EPOCHIntSet the Unix timestamp for created image and layers. More info from 可重现构建. Supported since Dockerfile 1.5, BuildKit 0.11

示例:保留 .git 目录

在使用Git上下文时,.git目录不会保留在检出中。如果您想在构建期间检索git信息,保留它可能会很有用:

# syntax=docker/dockerfile:1
FROM alpine
WORKDIR /src
RUN --mount=target=. \
  make REVISION=$(git rev-parse HEAD) build
$ docker build --build-arg BUILDKIT_CONTEXT_KEEP_GIT_DIR=1 https://github.com/user/repo.git#main

对构建缓存的影响

ARG 变量不会像 ENV 变量那样持久化到构建的镜像中。 然而,ARG 变量以类似的方式影响构建缓存。如果 Dockerfile 定义了一个 ARG 变量,其值与之前的构建不同, 那么在其首次使用时会发生“缓存未命中”,而不是在其定义时。 特别是,所有在 ARG 指令之后的 RUN 指令都会隐式使用 ARG 变量(作为环境变量),因此可能导致缓存未命中。 所有预定义的 ARG 变量都不受缓存影响,除非 Dockerfile 中有 匹配的 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=,在两种情况下,第2行的规范不会导致缓存未命中;第3行会导致缓存未命中。ARG CONT_IMG_VER导致RUN行被识别为与运行CONT_IMG_VER= echo hello相同,所以如果发生变化,你会得到一个缓存未命中。

考虑另一个在相同命令行下的例子:

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. 稍后,该镜像可能会用作新构建的基础,使用FROM指令。作为处理FROM指令的一部分,下游构建器会查找ONBUILD触发器,并按它们注册的顺序执行它们。如果任何触发器失败,FROM指令将被中止,从而导致构建失败。如果所有触发器都成功,FROM指令完成,构建将照常继续。
  4. 触发器在执行后从最终镜像中清除。换句话说,它们不会被“孙子”构建继承。

例如,你可能会添加类似这样的内容:

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

从阶段、镜像或上下文中复制或挂载

从Dockerfile语法1.11开始,您可以使用ONBUILD与从其他阶段、镜像或构建上下文中复制或挂载文件的指令。例如:

# syntax=docker/dockerfile:1.11
FROM alpine AS baseimage
ONBUILD COPY --from=build /usr/bin/app /app
ONBUILD RUN --mount=from=config,target=/opt/appconfig ...

如果from的来源是一个构建阶段,那么该阶段必须在触发ONBUILD的Dockerfile中定义。如果它是一个命名的上下文,那么该上下文必须传递给下游构建。

ONBUILD 限制

  • 不允许使用ONBUILD ONBUILD来链接ONBUILD指令。
  • ONBUILD 指令可能不会触发 FROMMAINTAINER 指令。

STOPSIGNAL

STOPSIGNAL signal

STOPSIGNAL 指令设置将发送给容器以退出的系统调用信号。此信号可以是格式为 SIG 的信号名称,例如 SIGKILL,或者是与内核系统调用表中的位置匹配的无符号数字,例如 9。如果未定义,则默认为 SIGTERM

图像的默认停止信号可以通过每个容器进行覆盖,使用--stop-signal标志在docker rundocker create上。

健康检查

HEALTHCHECK 指令有两种形式:

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

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

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

可以出现在CMD之前的选项有:

  • --interval=DURATION (默认: 30s)
  • --timeout=DURATION (默认: 30s)
  • --start-period=DURATION (默认: 0s)
  • --start-interval=DURATION (默认: 5s)
  • --retries=N (默认值: 3)

健康检查将在容器启动后首先运行interval秒,然后在每次前一次检查完成后再次运行interval秒。

如果检查的单个运行时间超过timeout秒,则认为检查失败。

需要retries次连续的健康检查失败,容器才会被视为unhealthy

启动周期为需要时间启动的容器提供初始化时间。 在此期间,探针失败不会计入最大重试次数。 但是,如果在启动周期内健康检查成功,容器将被视为已启动,所有连续的失败都将计入最大重试次数。

启动间隔 是启动期间健康检查之间的时间。 此选项需要 Docker Engine 版本 25.0 或更高版本。

在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

为了帮助调试失败的探针,命令在标准输出或标准错误上写入的任何输出文本(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 上特别有用,因为 Windows 上有两种常用且截然不同的原生 shell:cmdpowershell,以及包括 sh 在内的其他可用 shell。

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"

构建器调用的命令将是:

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

这效率低下有两个原因。首先,调用了一个不必要的cmd.exe命令处理器(也称为shell)。其次,shell形式中的每个RUN指令都需要在命令前加上额外的powershell -command前缀。

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

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 等,SHELL 指令也可以在 Linux 上使用。

Here-Documents

Here-documents 允许将后续的 Dockerfile 行重定向到 RUNCOPY 命令的输入中。如果此类命令包含一个 here-document Dockerfile 会将接下来的行视为同一命令的一部分,直到仅包含 here-doc 分隔符的行。

示例:运行多行脚本

# syntax=docker/dockerfile:1
FROM debian
RUN <<EOT bash
  set -ex
  apt-get update
  apt-get install -y vim
EOT

如果命令仅包含一个here-document,其内容将使用默认shell进行评估。

# syntax=docker/dockerfile:1
FROM debian
RUN <<EOT
  mkdir -p foo/bar
EOT

或者,可以使用shebang头来定义解释器。

# syntax=docker/dockerfile:1
FROM python:3.6
RUN <<EOT
#!/usr/bin/env python
print("hello world")
EOT

更复杂的示例可能会使用多个here-documents。

# syntax=docker/dockerfile:1
FROM alpine
RUN <<FILE1 cat > file1 && <<FILE2 cat > file2
I am
first
FILE1
I am
second
FILE2

示例:创建内联文件

使用COPY指令,您可以用here-doc指示符替换源参数,以将here-document的内容直接写入文件。以下示例使用COPY指令创建一个包含hello worldgreeting.txt文件。

# syntax=docker/dockerfile:1
FROM alpine
COPY <<EOF greeting.txt
hello world
EOF

常规的here-doc 变量扩展和制表符剥离规则 适用。 以下示例展示了一个小型的Dockerfile,它使用带有here-document的COPY指令创建了一个hello.sh脚本文件。

# syntax=docker/dockerfile:1
FROM alpine
ARG FOO=bar
COPY <<-EOT /script.sh
  echo "hello ${FOO}"
EOT
ENTRYPOINT ash /script.sh

在这种情况下,文件脚本打印“hello bar”,因为变量在COPY指令执行时被扩展。

$ docker build -t heredoc .
$ docker run heredoc
hello bar

如果你引用here-document单词EOT的任何部分,变量在构建时将不会被扩展。

# syntax=docker/dockerfile:1
FROM alpine
ARG FOO=bar
COPY <<-"EOT" /script.sh
  echo "hello ${FOO}"
EOT
ENTRYPOINT ash /script.sh

请注意,ARG FOO=bar 在这里是多余的,可以删除。该变量在脚本被调用时在运行时解释:

$ docker build -t heredoc .
$ docker run -e FOO=world heredoc
hello world

Dockerfile 示例

有关Dockerfile的示例,请参考:


  1. 需要值 ↩︎ ↩︎ ↩︎

  2. 对于Docker集成的 BuildKitdocker buildx build ↩︎