Dockerfile 参考

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

概述

Dockerfile 支持以下指令:

指令描述
ADD添加本地或远程文件和目录。
ARG使用构建时变量。
CMD指定默认命令。
COPY复制文件和目录。
ENTRYPOINT指定默认可执行文件。
ENV设置环境变量。
EXPOSE描述您的应用程序正在侦听的端口。
FROM从基础镜像创建新的构建阶段。
HEALTHCHECK检查容器启动时的健康状况。
LABEL向镜像添加元数据。
MAINTAINER指定镜像作者。
ONBUILD指定在构建中使用此镜像时要执行的指令。
RUN执行构建命令。
SHELL设置镜像的默认 shell。
STOPSIGNAL指定用于退出容器的系统调用信号。
USER设置用户和组 ID。
VOLUME创建卷挂载。
WORKDIR更改工作目录。

格式

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 是无效的,因为检查名称必须使用 Pascal Case,而不是小写。在任何解析器指令之后包含一个空行也是约定俗成的做法。解析器指令中不支持行继续符。

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

由于行继续符而无效

# 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

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

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

# syntax=docker/dockerfile:1

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

escape

# escape=\

或者

# escape=`

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

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

在 Windows 上将转义字符设置为 ` 特别有用,因为在 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-name> 禁用特定检查。要指定多个要跳过的检查,请用逗号分隔:

# 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 语法版本为最新的稳定版本。

环境变量替换

环境变量(使用 ENV 语句 声明)也可以在某些指令中用作变量,供 Dockerfile 解释。也支持使用转义来将类变量语法字面量包含到语句中。

Dockerfile 中的环境变量使用 $variable_name${variable_name} 表示。它们被视为等效的,并且大括号语法通常用于处理没有空格的变量名问题,例如 ${foo}_bar

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

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

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

  • ${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}variablepattern 的第一次出现替换为 replacement

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

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

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

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

您可以通过在变量前添加 \ 来转义整个变量名:例如,\$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 文件

Shell 和 exec 形式

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

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

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

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

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 <name>COPY --from=<name>RUN --mount=type=bind,from=<name> 指令中使用,以引用在此阶段构建的镜像。
  • tagdigest 值是可选的。如果你省略其中任何一个,构建器默认假定使用 latest 标签。如果构建器找不到 tag 值,则会返回错误。

可选的 --platform 标志可用于指定镜像的平台,以防 FROM 引用多平台镜像。例如,linux/amd64linux/arm64windows/amd64。默认情况下,使用构建请求的目标平台。全局构建参数可用于此标志的值,例如自动平台 ARG 允许你强制阶段使用原生构建平台(--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

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

RUN 指令的可用 [OPTIONS] 如下

选项最低 Dockerfile 版本
--mount1.2
--network1.3
--security1.1.2-labs

RUN 指令的缓存无效化

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

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

ADDCOPY 指令可以使 RUN 指令的缓存失效。

RUN --mount

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

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

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

支持的挂载类型有

类型描述
bind (默认)绑定挂载上下文目录(只读)。
cache挂载一个临时目录,用于缓存编译器和包管理器的目录。
tmpfs在构建容器中挂载一个 tmpfs
secret允许构建容器访问安全文件(如私钥),而无需将其烘焙到镜像或构建缓存中。
ssh允许构建容器通过 SSH 代理访问 SSH 密钥,支持密码。

RUN --mount=type=bind

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

选项描述
target, dst, destination1挂载路径。
sourcefrom 中的源路径。默认为 from 的根目录。
from源根目录的构建阶段、上下文或镜像名称。默认为构建上下文。
rw,readwrite允许在该挂载上写入。写入的数据将被丢弃。

RUN --mount=type=cache

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

选项描述
id可选的 ID,用于标识独立/不同的缓存。默认为 target 的值。
target, dst, destination1挂载路径。
ro,readonly如果设置,则为只读。
sharing可以是 shared, privatelocked 之一。默认为 sharedshared 缓存挂载可以由多个写入者并发使用。如果有多个写入者,private 会创建一个新的挂载。locked 会暂停第二个写入者,直到第一个释放挂载。
from用作缓存挂载基础的构建阶段、上下文或镜像名称。默认为空目录。
sourcefrom 中的子路径,用于挂载。默认为 from 的根目录。
mode新缓存目录的文件模式(八进制)。默认为 0755
uid新缓存目录的用户 ID。默认为 0
gid新缓存目录的组 ID。默认为 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

RUN --mount=type=tmpfs

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

选项描述
target, dst, destination1挂载路径。
size指定文件系统大小的上限。

RUN --mount=type=secret

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

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

选项描述
id秘密的 ID。默认为目标路径的基本名称。
target, dst, destination将秘密挂载到指定的路径。如果未设置,并且 env 也未设置,则默认为 /run/secrets/ + id
env将秘密挂载到环境变量而不是文件,或两者都挂载。(自 Dockerfile v1.10.0 起)
required如果设置为 true,当秘密不可用时,指令会出错。默认为 false
modemode
uidUser ID for secret file. Default 0.
gidGroup ID for secret file. Default 0.

Example: access to 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 .

Example: Mount as environment variable

The following example takes the secret API_KEY and mounts it as an environment variable with the same name.

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

Assuming that the API_KEY environment variable is set in the build environment, you can build this with the following command

$ docker buildx build --secret id=API_KEY .

RUN --mount=type=ssh

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

选项描述
idSSH 代理套接字或密钥的 ID。默认为 "default"。
target, dst, destinationSSH 代理套接字路径。默认为 /run/buildkit/ssh_agent.${N}
required如果设置为 true,当密钥不可用时,指令会出错。默认为 false
modemode
uiduid
gidgid

示例:访问 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 允许控制命令在哪种网络环境中运行。

支持的网络类型有

类型描述
default (默认)在默认网络中运行。
none无网络访问地运行。
host在主机的网络环境中运行。

RUN --network=default

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

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 将只能安装 tar 文件中提供的包,这可以通过早期的构建阶段进行控制。

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 守护进程时使用 --allow-insecure-entitlement security.insecure 标志或在 buildkitd 配置中启用 security.insecure 权限,并且在构建请求中带有 --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"] (exec 形式)
  • CMD ["param1","param2"] (exec 形式,作为 ENTRYPOINT 的默认参数)
  • CMD command param1 param2 (shell 形式)

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

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

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

如果 CMD 用于为 ENTRYPOINT 指令提供默认参数,则 CMDENTRYPOINT 指令都应以exec 形式指定。

注意

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

LABEL

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 指令设置生成的镜像的 Author 字段。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 指令将环境变量 <key> 设置为值 <value>。此值将存在于构建阶段所有后续指令的环境中,并且也可以在许多指令中进行内联替换。该值将被解释为其他环境变量,因此未转义的引号字符将被删除。与命令行解析类似,可以使用引号和反斜杠在值中包含空格。

示例

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 inspect 查看这些值,并使用 docker run --env <key>=<value> 更改它们。

一个阶段继承其父阶段或任何祖先阶段使用 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 <key> <value>,省略 =。例如

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] 如下

选项最低 Dockerfile 版本
--keep-git-dir1.1
--checksum1.6
--chown
--chmod1.2
--link1.4
--exclude1.7-labs

ADD 指令从 <src> 复制新文件或目录,并将它们添加到镜像文件系统中的路径 <dest>。文件和目录可以从构建上下文、远程 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 会检测 <src> 的类型并相应地处理它。

从构建上下文添加文件

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

指定以斜杠开头或导航到构建上下文之外的源路径(例如 ADD ../something /something),会自动删除任何父目录导航(../)。源路径中的尾随斜杠也会被忽略,使 ADD something/ /something 等同于 ADD something /something

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

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

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

模式匹配

对于本地文件,每个 <src> 可以包含通配符,匹配将使用 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 agent socket 挂载到构建中。例如

$ docker build --ssh default .

有关使用 secrets 进行构建的更多信息,请参阅 构建 secrets

目标

如果目的地路径以斜杠开头,则将其解释为绝对路径,源文件将被复制到相对于当前构建阶段根目录的指定目的地。

# 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>

<src> 是远程 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 标志允许您验证远程资源的校验和。校验和的格式为 sha256:<hash>。SHA-256 是唯一支持的哈希算法。

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] 如下

选项最低 Dockerfile 版本
--from
--chown
--chmod1.2
--link1.4
--parents1.7-labs
--exclude1.7-labs

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

ADDCOPY 指令在功能上相似,但服务于略微不同的目的。了解更多关于ADDCOPY 之间差异

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

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

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

COPY 接受一个标志 --from=<name>,该标志允许您指定源位置为构建阶段、上下文或镜像。以下示例从名为 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 等同于 COPY something /something

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

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

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

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

模式匹配

对于本地文件,每个 <src> 可以包含通配符,匹配将使用 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>

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

# 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 <name>=<source> 指定)或镜像复制文件。以下示例从官方 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 容器的 Dockerfile 上受支持,在 Windows 容器上不起作用。由于用户和组所有权概念在 Linux 和 Windows 之间无法转换,因此使用 /etc/passwd/etc/group 将用户和组名称转换为 ID 的限制,使得此功能仅适用于基于 Linux OS 的容器。

从构建上下文复制的所有文件和目录都创建时 UID 和 GID 为 0,除非可选的 --chown 标志指定了给定的用户名、组名或 UID/GID 组合,以请求复制内容的特定所有权。--chown 标志的格式允许使用用户名和组名字符串或直接的整数 UID 和 GID 的任意组合。仅提供用户名而不提供组名,或仅提供 UID 而不提供 GID,将使用相同的数字 UID 作为 GID。如果提供了用户名或组名,则容器的根文件系统中的 /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,先前构建生成的层会被重用并合并在新层之上。这也意味着当基础镜像接收到更新时,您可以轻松地 rebase 您的镜像,而无需再次执行整个构建。在支持它的后端中,BuildKit 可以执行此 rebase 操作,而无需在客户端和注册表之间 push 或 pull 任何层。BuildKit 将检测到此情况,并且只会创建包含新层和旧层按正确顺序排列的新镜像 manifest。

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

--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 工具的 --parents 标志或 rsync --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 标志允许您指定要排除的文件的路径表达式。

路径表达式的格式与 <src> 相同,支持通配符和使用 Go 的 filepath.Match 规则。例如,要添加所有以 "hom" 开头的文件,但排除扩展名为 .txt 的文件

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

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

您可以为 COPY 指令多次指定 --exclude 选项。多个 --excludes 表示即使文件路径与 <src> 中指定的模式匹配,匹配其模式的文件也不会被复制。要添加所有以 "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 <image> 的命令行参数将追加在 exec 形式 ENTRYPOINT 的所有元素之后,并会覆盖使用 CMD 指定的所有元素。

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

ENTRYPOINT 的 shell 形式阻止使用任何 CMD 命令行参数。它还会将您的 ENTRYPOINT 作为 /bin/sh -c 的子命令启动,这不会传递信号。这意味着可执行文件不会是容器的 PID 1,并且不会接收 Unix 信号。在这种情况下,您的可执行文件不会收到来自 docker stop <container>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

您可以使用 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 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 如何交互

CMDENTRYPOINT 指令都定义了运行容器时要执行的命令。有一些规则描述了它们的协作关系。

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

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

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

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

下表显示了不同的 ENTRYPOINT / CMD 组合将执行哪些命令

无 ENTRYPOINTENTRYPOINT exec_entry p1_entryENTRYPOINT ["exec_entry", "p1_entry"]
无 CMD错误,不允许/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 的容器时,容器内卷的目的地必须是以下之一

    • 不存在或为空的目录
    • C: 以外的驱动器
  • 在 Dockerfile 中更改卷:如果在声明卷后,任何构建步骤更改了卷内的数据,使用旧版构建器时,这些更改将被丢弃。使用 Buildkit 时,这些更改将被保留。

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

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

USER

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 <varname>=<value> 标志将其传递给构建器。

警告

不建议使用构建参数传递 secrets,例如用户凭据、API 令牌等。构建参数在 docker history 命令中可见,并且在 max 模式的 provenance attestations 中可见,如果您使用 Buildx GitHub Actions 且您的 GitHub 仓库是公开的,则这些 attestations 默认会附加到镜像中。

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

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 变量尚未声明。
  • username 变量在第 3 行声明,并从那一点开始可在 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 指令集

预定义的 ARG

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 变量中意外泄露敏感身份验证信息的风险。

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

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 中,并且更改其值会使构建缓存失效。

全局作用域中的自动平台 ARG

此功能仅在使用 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 内置构建参数

参数类型描述
BUILDKIT_CACHE_MOUNT_NS字符串设置可选的缓存 ID 命名空间。
BUILDKIT_CONTEXT_KEEP_GIT_DIR布尔触发 Git 上下文以保留 .git 目录。
BUILDKIT_INLINE_CACHE2布尔将缓存元数据内联到镜像配置中或不内联。
BUILDKIT_MULTI_PLATFORM布尔选择确定性输出,无论是否是多平台输出。
BUILDKIT_SANDBOX_HOSTNAME字符串设置主机名(默认为 buildkitsandbox
BUILDKIT_SYNTAX字符串设置前端镜像
SOURCE_DATE_EPOCH整数设置创建的镜像和层的 Unix 时间戳。更多信息请参阅 可重现构建。自 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=<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. 之后,该镜像可以使用 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<NAME> 格式的信号名称,例如 SIGKILL,也可以是与内核系统调用表中某个位置匹配的无符号数字,例如 9。如果未定义,默认为 SIGTERM

可以使用 docker rundocker create 命令上的 --stop-signal 标志,为每个容器覆盖镜像的默认停止信号。

HEALTHCHECK

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(不健康)。

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

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

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

CMD 关键字后面的命令可以是 Shell 命令(例如 HEALTHCHECK CMD /bin/check-running),也可以是 Exec 数组(与其他 Dockerfile 命令一样;详情请参阅 ENTRYPOINT 等)。

命令的退出状态指示容器的健康状态。可能的值有

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

例如,大约每五分钟检查一次 Web 服务器是否能够在三秒内提供站点的主要页面

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 上特别有用,因为 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 可以修改延迟环境变量扩展的语义。

如果在 Linux 上需要备用 Shell(例如 zshcshtcsh 等),也可以使用 SHELL 指令。

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-document。

# 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 变量扩展和 Tab 剥离规则适用。以下示例展示了一个使用带有 here-document 的 COPY 指令创建 hello.sh 脚本文件的小型 Dockerfile。`

# 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 ↩︎

页面选项