Dockerfile 参考文档

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 的格式:

1
2
# 注释
INSTRUCTION 参数

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

Docker 按顺序运行 Dockerfile 中的指令。一个 Dockerfile 必须 FROM 指令开头。这可以出现在 解析器指令注释 和全局作用域的 ARG 之后。FROM 指令指定了您正在构建的 基础镜像FROM 前面只能有一个或多个 ARG 指令,这些指令声明了在 Dockerfile 的 FROM 行中使用的参数。

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

1
2
# 注释
RUN echo 'we are running some # of cool things'

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

1
2
3
RUN echo hello 
# 注释
world

以下示例是等效的。

1
2
RUN echo hello 
world

注释不支持行继续字符。

注意

关于空白的说明

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

1
2
3
        # 这是一个注释行
RUN echo hello
RUN echo world
1
2
3
# 这是一个注释行
RUN echo hello
RUN echo world

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

1
2
3
RUN echo \"
hello
world\"

解析器指令

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

支持以下解析器指令:

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

解析器指令的键,如 syntaxcheck,不区分大小写,但按照惯例使用小写。指令的值区分大小写,并且必须根据指令的要求以适当的大小写书写。例如,#check=skip=jsonargsrecommended 是无效的,因为检查名称必须使用 Pascal 大小写,而不是小写。按照惯例,在任何解析器指令之后包含一个空行。解析器指令不支持行继续字符。

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

由于行继续而无效:

1
2
# direc 
tive=value

由于出现两次而无效:

1
2
3
4
# directive=value1
# directive=value2

FROM ImageName

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

1
2
FROM ImageName
# directive=value

由于出现在非解析器指令的注释之后而被视为注释:

1
2
3
# About my dockerfile
# directive=value
FROM ImageName

以下 unknowndirective 由于未被识别而被视为注释。已知的 syntax 指令由于出现在非解析器指令的注释之后而被视为注释。

1
2
# unknowndirective=value
# syntax=value

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

1
2
3
4
5
#directive=value
# directive =value
#\tdirective= value
# directive = value
#\t dIrEcTiVe=value

syntax

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

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

1
# syntax=docker/dockerfile:1

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

escape

1
# escape=

1
# escape=`

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

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

将转义字符设置为 在 `Windows` 上特别有用,因为 `\\` 是目录路径分隔符。Windows PowerShell 保持一致。

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

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

结果:

1
2
3
4
5
6
7
8
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 上文件路径的自然平台语义:

1
2
3
4
5
# escape=`

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

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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

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

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

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

1
# check=skip=JSONArgsRecommended,StageNameCasing

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

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

1
# check=error=true

注意

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

要同时使用 skiperror 选项,请用分号分隔它们:

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

  • ${variable#pattern}variable 的开头开始,移除与 pattern 的最短匹配。
1
str=foobarbaz echo ${str#f*b}     # arbaz
  • ${variable##pattern}variable 的开头开始,移除与 pattern 的最长匹配。
1
str=foobarbaz echo ${str##f*b}    # az
  • ${variable%pattern}variable 的末尾开始向后搜索,移除与 pattern 的最短匹配。
1
string=foobarbaz echo ${string%b*}    # foobar
  • ${variable%%pattern}variable 的末尾开始向后搜索,移除与 pattern 的最长匹配。
1
string=foobarbaz echo ${string%%b*}   # foo
  • ${variable/pattern/replacement}variablepattern 的第一次出现替换为 replacement
1
string=foobarbaz echo ${string/ba/fo}  # fooforbaz
  • ${variable//pattern/replacement}variable 中所有出现的 pattern 替换为 replacement
1
string=foobarbaz echo ${string//ba/fo}  # fooforfoz

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

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

您可以通过在变量前添加 \\ 来转义整个变量名:例如,\\$foo\\${foo} 将分别转换为字面量 $foo${foo}

示例(# 号后为解析结果):

1
2
3
4
5
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。请参阅 变量替换

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

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

.dockerignore 文件

您可以使用 .dockerignore 文件从构建上下文(即 Dockerfile 所在目录的文件集合)中排除文件和目录。有关更多信息,请参阅 .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 数组,这意味着您必须在单词周围使用双引号 ("),而不是单引号 (')。

1
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 形式,并以意外的方式失败:

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

此示例的正确语法是:

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

Shell 形式

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

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

它们等效于以下行:

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

您还可以在 shell 形式中使用 heredoc 来拆分支持的命令。

1
2
3
4
RUN <<EOF
source $HOME/.bashrc
echo $HOME
EOF

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

使用不同的 Shell

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

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

有关更多信息,请参阅 SHELL

FROM

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

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

1
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 指令声明的变量。

1
2
3
4
5
6
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 指令:

1
2
3
4
ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version

RUN

RUN 指令将执行任何命令,以在当前镜像之上创建一个新层。添加的层用于 Dockerfile 中的下一步。RUN 有两种形式:

1
2
3
4
# Shell 形式:
RUN [OPTIONS] <command> ...
# Exec 形式:
RUN [OPTIONS] [ \"<command>\", ... ]

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

shell 形式最常用,它允许您将较长的指令拆分为多行,可以使用换行符 转义,也可以使用 heredoc

1
2
3
4
RUN <<EOF
apt-get update
apt-get install -y curl
EOF

RUN 指令可用的 [OPTIONS] 有:

选项 最低 Dockerfile 版本
--device 1.14-labs
--mount 1.2
--network 1.3
--security 1.20

RUN 指令的缓存失效

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

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

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

RUN --device

注意

在稳定语法中尚不可用,请使用 docker/dockerfile:1-labs 版本。它还需要 BuildKit 0.20.0 或更高版本。

1
RUN --device=name,[required]

RUN --device 允许构建请求 CDI 设备(容器设备接口,允许容器访问特定硬件)对构建步骤可用。

警告

--device 的使用受 device 权限保护,该权限需要在启动 buildkitd 守护进程时通过 --allow-insecure-entitlement device 标志或在 buildkitd 配置 中启用,并且对于构建请求需要使用 --allow device 标志

设备 name 由 BuildKit 中注册的 CDI 规范提供。

在以下示例中,多个设备在 CDI 规范中为 vendor1.com/device 供应商注册。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
cdiVersion: \"0.6.0\"
kind: \"vendor1.com/device\"
devices:
- name: foo
containerEdits:
env:
- FOO=injected
- name: bar
annotations:
org.mobyproject.buildkit.device.class: class1
containerEdits:
env:
- BAR=injected
- name: baz
annotations:
org.mobyproject.buildkit.device.class: class1
containerEdits:
env:
- BAZ=injected
- name: qux
annotations:
org.mobyproject.buildkit.device.class: class2
containerEdits:
env:
- QUX=injected
annotations:
org.mobyproject.buildkit.device.autoallow: true

设备名称格式灵活,接受各种模式以支持多种设备配置:

  • vendor1.com/device:请求此供应商找到的第一个设备
  • vendor1.com/device=foo:请求特定设备
  • vendor1.com/device=*:请求此供应商的所有设备
  • class1:通过 org.mobyproject.buildkit.device.class 注解请求设备

注意

自 0.6.0 起,CDI 规范支持注解。

注意

要自动允许 CDI 规范中注册的所有设备,您可以设置 org.mobyproject.buildkit.device.autoallow 注解。您也可以为特定设备设置此注解。

示例:CUDA 驱动的 LLaMA 推理

在此示例中,我们使用 --device 标志通过 CDI 运行 llama.cpp 推理,使用 NVIDIA GPU 设备:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# syntax=docker/dockerfile:1-labs

FROM scratch AS model
ADD https://huggingface.co/bartowski/Llama-3.2-1B-Instruct-GGUF/resolve/main/Llama-3.2-1B-Instruct-Q4_K_M.gguf /model.gguf

FROM scratch AS prompt
COPY <<EOF prompt.txt
Q: Generate a list of 10 unique biggest countries by population in JSON with their estimated poulation in 1900 and 2024. Answer only newline formatted JSON with keys \"country\", \"population_1900\", \"population_2024\" with 10 items.
A:
[
{

EOF

FROM ghcr.io/ggml-org/llama.cpp:full-cuda-b5124
RUN --device=nvidia.com/gpu=all
--mount=from=model,target=/models
--mount=from=prompt,target=/tmp
./llama-cli -m /models/model.gguf -no-cnv -ngl 99 -f /tmp/prompt.txt

RUN --mount

1
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 挂载路径。
source from 中的源路径。默认为 from 的根目录。
from 源根目录的构建阶段、上下文或镜像名称。默认为构建上下文。
rw,readwrite 允许在挂载上进行写入。写入的数据将被丢弃。

RUN --mount=type=cache

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

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

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

示例:缓存 Go 包

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

示例:缓存 apt 包

1
2
3
4
5
6
# 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
mode 密钥文件的文件模式,八进制。默认 0400
uid 密钥文件的用户 ID。默认 0
gid 密钥文件的组 ID。默认 0

示例:访问 S3

1
2
3
4
5
# 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://... ...
1
$ docker buildx build --secret id=aws,src=$HOME/.aws/credentials .

示例:作为环境变量挂载

以下示例获取密钥 API_KEY 并将其作为同名环境变量挂载。

1
2
3
4
# 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 环境变量在构建环境中设置,您可以使用以下命令构建:

1
$ docker buildx build --secret id=API_KEY .

RUN --mount=type=ssh

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

选项 描述
id SSH 代理套接字或密钥的 ID。默认为 "default"。
target, dst, destination SSH 代理套接字路径。默认为 /run/buildkit/ssh_agent.${N}
required 如果设置为 true,当密钥不可用时,指令会出错。默认为 false
mode 套接字的文件模式,八进制。默认 0600
uid 套接字的用户 ID。默认 0
gid 套接字的组 ID。默认 0

示例:访问 GitLab

1
2
3
4
5
6
7
8
# 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\" 应该在这里打印
# 当构建进度类型定义为 `plain` 时。
1
2
3
4
$ eval $(ssh-agent)
$ ssh-add ~/.ssh/id_rsa
(在此处输入您的密码短语)
$ docker buildx build --ssh default=$SSH_AUTH_SOCK .

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

RUN --network

1
RUN --network=TYPE

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

支持的网络类型有:

类型 描述
default (默认) 在默认网络中运行。
none 在没有网络访问的情况下运行。
host 在主机的网络环境中运行。

RUN --network=default

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

RUN --network=none

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

示例:隔离外部影响

1
2
3
4
# 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

1
RUN --security=<sandbox|insecure>

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

警告

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

默认沙箱模式可以通过 --security=sandbox 激活,但这是无操作的。

示例:检查权限

1
2
3
# syntax=docker/dockerfile:1
FROM ubuntu
RUN --security=insecure cat /proc/self/status | grep CapEff
1
#84 0.093 CapEff:\t0000003fffffffff

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

1
LABEL <key>=<value> [<key>=<value>...]

LABEL 指令向镜像添加元数据。LABEL 是一个键值对。要在 LABEL 值中包含空格,请使用引号和反斜杠,就像在命令行解析中一样。一些用法示例:

1
2
3
4
5
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 之前,这减少了最终镜像的大小,但现在不再如此。您仍然可以选择在单个指令中以以下两种方式之一指定多个标签:

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

注意

务必使用双引号而不是单引号。特别是当您使用字符串插值时(例如 LABEL example=\"foo-$ENV_VAR\"),单引号将按原样接受字符串,而不会解包变量的值。

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

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

1
$ docker image inspect --format='{{json .Config.Labels}}' myimage
1
2
3
4
5
6
7
8
9
{
"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 (已弃用)

1
MAINTAINER <name>

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

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

然后,这可以通过 docker inspect 与其他标签一起查看。

EXPOSE

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

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

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

默认情况下,EXPOSE 假定为 TCP。您也可以指定 UDP:

1
EXPOSE 80/udp

要同时暴露 TCP 和 UDP,请包含两行:

1
2
EXPOSE 80/tcp
EXPOSE 80/udp

在这种情况下,如果您在 docker run 时使用 -P,端口将暴露一次用于 TCP,一次用于 UDP。请记住,-P 使用主机上的临时高端口,因此 TCP 和 UDP 不使用相同的端口。

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

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

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

ENV

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

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

示例:

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

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

1
2
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 的行为,并可能使镜像的用户感到困惑。

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

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

或者使用 ARG,它不会在最终镜像中持久存在:

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

注意

替代语法

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

1
ENV MY_VAR my-value

此语法不允许在单个 ENV 指令中设置多个环境变量,并且可能会令人困惑。例如,以下设置了一个环境变量 (ONE),值为 \"TWO= THREE=world\"

1
ENV ONE TWO= THREE=world

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

ADD

ADD 有两种形式。
后一种形式是包含空格的路径所必需的。

1
2
ADD [OPTIONS] <src> ... <dest>
ADD [OPTIONS] [\"<src>\", ... \"<dest>\"]

可用的 [OPTIONS] 有:

选项 最低 Dockerfile 版本
--keep-git-dir 1.1
--checksum 1.6
--chown
--chmod 1.2
--link 1.4
--exclude 1.19

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

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

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

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

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

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

1
2
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 结尾的文件和目录:

1
ADD *.png /dest/

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

1
ADD index.?s /dest/

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

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

添加本地 tar 存档

当使用本地 tar 存档作为 ADD 的源,并且存档是识别的压缩格式(gzipbzip2xz,或未压缩)时,存档被解压缩并提取到指定的目标。本地 tar 存档默认被提取,请参阅 [ADD --unpack 标志]。

当提取目录时,其行为与 tar -x 相同。
结果是以下内容的并集:

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

注意

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

从 URL 添加文件

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

如果远程文件是 tar 存档,默认情况下不会提取存档。要下载并提取存档,请使用 [ADD --unpack 标志]。

如果目标以尾随斜杠结尾,则文件名从 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 地址引用为源。仓库被克隆到镜像中的指定目标。

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

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

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

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

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

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

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

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

1
$ docker build --ssh default .

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

目标

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

1
2
# 创建 /abs/test.txt
ADD test.txt /abs/

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

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

1
2
3
WORKDIR /usr/src/app
# 创建 /usr/src/app/rel/test.txt
ADD test.txt rel/

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

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

ADD --keep-git-dir

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

<src> 是远程 Git 仓库的 HTTP 或 SSH 地址时,BuildKit 默认将 Git 仓库的内容添加到镜像,排除 .git 目录。

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

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

ADD --checksum

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

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

1
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

ADD --unpack

1
ADD [--unpack=<bool>] <src> ... <dir>

--unpack 标志控制是否在将 tar 存档(包括压缩格式如 gzipbzip2)添加到镜像时自动解包它们。本地 tar 存档默认解包,而远程 tar 存档(其中 src 是 URL)则不解包下载。

1
2
3
4
5
6
# syntax=docker/dockerfile:1
FROM alpine
# 下载并解包 archive.tar.gz 到 /download:
ADD --unpack=true https://example.com/archive.tar.gz /download
# 添加本地 tar 而不解包:
ADD --unpack=false my-archive.tar.gz .

COPY

COPY 有两种形式。
后一种形式是包含空格的路径所必需的。

1
2
COPY [OPTIONS] <src> ... <dest>
COPY [OPTIONS] [\"<src>\", ... \"<dest>\"]

可用的 [OPTIONS] 有:

选项 最低 Dockerfile 版本
--from
--chown
--chmod 1.2
--link 1.4
--parents 1.20
--exclude 1.19

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

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

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

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

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

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

1
2
3
4
5
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 结尾的文件和目录:

1
COPY *.png /dest/

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

1
COPY index.?s /dest/

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

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

目标

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

1
2
# 创建 /abs/test.txt
COPY test.txt /abs/

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

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

1
2
3
WORKDIR /usr/src/app
# 创建 /usr/src/app/rel/test.txt
COPY test.txt rel/

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

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

COPY --from

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

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

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

1
2
3
4
5
6
7
8
# 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 文件。

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

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

COPY --chown --chmod

注意

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

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

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

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

1
2
3
4
5
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 标志支持变量插值,允许您使用构建参数定义权限位:

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

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

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

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

等效于执行两次构建:

1
FROM alpine

1
2
FROM scratch
COPY /foo /bar

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

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

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

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

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

COPY --parents

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

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

1
2
3
4
5
6
7
8
9
# syntax=docker/dockerfile:1
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 复制时可能特别有用,其中源路径需要是绝对的。

1
2
3
4
5
6
7
8
9
10
11
12
# syntax=docker/dockerfile:1
FROM scratch

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

# 构建上下文:
# ./x/y/a.txt
# ./x/y/b.txt
#
# 输出:
# /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

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

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

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

1
2
3
4
# syntax=docker/dockerfile:1
FROM scratch

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

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

1
2
3
4
# syntax=docker/dockerfile:1
FROM scratch

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

ENTRYPOINT

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

ENTRYPOINT 有两种可能的形式:

  • exec 形式,这是首选形式:
1
ENTRYPOINT [\"executable\", \"param1\", \"param2\"]
  • shell 形式:
1
ENTRYPOINT command param1 param2

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

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

1
$ 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 的任一种形式来设置更可能更改的附加默认值。

1
2
3
FROM ubuntu
ENTRYPOINT [\"top\", \"-b\"]
CMD [\"-c\"]

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

1
2
3
4
5
6
7
8
9
10
$ 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

1
2
3
4
5
$ 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):

1
2
3
4
5
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 信号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/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 信号,传递它们,然后进行更多工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/sh
# 注意:我使用 sh 编写此脚本,以便它也在 busybox 容器中工作

# 如果您还需要在服务停止后手动清理,或者需要在一个容器中启动多个服务,请使用 trap
trap \"echo TRAPed signal\" HUP INT QUIT TERM

# 在此处启动后台服务
/usr/sbin/apachectl start

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

# 在此处停止服务并清理
echo \"stopping apache\"
/usr/sbin/apachectl stop

echo \"exited $0\"

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ 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\t0m 0.27s
user\t0m 0.03s
sys\t0m 0.03s

注意

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

Shell 形式 ENTRYPOINT 示例

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

1
2
FROM ubuntu
ENTRYPOINT exec top -b

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

1
2
3
4
5
6
7
$ 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 时干净退出:

1
2
3
4
5
6
$ /usr/bin/time docker stop test

test
real\t0m 0.20s
user\t0m 0.02s
sys\t0m 0.04s

如果您忘记在 ENTRYPOINT 开头添加 exec

1
2
3
FROM ubuntu
ENTRYPOINT top -b
CMD -- --ignored-param1

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

1
2
3
4
5
6
7
8
9
10
11
$ 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

1
2
3
4
5
6
7
8
9
10
11
12
13
$ 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\t0m 10.19s
user\t0m 0.04s
sys\t0m 0.03s

理解 CMD 和 ENTRYPOINT 如何交互

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

  1. Dockerfile 应至少指定一个 CMDENTRYPOINT 命令。
  2. 当将容器用作可执行文件时,应定义 ENTRYPOINT
  3. CMD 应用作定义 ENTRYPOINT 命令的默认参数的方式,或用于在容器中执行临时命令。
  4. 当使用替代参数运行容器时,CMD 将被覆盖。

下表显示了不同 ENTRYPOINT / CMD 组合执行什么命令:

无 ENTRYPOINT ENTRYPOINT exec\_entry p1\_entry ENTRYPOINT \["exec\_entry", "p1\_entry"\]
无 CMD 错误,不允许 /bin/sh -c exec\_entry p1\_entry exec\_entry p1\_entry
CMD \["exec\_cmd", "p1\_cmd"\] exec\_cmd p1\_cmd /bin/sh -c exec\_entry p1\_entry exec\_entry p1\_entry exec\_cmd p1\_cmd
CMD exec\_cmd p1\_cmd /bin/sh -c exec\_cmd p1\_cmd /bin/sh -c exec\_entry p1\_entry exec\_entry p1\_entry /bin/sh -c exec\_cmd p1\_cmd

注意

如果 CMD 是从基础镜像定义的,设置 ENTRYPOINT 会将 CMD 重置为空值。在这种情况下,必须在当前镜像中定义 CMD 以具有值。

VOLUME

1
VOLUME [\"/data\"]

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

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

1
2
3
4
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

1
USER <user>[:<group>]

1
USER UID[:GID]

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

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

警告

当用户没有主要组时,镜像(或后续指令)将以 root 组运行。

在 Windows 上,如果用户不是内置帐户,则必须先创建用户。这可以通过在 Dockerfile 中调用 net user 命令来完成。

1
2
3
4
5
FROM microsoft/windowsservercore
# 在容器中创建 Windows 用户
RUN net user /add patrick
# 为后续命令设置它
USER patrick

WORKDIR

1
WORKDIR /path/to/workdir

WORKDIR 指令为 Dockerfile 中跟随它的任何 RUNCMDENTRYPOINTCOPYADD 指令设置工作目录。如果 WORKDIR 不存在,即使它不在任何后续 Dockerfile 指令中使用,也会创建它。

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

1
2
3
4
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

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

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

1
2
3
ENV DIRPATH=/path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd

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

如果未指定,默认工作目录是 /。实际上,如果您不是从头开始构建 Dockerfile (FROM scratch),WORKDIR 很可能由您使用的基础镜像设置。

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

ARG

1
ARG <name>[=<default value>] [<name>[=<default value>]...]

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

警告

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

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

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

1
2
3
4
FROM busybox
ARG user1
ARG buildno
# ...

默认值

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

1
2
3
4
FROM busybox
ARG user1=someuser
ARG buildno=1
# ...

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

作用域

ARG 变量从其在 Dockerfile 中声明的行开始生效。例如,考虑此 Dockerfile:

1
2
3
4
5
FROM busybox
USER ${username:-some_user}
ARG username
USER $username
# ...

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

1
$ 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。

1
2
3
4
FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=v1.0.0
RUN echo $CONT_IMG_VER

然后,假设使用此命令构建此镜像:

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

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

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

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

ARG 指令不同,ENV 值始终持久存在于构建的镜像中。考虑没有 --build-arg 标志的 docker build:

1
$ 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 标志传递它们,例如:

1
$ 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

1
2
FROM ubuntu
RUN echo \"Hello World\"

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

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

1
2
3
FROM ubuntu
ARG HTTP_PROXY
RUN echo \"Hello World\"

构建此 Dockerfile 时,HTTP_PROXY 保留在 docker history 中,并且更改其值会使构建缓存失效。

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

此功能仅在 BuildKit 后端可用。

BuildKit 支持一组预定义的 ARG 变量,其中包含执行构建的节点(构建平台)的平台信息和结果镜像的平台信息(目标平台)。目标平台可以使用 docker build 上的 --platform 标志指定。

以下 ARG 变量自动设置:

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

这些参数在全局作用域中定义,因此不会自动在构建阶段内或您的 RUN 命令中可用。要在构建阶段内公开这些参数之一,请重新定义它而不带值。

例如:

1
2
3
FROM alpine
ARG TARGETPLATFORM
RUN echo \"I'm building for $TARGETPLATFORM\"

BuildKit 内置构建参数

参数 类型 描述
BUILDKIT_BUILD_NAME 字符串 覆盖在 buildx history 命令Docker Desktop 构建视图 中显示的构建名称。
BUILDKIT_CACHE_MOUNT_NS 字符串 设置可选的缓存 ID 命名空间。
BUILDKIT_CONTEXT_KEEP_GIT_DIR 布尔值 触发 Git 上下文保留 .git 目录。
BUILDKIT_HISTORY_PROVENANCE_V1 布尔值 为构建历史记录启用 SLSA Provenance v1
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 信息,保留它可能很有用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# syntax=docker/dockerfile:1
FROM alpine
WORKDIR /src
RUN --mount=target=.
ke REVISION=$(git rev-parse HEAD) build
`

`console
docker build --build-arg BUILDKIT_CONTEXT_KEEP_GIT_DIR=1 https://github.com/user/repo.git#main
`

# 对构建缓存的影响

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

例如,考虑这两个 Dockerfile:

```dockerfile
FROM ubuntu
ARG CONT_IMG_VER
RUN echo $CONT_IMG_VER
1
2
3
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> 更改,您会得到缓存未命中。

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

1
2
3
4
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:

1
2
3
4
FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=hello
RUN echo $CONT_IMG_VER

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

ONBUILD

1
ONBUILD INSTRUCTION

ONBUILD 指令向镜像添加一个延迟执行的触发器指令,以便在以后的时间执行,当镜像用作另一个构建的基础时。触发器将在下游构建的上下文中执行,就好像它已插入到下游 Dockerfile 中 FROM 指令之后一样。

如果您正在构建一个将用作构建其他镜像的基础的镜像,这很有用,例如应用程序构建环境或可以使用用户特定配置定制的守护程序。

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

解决方案是使用 ONBUILD 注册预先指令,以便在下一个构建阶段运行。

它的工作原理如下:

  1. 当构建器遇到 ONBUILD 指令时,它会向正在构建的镜像的元数据添加一个触发器。该指令不会以其他方式影响当前构建。
  2. 在构建结束时,所有触发器的列表存储在镜像清单中,键为 OnBuild。它们可以使用 docker inspect 命令检查。
  3. 稍后,镜像可能用作新构建的基础,使用 FROM 指令。作为处理 FROM 指令的一部分,下游构建器查找 ONBUILD 触发器,并按它们注册的顺序执行它们。如果任何触发器失败,FROM 指令将中止,从而导致构建失败。如果所有触发器都成功,FROM 指令完成,构建照常继续。
  4. 触发器在执行后从最终镜像中清除。换句话说,它们不会被“孙”构建继承。

例如,您可能会添加如下内容:

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

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

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

1
2
3
4
# 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

1
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 秒,则检查被视为失败。执行检查的进程会突然停止,并发出 SIGKILL

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

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

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

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

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

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

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

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

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

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

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

SHELL

1
SHELL [\"executable\", \"parameters\"]

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

SHELL 指令在 Windows 上特别有用,那里有两个常用且非常不同的本机 shell:cmdpowershell,以及可用的替代 shell,包括 sh

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM microsoft/windowsservercore

# 执行为 cmd /S /C echo default
RUN echo default

# 执行为 cmd /S /C powershell -command Write-Host default
RUN powershell -command Write-Host default

# 执行为 powershell -command Write-Host hello
SHELL [\"powershell\", \"-command\"]
RUN Write-Host hello

# 执行为 cmd /S /C echo hello
SHELL [\"cmd\", \"/S\", \"/C\"]
RUN echo hello

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

以下示例是 Windows 上常见的模式,可以通过使用 SHELL 指令来简化:

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

构建器调用的命令将是:

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

这效率低下,原因有二。首先,调用了不必要的 cmd.exe 命令处理器(又名 shell)。其次,shell 形式的每个 RUN 指令都需要额外的 powershell -command 前缀命令。

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

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

虽然 JSON 形式明确且不使用不必要的 cmd.exe,但它确实需要更多的冗长,通过双引号和转义。替代机制是使用 SHELL 指令和 shell 形式,为 Windows 用户提供更自然的语法,尤其是在与 escape 解析器指令结合使用时:

1
2
3
4
5
6
7
# 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'

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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 指令也可以在 Linux 上使用,如果需要替代 shell,如 zshcshtcsh 等。

Here-Documents

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

示例:运行多行脚本

1
2
3
4
5
6
7
# syntax=docker/dockerfile:1
FROM debian
RUN <<EOT bash
set -ex
apt-get update
apt-get install -y vim
EOT

如果命令仅包含 here-document,则其内容使用默认 shell 评估。

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

或者,shebang 头可用于定义解释器。

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

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

1
2
3
4
5
6
7
8
9
# 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 文件。

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

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

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

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

1
2
3
$ docker build -t heredoc .
$ docker run heredoc
hello bar

如果您改为引用 here-document 单词 EOT 的任何部分,则变量在构建时不会扩展。

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

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

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

Dockerfile 示例

有关 Dockerfile 的示例,请参阅:

作者

孤独小狼

发布于

2025-12-15

更新于

2025-12-15

许可协议

评论