Docker 中的 Dockerfile
Docker 可以通过读取 Dockerfile 中的指令来自动构建镜像。Dockerfile 是一个文本文档,其中包含了用户可以在命令行上调用的所有命令,以组装镜像。
命令 | 描述 |
---|---|
ADD |
添加本地或远程的文件和目录。 |
ARG |
使用构建时的变量。 |
CMD |
指定默认命令。 |
COPY |
复制文件和目录。 |
ENTRYPOINT |
指定默认可执行文件。 |
ENV |
设置环境变量。 |
EXPOSE |
描述应用程序监听的端口。 |
FROM |
从基础镜像创建一个新的构建阶段。 |
HEALTHCHECK |
启动时检查容器的健康状况。 |
LABEL |
为镜像添加元数据。 |
MAINTAINER |
指定镜像的作者。 |
ONBUILD |
指定在镜像用于构建时的指令。 |
RUN |
执行构建命令。 |
SHELL |
设置镜像的默认 shell。 |
STOPSIGNAL |
指定退出容器时的系统调用信号。 |
USER |
设置用户和组 ID。 |
VOLUME |
创建卷挂载。 |
WORKDIR |
更改工作目录。 |
一、格式要求
以下是 Dockerfile 的格式:
1 | # 注释 |
指令不区分大小写。但按照惯例,通常使用大写字母以便更容易与参数区分。
Docker 按照 Dockerfile 中的指令顺序执行。Dockerfile 必须以 FROM
指令开头。在此之前可以包含解析器指令、注释和全局范围的 ARG
指令。FROM
指令指定了构建时所使用的基础镜像。FROM
指令之前只能有一个或多个 ARG
指令,这些指令声明了在 Dockerfile 的 FROM
行中使用的参数。
以 #
开头的行会被视为注释,除非该行是有效的解析器指令。行中其他位置的 #
标记被视为参数。这允许类似以下的语句:
1 | # 注释 |
在执行 Dockerfile 指令之前,注释行会被移除。以下示例中的注释在 shell 执行 echo
命令之前会被移除。
1 | RUN echo hello \ |
以下示例与上述示例等效:
1 | RUN echo hello \ |
注释不支持行 continuation 字符。
1、关于空格的说明
为了向后兼容,注释 #
和指令(如 RUN
)前的空格会被忽略,但不推荐这样做。在这些情况下,前导空格不会被保留,因此以下示例是等效的:
1 | # 这是一个注释行 |
然而,指令参数中的空格不会被忽略。以下示例会按照指定的前导空格打印 hello world
:
1 | RUN echo "\ |
二、解析器指令(parser directives)
解析器指令是可选的,它们影响 Dockerfile 中后续行的处理方式。这些指令不会为构建添加层,也不会显示为构建步骤。它们以特殊形式的注释形式书写,格式为 # directive=value
。
支持的解析器指令包括:
syntax
escape
- 自 Dockerfile v1.8.0 起支持的
check
需要注意的是,一旦处理了注释、空行或构建器指令后,BuildKit 就不再查找解析器指令了。相反,它会将任何格式化的解析器指令视为注释,并不尝试验证它是否可能是解析器指令。因此,所有的解析器指令必须放在 Dockerfile 的顶部。
解析器指令键如 syntax
或 check
不区分大小写,但习惯上使用小写。指令的值区分大小写,必须以适当的大小写书写。例如, #check=skip=jsonargsrecommended
是无效的,因为 check
必须使用 Pascal case 命名法,而不是小写。通常在任何解析器指令后包含一个空行。解析器指令中不支持行延续字符。
由于这些规则,下面的例子都是无效的:
由于使用了行延续字符而无效:
1 | # direc \ |
由于同一个指令出现了两次而无效:
1 | # directive=value1 |
因为出现在构建器指令之后被视为注释:
1 | FROM ImageName |
因为出现在非解析器指令的注释之后被视为注释:
1 | # About my dockerfile |
未知的 unknowndirective
被视为注释,因为它未被识别;已知的 syntax
指令也被视为注释,因为它出现在一个非解析器指令的注释之后。
1 | # unknowndirective=value |
此外,解析器指令中允许出现非换行空格,因此以下各行都被同等对待:
1 | #directive=value |
1、syntax
使用 syntax
解析器指令来声明用于构建的 Dockerfile 语法版本。如果未指定,BuildKit 将使用捆绑的 Dockerfile 前端版本。声明语法版本可以让你自动使用最新的 Dockerfile 版本,而无需升级 BuildKit 或 Docker Engine,甚至可以使用自定义的 Dockerfile 实现。
大多数用户会希望将此解析器指令设置为 docker/dockerfile:1
,这将导致 BuildKit 在构建前拉取最新的稳定版 Dockerfile 语法。
1 | # syntax=docker/dockerfile:1 |
有关解析器指令如何工作的更多信息,请参阅 Custom Dockerfile syntax | Docker Docs
2、escape
escape
指令设置了在 Dockerfile 中用于转义字符的符号。如果没有指定,默认的转义字符是反斜杠 \
。
转义字符既可以用来转义一行中的字符,也可以用来转义换行符。这允许一个 Dockerfile 指令跨越多行。需要注意的是,无论是否在 Dockerfile 中包含了 escape
解析器指令,在 RUN
命令中都不会执行转义,除非是在行尾。
将转义字符设置为反引号 `
在 Windows 上特别有用,因为在 Windows 中路径分隔符是反斜杠 \
。`
与 Windows PowerShell 保持一致。
考虑以下示例,它会在 Windows 上以一种不明显的方式失败。第二行末尾的第二个反斜杠 \
会被解释为对换行符的转义,而不是第一个反斜杠 \
的转义目标。类似地,第三行末尾的反斜杠 \
如果被视为一条指令处理的话,会导致其被视为行延续。此 Dockerfile 的结果是第二和第三行被认为是单个指令:
1 | FROM microsoft/nanoserver |
导致的结果是:
1 | PS E:\myproject> docker build -t cmd . |
上述问题的一个解决方案是在 COPY
指令和 dir
命令的目标中都使用 /
。然而,这种语法在最好情况下是令人困惑的,因为它不是 Windows 上路径的自然形式,并且在最坏的情况下容易出错,因为并非所有 Windows 上的命令都支持 /
作为路径分隔符。
通过添加 escape
解析器指令,下面的 Dockerfile 使用了 Windows 上文件路径的自然平台语义,如预期那样成功执行:
1 | # escape=` |
结果是:
1 | PS E:\myproject> docker build -t succeeds --no-cache=true . |
3、check
check
指令用于配置如何评估构建检查。默认情况下,所有检查都会运行,并且失败被视为警告。
你可以使用 #check=skip=<检查名称>
来禁用特定的检查。要指定多个需要跳过的检查,请用逗号将它们隔开:
1 | # check=skip=JSONArgsRecommended,StageNameCasing |
若要禁用所有检查,请使用 #check=skip=all
。
默认情况下,即使有警告信息,包含失败构建检查的构建仍会以状态码 0 退出。如果希望构建在出现警告时失败,请设置 #check=error=true
。
1 | # check=error=true |
注意:当使用带有 error=true
选项的 check
指令时,建议将 Dockerfile 语法固定到特定版本。否则,当未来版本中添加新的检查时,您的构建可能会开始失败。
要结合使用 skip
和 error
选项,可以使用分号将它们分开:
1 | # check=skip=JSONArgsRecommended;error=true |
要查看所有可用的检查,请参阅 Build checks | Docker Docs。请注意,可用的检查取决于 Dockerfile 语法版本。为了确保您获得最新的检查更新,可以使用 syntax
指令将 Dockerfile 语法版本指定为最新的稳定版本。这样可以帮助你在使用最新特性的同时避免因为新加入的检查而导致构建失败的问题。
三、环境变量替换
环境变量(通过 ENV
语句声明)也可以在某些指令中作为变量使用,由 Dockerfile 解释。转义符也用于将类似变量的语法直接包含在语句中。
在 Dockerfile 中,环境变量可以用 $variable_name
或 ${variable_name}
表示。它们的处理方式是等价的,而花括号语法通常用于解决变量名中没有空格的问题,例如 ${foo}_bar
。
${variable_name}
语法还支持一些标准的 bash
修饰符,如下所示:
${variable:-word}
表示如果variable
已设置,则结果为该值。如果variable
未设置,则结果为word
。${variable:+word}
表示如果variable
已设置,则结果为word
,否则结果为空字符串。
在使用 # syntax=docker/dockerfile-upstream:master
语法指令的 Dockerfile 中,以下变量替换在 Dockerfile 语法的预发布版本中受支持:
${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}
将variable
中第一个匹配的pattern
替换为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 | FROM busybox |
Dockerfile 中的以下指令支持环境变量:
ADD
COPY
ENV
EXPOSE
FROM
LABEL
STOPSIGNAL
USER
VOLUME
WORKDIR
ONBUILD
(当与上述支持的指令结合使用时)
你也可以在 RUN
、CMD
和 ENTRYPOINT
指令中使用环境变量,但在这些情况下,变量替换由命令 shell 处理,而不是构建器。请注意,使用 exec 形式的指令不会自动调用命令 shell。参见 变量替换
环境变量替换在整个指令中对每个变量使用相同的值。更改变量的值仅在后续指令中生效。考虑以下示例:
1 | ENV abc=hello |
def
的值为hello
ghi
的值为bye
四、dockerignore 文件
您可以使用 .dockerignore
文件从构建上下文中排除文件和目录。有关更多信息,请参见 Build context | Docker Docs
五、shell 和 exec 格式
RUN
、CMD
和 ENTRYPOINT
指令都有两种可能的形式:
- exec 形式,
INSTRUCTION ["executable","param1","param2"]
- shell 形式,
INSTRUCTION command param1 param2
Exec 形式使得避免 shell 字符串操作成为可能,并且可以使用特定的命令 shell 或任何其他可执行文件来调用命令。它使用 JSON 数组语法,数组中的每个元素都是一个命令、标志或参数。
Shell 形式更加宽松,强调易用性、灵活性和可读性。Shell 形式会自动使用命令 shell,而 exec 形式则不会。
1、exec 格式
该形式被解析为一个 JSON 数组,这意味着你必须使用双引号(”)而不是单引号(’)包围单词。
1 | ENTRYPOINT ["/bin/bash", "-c", "echo hello"] |
Exec 形式最适合用于指定 ENTRYPOINT
指令,结合 CMD
设置可以在运行时覆盖的默认参数。
1.1 变量替换
使用 exec 形式不会自动调用命令 shell。这意味着正常的 shell 处理,如变量替换,不会发生。例如,RUN [ "echo", "$HOME" ]
不会对 $HOME
进行变量替换。
如果你想进行 shell 处理,则要么使用 shell 形式,要么直接使用 exec 形式执行一个 shell,例如:RUN [ "sh", "-c", "echo $HOME" ]
。当使用 exec 形式并直接执行 shell 时,如同 shell 形式一样,是 shell 在做环境变量替换,而不是构建器在做。
1.2 反斜杠
在 exec 形式中,你必须转义反斜杠。这在 Windows 上尤其重要,因为反斜杠是路径分隔符。例如,如果没有正确转义,下面的行由于不是有效的 JSON 而被视为 shell 形式,可能会以意想不到的方式失败:
1 | RUN ["c:\windows\system32\tasklist.exe"] |
正确的语法应该是:
1 | RUN ["c:\\windows\\system32\\tasklist.exe"] |
2、Shell 格式
与 exec 形式不同,使用 shell 形式的指令总是使用命令 shell。Shell 形式不使用 JSON 数组格式,而是使用常规字符串。Shell 形式的字符串允许你使用转义字符(默认为反斜杠)来转义换行符,从而将单个指令继续到下一行。这使得它在处理较长命令时更易于使用,因为它允许你将它们拆分为多行。例如,考虑以下两行:
1 | RUN source $HOME/.bashrc && \ |
它们等同于:
1 | RUN source $HOME/.bashrc && echo $HOME |
你还可以对支持的命令使用 heredoc 来分割 shell 形式,例如:
1 | RUN <<EOF |
关于 heredocs 的更多信息,请参阅 Here-documents Dockerfile reference | Docker Docs
3、使用不同的 shell
你可以使用 SHELL
命令更改默认 shell。例如:
1 | SHELL ["/bin/bash", "-c"] |
六、命令
1、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
是唯一可以在 FROM
之前的 Dockerfile 中使用的指令。
FROM
可以在单个 Dockerfile 中出现多次,以创建多个镜像或将一个构建阶段作为另一个构建阶段的依赖项。只需在每个新的 FROM
指令前记录下由提交输出的最后一个镜像 ID 即可。在每个镜像创建完成后,Docker 命令行界面会输出该镜像的 ID。每个 FROM
指令都会清除之前指令创建的所有状态。
通过在 FROM
指令中添加 AS name
,可以给新的构建阶段命名。这个名字可以在随后的 FROM <name>
、COPY --from=<name>
和 RUN --mount=type=bind,from=<name>
指令中使用,以引用该阶段构建的镜像。
tag
和 digest
是可选的。如果省略它们中的任何一个,默认情况下构建器会假设使用 latest
标签。如果构建器找不到 tag
,则会返回错误。
可选的 --platform
标志可用于指定 FROM
引用的多平台镜像的平台。例如 linux/amd64、linux/arm64 或 windows/amd64。默认情况下,使用构建请求的目标平台。全局构建参数可以用在这个标志的值中,例如自动平台 ARGs
允许你强制一个阶段到本地构建平台(--platform=$BUILDPLATFORM
),并在阶段内使用它跨平台编译到目标平台。
1.1 了解 ARG
和 FROM
如何交互
FROM
指令支持由第一个 FROM
之前出现的任何 ARG
指令声明的变量。
1 | ARG CODE_VERSION=latest |
在 FROM
之前的 ARG
是位于构建阶段之外的,因此不能用于 FROM
后的任何指令。要在第一个 FROM
之前使用声明的 ARG
的默认值,可以在构建阶段内使用没有值的 ARG
指令:
1 | ARG VERSION=latest |
这段配置首先定义了一个默认值为 latest
的 ARG VERSION
,然后将其用于 FROM
指令来选择基础镜像,并在接下来的构建阶段重新声明了 VERSION
来使用它的值。
2、COPY
1 | COPY <src> <dest> |
COPY 指令复制 <src>
所指向的文件或目录,将它添加到新镜像中,复制的文件或目录在镜像中的路径是 <dest>
。<src>
所指定的源可以有多个,但必须在上下文中,即必须是上下文根目录的相对路径。不能使用形如 COPY ../something /something
这样的指令。此外,<src>
可以使用通配符指向所有匹配通配符的文件或目录,例如,COPY hom\* /mydir/
表示添加所有以“hom”开头的文件到目录 /mydir/
中。
<dest>
可以是文件或目录,但必须是目标镜像中的绝对路径或者相对于 WORKDIR
的相对路径(WORKDIR 即 Dockerfile 中 WORKDIR
指令指定的路径,用来为其他指令设置工作目录)。
若 <dest>
以反斜杠结尾则其指向的是目录;否则指向文件。<src>
同理。
若 <dest>
是一个文件,则 <src>
的内容会被写入到 <dest>
中;否则 <src>
所指向的文件或目录中的内容会被复制添加到 <dest>
目录中。
当 <src>
指定多个源时,<dest>
必须是目录。另外,如果 <dest>
不存在,则路径中不存在的目录会被创建。
3、ADD
ADD
指令有两种形式,后者对于包含空格的路径是必需的。
1 | ADD [OPTIONS] <src> ... <dest> |
可用的 [OPTIONS]
选项包括:
选项 | 最低 Dockerfile 版本 |
---|---|
–keep-git-dir | 1.1 |
–checksum | 1.6 |
–chown | - |
–chmod | 1.2 |
–link | 1.4 |
–exclude | 1.7-labs |
ADD
指令从 <src>
复制新的文件或目录,并将它们添加到镜像文件系统中的 <dest>
路径。文件和目录可以从构建上下文、远程 URL 或 Git 仓库中复制。
ADD
和 COPY
指令在功能上相似,但用途略有不同。了解更多关于 ADD
和 COPY
之间的区别请查看 add-or-copy | Best practices | Docker Docs
ADD
可以自动解压识别为压缩包的本地.tar
文件到指定的目标路径(如果是远程 URL 下载的内容,则不会自动解压),并且支持直接从远程 URL 或 Git 仓库克隆代码。COPY
则主要用于简单地复制本地文件到镜像中,不支持上述ADD
的一些额外特性,如自动解压等。
选择使用哪一个指令取决于你的具体需求。如果需要的功能只是简单地复制文件,推荐使用 COPY
,因为它更透明且预期行为更明确。如果需要额外的功能,比如从 URL 获取资源或者自动解压 .tar
文件,那么可以考虑使用 ADD
。
请注意,对于包含空格的路径,应该使用第二种形式(即带引号的形式)以确保路径被正确解析。例如:
1 | ADD ["path/to/my file", "destination/path"] |
这样可以避免由于路径中含有空格而导致的问题。
3.1 ADD 的其他描述
ADD 与 COPY 指令在功能上很相似,都支持复制本地文件到镜像的功能,但 ADD 指令还支持其他功能。
<src>
可以是一个指向一个网络文件的 URL,此时若 <dest>
指向一个目录,则 URL 必须是完全路径,这样可以获得该网络文件的文件名 filename,该文件会被复制添加到 <dest>/<filename>
。例如,ADD http://example.com/foobar /
会创建文件 /foobar
。
<src>
还可以指向一个本地压缩归档文件,该文件在复制到容器中时会被解压提取,如 ADD example.tar.xz /
。但若 URL 中的文件为归档文件则不会被解压提取。
ADD 和 COPY 指令虽然功能相似,但一般推荐使用 COPY,因为 COPY 只支持本地文件,相比 ADD 而言,它更透明。
3.2 源文件
ADD
指令允许你指定多个源文件或目录。最后一个参数必须始终是目标路径。例如,要将 file1.txt
和 file2.txt
从构建上下文添加到构建容器中的 /usr/src/things/
:
1 | ADD file1.txt file2.txt /usr/src/things/ |
如果指定了多个源文件(直接指定或使用通配符),则目标必须是一个目录(必须以斜杠 /
结尾)。
你可以指定一个 URL 或 Git 仓库地址作为源。例如:
1 | ADD https://example.com/archive.zip /usr/src/things/ |
BuildKit 会检测 <src>
的类型并相应地处理它。
- 如果
<src>
是本地文件或目录,则该目录的内容会被复制到指定的目标。 - 如果
<src>
是本地 tar 归档文件,则它会被解压缩并提取到指定的目标。 - 如果
<src>
是一个 URL,URL 的内容会被下载并放置在指定的目标位置。 - 如果
<src>
是 Git 仓库,则该仓库会被克隆到指定的目标。
3.2.1 从构建上下文添加文件
任何不以 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 和归档文件的其余部分将作为构建的上下文。这意味着你可以利用 tar 归档文件来提供所需的构建上下文,而不需要依赖默认的构建上下文目录。这种方式对于需要动态生成构建上下文的场景特别有用。
3.2.2 模式匹配
对于本地文件,每个 <src>
可能包含通配符,并按照 Go 的 filepath.Match
规则进行匹配。例如,要添加构建上下文根目录下所有以 .png
结尾的文件和目录:
1 | ADD *.png /dest/ |
以下示例中,?
是单字符通配符,可以匹配例如 index.js
和 index.ts
:
1 | ADD index.?s /dest/ |
当添加包含特殊字符(如 [
和 ]
)的文件或目录时,需要遵循 Golang 规则转义这些路径,以防止它们被视为匹配模式。例如,要添加名为 arr[0].txt
的文件,使用如下命令:
1 | ADD arr[[]0].txt /dest/ |
3.2.3 添加本地 tar 归档文件
当使用本地 tar 归档文件作为 ADD
的源,并且归档文件处于公认的压缩格式(gzip
、bzip2
或 xz
,或未压缩),归档文件会被解压缩并提取到指定的目标。只有本地 tar 归档文件会被解压。如果是远程 URL 的 tar 归档文件,归档文件不会被解压,而是下载并放置在目标位置。
当一个目录被解压时,它的行为与 tar -x
相同。结果是以下两者的联合:
- 目标路径上已存在的内容;
- 源树的内容,任何冲突将以逐文件为基础解决,新添加的内容优先。
注意:一个文件是否被识别为公认的压缩格式仅基于文件的内容,而不是文件的名称。例如,如果一个空文件恰好以 .tar.gz
结尾,它不会被识别为压缩文件,并且不会生成任何解压缩错误消息,而是简单地将该文件复制到目标位置。
3.2.4 从 URL 添加文件
在源是远程文件 URL 的情况下,目标将具有 600 的权限。如果 HTTP 响应包含 Last-Modified
头,则该头中的时间戳将用于设置目标文件的 mtime
。但是,如同其他在 ADD
过程中处理的文件一样,mtime
不包括在确定文件是否已更改以及是否应更新缓存的考量中。
如果目标路径以斜杠结尾,则文件名会根据 URL 路径推断。例如,ADD http://example.com/foobar /
会在根目录创建名为 foobar
的文件。URL 必须有一个非空的路径以便能够正确推断出文件名(http://example.com
这样的 URL 是无效的,因为它没有提供足够的信息来确定文件名)。
如果目标路径不以斜杠结尾,则目标路径将成为从 URL 下载的文件的文件名。例如,ADD http://example.com/foo /bar
会创建一个名为 /bar
的文件。
如果你的 URL 文件使用身份验证进行保护,则需要在容器内使用 RUN wget
或 RUN curl
等工具来进行下载,因为 ADD
指令不支持身份验证。这意味着对于需要认证才能访问的资源,你不能直接使用 ADD
指令,而应该通过其他命令先获取文件,再将其添加到镜像中。
3.2.5 从 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 仓库添加时,文件的权限位为 644。如果仓库中的文件设置了可执行位,则权限将设置为 755。目录的权限设置为 755。
使用 Git 仓库作为源时,仓库必须可以从构建上下文中访问。要通过 SSH 添加私有仓库,必须传递 SSH 密钥进行身份验证。例如,给定以下 Dockerfile:
1 | # syntax=docker/dockerfile:1 |
为了构建这个 Dockerfile,需将 --ssh
标志传递给 docker build
以挂载 SSH 代理套接字到构建环境中。例如:
1 | docker build --ssh default . |
5、ENTRYPOINT
ENTRYPOINT
允许你给容器配置一个可执行文件,该容器在启动时会执行该文件中的内容。
ENTRYPOINT
有两种可能的形式:
优先使用的 exec 形式:
1 | ENTRYPOINT ["executable", "param1", "param2"] |
shell 形式:
1 | ENTRYPOINT command param1 param2 |
例如,下面的命令使用默认内容启动了一个监听 80 端口的 nginx 容器:
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
指令会生效。这意味着如果你在 Dockerfile 中定义了多个 ENTRYPOINT
指令,只有最后一个会被实际应用。
5.1 ENTRYPOINT
指令的 exec 形式示例
可以使用 ENTRYPOINT
的 exec 形式来设置相对稳定的默认命令和参数,然后使用 CMD
的任一形式来设置更可能被更改的额外默认值。
1 | FROM ubuntu |
当你运行容器时,可以看到 top
是唯一的进程:
1 | docker run -it --rm --name test top -H |
为了进一步检查结果,可以使用 docker exec
:
1 | docker exec -it test ps aux |
并且可以优雅地请求 top
关闭,使用 docker stop test
。
下面的 Dockerfile 展示了如何使用 ENTRYPOINT
来以前台模式(即作为 PID 1
)运行 Apache:
1 | FROM debian:stable |
如果你需要为单一可执行文件编写启动脚本,可以确保最终的可执行文件接收 Unix 信号,通过使用 exec
和 gosu
命令:
1 |
|
最后,如果在关闭时需要进行一些额外的清理工作(或与其他容器通信),或者协调多个可执行文件,你可能需要确保 ENTRYPOINT
脚本接收 Unix 信号,传递它们,并完成更多的工作:
1 |
|
如果使用 docker run -it --rm -p 80:80 --name test apache
运行此镜像,可以使用 docker exec
或 docker top
检查容器的进程,然后要求脚本停止 Apache:
1 | docker exec -it test ps aux |
注意:
你可以使用 --entrypoint
覆盖 ENTRYPOINT
设置,但这只能设置要执行的二进制文件(不会使用 sh -c
)。这意味着如果你想覆盖 ENTRYPOINT
,则必须直接指定一个新的入口点程序,而不能是一个 shell 命令链。
5.2 ENTRYPOINT
指令的 shell 形式示例
你可以为 ENTRYPOINT
指定一个简单的字符串,它将在 /bin/sh -c
中执行。这种形式将使用 shell 处理来替换 shell 环境变量,并会忽略任何 CMD
或 docker run
命令行参数。为了确保 docker stop
能正确地向任何长时间运行的 ENTRYPOINT
可执行文件发出信号,需要记得在启动时使用 exec
:
1 | FROM ubuntu |
当你运行这个镜像时,你会看到单个 PID 1
进程:
1 | docker run -it --rm --name test top |
它会在执行 docker stop
时干净地退出:
1 | /usr/bin/time docker stop test |
如果忘记在 ENTRYPOINT
的开头添加 exec
:
1 | FROM ubuntu |
然后你可以运行它(给它命名以便进行下一步):
1 | docker run -it --name test top --ignored-param2 |
从 top
的输出中可以看到,指定的 ENTRYPOINT
不是 PID 1
。
如果之后你运行 docker stop test
,容器不会干净地退出——停止命令会在超时后被迫发送 SIGKILL
:
1 | docker exec -it test ps waux |
这段内容展示了如何正确地使用 exec
来确保 ENTRYPOINT
启动的进程可以接收到 Docker 发送的关闭信号,从而实现优雅的关闭过程。如果不使用 exec
,可能会导致容器无法正常接收关闭信号,最终需要强制终止。
5.3 了解 CMD 和 ENTRYPOINT 如何交互
CMD
和 ENTRYPOINT
指令都定义了在运行容器时执行的命令。有一些规则描述了它们之间的协作:
- Dockerfile 应该至少指定
CMD
或ENTRYPOINT
中的一个。 - 当将容器用作可执行文件时,应该定义
ENTRYPOINT
。 CMD
应该用于为ENTRYPOINT
命令定义默认参数,或者用于在容器中执行临时命令。- 当使用替代参数运行容器时,
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
才能使其具有值。
这意味着当你设置了 ENTRYPOINT
而没有明确指定 CMD
时,CMD
的默认值将会丢失,除非你在当前的 Dockerfile 中重新定义它。这种机制允许你灵活地配置容器以适应不同的场景,同时保持基础镜像的通用性。通过正确配置 ENTRYPOINT
和 CMD
,可以确保容器既可以作为独立的应用程序执行,也可以接受自定义参数进行更灵活的操作。
6、CMD
CMD
指令用于设置从镜像运行容器时要执行的命令。
你可以使用 shell 形式或 exec 形式来指定 CMD 指令:
- exec 形式,
CMD ["executable","param1","param2"]
- exec 形式,作为
ENTRYPOINT
的默认参数,CMD ["param1","param2"]
- shell 形式,
CMD command param1 param2
在一个 Dockerfile 中只能有一个 CMD
指令。如果你列出了多个 CMD
,只有最后一个会生效。
CMD
的目的是为正在执行的容器提供默认设置。这些默认值可以包含一个可执行文件,或者不包含可执行文件,在这种情况下,你必须同时指定一个 ENTRYPOINT
指令。
如果你想让容器每次都运行相同的可执行文件,你应该考虑将 ENTRYPOINT
与 CMD
结合使用。如果用户在 docker run
命令中指定了参数,则这些参数会覆盖 CMD
中指定的默认值,但仍然会使用默认的 ENTRYPOINT
。
如果 CMD
是用来为 ENTRYPOINT
指令提供默认参数的,CMD
和 ENTRYPOINT
指令都应该以 exec 形式指定。
注意:不要将 RUN
与 CMD
混淆。RUN
实际上是运行一个命令并提交结果;而 CMD
在构建时不会执行任何操作,它只是为镜像指定了预期的命令。这意味着 RUN
主要用于在构建过程中执行命令并创建新的镜像层,而 CMD
则定义了容器启动时默认执行的命令或参数。当基于该镜像启动容器时,CMD
提供的命令或参数会被执行,除非启动容器时通过命令行参数覆盖了它们。
CMD
指令提供容器运行时的默认值,这些默认值可以是一条指令,也可以是一些参数。一个 Dockerfile 中可以有多条 CMD
指令,但只有最后一条 CMD
指令有效。CMD ["param1", "param2"]
格式是在 CMD
指令和 ENTRYPOINT
指令配合时使用的,CMD
指令中的参数会添加到 ENTRYPOINT
指令中。使用 shell 和 exec 格式时,命令在容器中的运行方式与 RUN
指令相同。不同在于,RUN
指令在构建镜像时执行命令,并生成新的镜像;CMD
指令在构建镜像时并不执行任何命令,而是在容器启动时默认将 CMD
指令作为第一条执行的命令。如果用户在命令行界面运行 docker run
命令时指定了命令参数,则会覆盖 CMD
指令中的命令。
7、VOLUME
1 | VOLUME ["/data"] |
VOLUME
指令创建一个具有指定名称的挂载点,并将其标记为保存来自本地主机或其他容器的外部挂载卷。该值可以是一个 JSON 数组,如 VOLUME ["/var/log/"]
,或者是一个包含多个参数的简单字符串,例如 VOLUME /var/log
或 VOLUME /var/log /var/db
。有关更多信息、示例以及通过 Docker 客户端进行挂载的说明,请参阅 通过卷共享目录 | docker docs
docker run
命令会用基础镜像中指定位置存在的任何数据初始化新创建的卷。例如,考虑以下 Dockerfile 片段:
1 | FROM ubuntu |
这个 Dockerfile 生成的镜像会导致 docker run
在 /myvol
创建一个新的挂载点,并将 greeting
文件复制到新创建的卷中。
7.1 关于指定卷的注意事项
在 Dockerfile 中使用卷时,请记住以下几点:
- 基于 Windows 的容器中的卷:当使用基于 Windows 的容器时,容器中的卷的目的地必须是
- 一个不存在或为空的目录
C:
驱动器之外的驱动器
- 从 Dockerfile 内部更改卷:如果在声明卷之后的任何构建步骤改变了卷内的数据,在使用旧版构建器时这些更改会被丢弃。而使用 Buildkit 时,这些更改则会被保留。
- JSON 格式化:列表被解析为一个 JSON 数组。你必须用双引号 (
"
) 而不是单引号 ('
) 包围单词。 - 主机目录是在容器运行时声明的:由于其性质,主机目录(挂载点)依赖于主机。这是为了保持镜像的可移植性,因为不能保证给定的主机目录在所有主机上都可用。因此,你不能从 Dockerfile 内部挂载主机目录。
VOLUME
指令不支持指定host-dir
参数。你必须在创建或运行容器时指定挂载点。
8、USER
1 | USER <user>[:<group>] |
1 | USER UID[:GID] |
USER
指令设置用户名(或UID)以及可选的用户组(或GID),作为当前阶段剩余部分的默认用户和组。指定的用户用于 RUN
指令,并在运行时执行相关的 ENTRYPOINT
和 CMD
命令。
注意:
- 当为用户指定一个组时,该用户将仅具有指定的组成员身份。任何其他配置的组成员身份都将被忽略。
- 如果用户没有主组,则镜像(或后续指令)将以
root
组运行。 - 在 Windows 上,如果用户不是内置账户,则必须首先创建用户。这可以通过 Dockerfile 中调用
net user
命令来完成。 - 对于 Linux 系统,确保在 Dockerfile 中指定的用户和组事先已经存在,否则构建过程将会失败。你可以通过在 Dockerfile 中添加必要的用户和组创建步骤来解决这个问题。例如,可以使用
RUN groupadd -r mygroup && useradd -r -g mygroup myuser
来创建一个新的用户和组。
1 | FROM microsoft/windowsservercore |
这段示例展示了如何在基于 Windows 的 Docker 容器中创建并设置一个新用户 patrick
。首先使用 net user /add patrick
命令创建用户,然后通过 USER patrick
指令使之后的所有命令都以这个新用户的身份运行。
9、WORKDIR
1 | WORKDIR /path/to/workdir |
WORKDIR
指令设置 Dockerfile 中其后任何 RUN
、CMD
、ENTRYPOINT
、COPY
和 ADD
指令的工作目录。如果指定的 WORKDIR
不存在,它将会被创建,即使在后续的 Dockerfile 指令中没有使用这个目录。
WORKDIR
指令可以在 Dockerfile 中多次使用。如果提供了相对路径,它将是相对于前一个 WORKDIR
指令的路径。例如:
1 | WORKDIR /a |
在这个 Dockerfile 中,最后的 pwd
命令的输出将是 /a/b/c
。
WORKDIR
指令可以解析之前通过 ENV
设置的环境变量。你只能使用在 Dockerfile 中明确设置的环境变量。例如:
1 | ENV DIRPATH=/path |
在这个 Dockerfile 中,最后的 pwd
命令的输出将是 /path/$DIRNAME
(注意这里的 $DIRNAME
需要在之前或之后被定义,否则将直接作为字符串处理)。
如果没有指定 WORKDIR
,默认工作目录是 /
。实际上,如果你不是从头开始构建 Dockerfile(即不使用 FROM scratch
),你所使用的基础镜像可能会设置 WORKDIR
。
因此,为了避免在未知目录下进行意外操作,最佳实践是显式地设置你的 WORKDIR。
10、ARG
ARG
指令定义了一个变量,用户可以在构建时通过 docker build
命令使用 --build-arg <varname>=<value>
标志将值传递给构建器。
警告:不建议使用构建参数来传递诸如用户凭据、API 令牌等敏感信息。构建参数可以通过 docker history
命令查看,并且在 max
模式的出处声明中也是可见的,这些声明默认会附加到镜像上,特别是当你使用 Buildx GitHub Actions 并且你的 GitHub 仓库是公开的情况下。要了解在构建镜像时安全使用密钥的方法,请参阅 RUN –mount=type=secret | docs.docker.com
Dockerfile 中可以包含一个或多个 ARG
指令。例如,下面是一个有效的 Dockerfile 示例:
1 | FROM busybox |
在此示例中,ARG user1
和 ARG buildno
定义了两个可以在构建时设置的变量。如果你不为这些 ARG
提供默认值或者在 docker build
命令中没有指定它们的值,则这些变量将是空的。你可以在构建镜像时使用命令 docker build --build-arg user1=someuser --build-arg buildno=123
来为这些参数赋值。如果为 ARG
指令提供了默认值,那么当在构建命令中没有指定对应的 --build-arg
参数时,就会使用这个默认值。例如:
1 | ARG user1=defaultUser |
在这个例子中,如果没有在构建命令中提供 user1
或 buildno
的值,那么它们将分别采用 defaultUser
和 1
作为其值。
10.1 默认值
ARG
指令可以选择性地包含一个默认值:
1 | FROM busybox |
如果 ARG
指令具有默认值,并且在构建时没有传递任何值,那么构建器将使用该默认值。
10.2 作用域
ARG
变量从其在 Dockerfile 中声明的那一行开始生效。例如,考虑以下 Dockerfile:
1 | FROM busybox |
当通过以下命令构建此文件时:
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 中声明变量的共享基础阶段。
更多关于变量作用域的信息,请参阅变量作用域的相关文档 Variables | Docker Docs
10.3 使用 ARG 变量
你可以使用 ARG
或 ENV
指令来指定对 RUN
指令可用的变量。使用 ENV
指令定义的环境变量总是会覆盖同名的 ARG
指令。考虑下面这个包含 ENV
和 ARG
指令的 Dockerfile 示例:
1 | FROM ubuntu |
然后,假设用以下命令构建镜像:
1 | docker build --build-arg CONT_IMG_VER=v2.0.1 . |
在这种情况下,RUN
指令将使用 v1.0.0
而不是用户通过 ARG
设置传递的 v2.0.1
。这种行为类似于 shell 脚本,其中局部作用域的变量会覆盖从参数传入或从环境中继承的同名变量,从其定义点开始生效。
如果使用上面的例子但是不同的 ENV
规定,你可以在 ARG
和 ENV
指令之间创建更有用的交互:
1 | FROM ubuntu |
与 ARG
指令不同,ENV
的值在构建的镜像中总是被持久化。考虑一个没有使用 --build-arg
标志的 docker build
命令:
1 | docker build . |
使用这个 Dockerfile 示例,即使没有提供 --build-arg
,CONT_IMG_VER
仍然会在镜像中被持久化,但它的值将是 v1.0.0
,这是由第 3 行的 ENV
指令设置的默认值。
此示例中的变量扩展技术允许你通过命令行传递参数,并通过利用 ENV
指令将其持久化到最终镜像中。需要注意的是,变量扩展仅支持 Dockerfile 指令的有限集合。这意味着并非所有指令都支持变量替换,具体取决于 Dockerfile 中的上下文和使用的指令类型。
10.4 预定义的 ARGs
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 | FROM ubuntu |
在这种情况下,HTTP_PROXY
变量的值不会出现在 docker history
中,并且不会被缓存。如果你改变了位置,代理服务器变更为 http://user:pass@proxy.sfo.example.com
,后续的构建不会导致缓存失效。
如果你需要覆盖这种行为,则可以在 Dockerfile 中添加一个 ARG
语句,如下所示:
1 | FROM ubuntu |
当构建这个 Dockerfile 时,HTTP_PROXY
将保留在 docker history
中,改变它的值会使构建缓存失效。这意味着每次修改预定义的 ARG
变量值时,都会影响到构建过程中的缓存使用情况。
10.5 全局作用域中的自动平台 ARGs
此功能仅在使用 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
命令中可用。要在构建阶段中暴露其中一个参数,请重新定义它但不赋值。
例如:
1 | FROM alpine |
10.6 BuildKit 内置构建参数
以下是 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 时间戳。更多关于可重现构建的信息请查看 SOURCE_DATE_EPOCH — reproducible-builds.org。自 Dockerfile 1.5 和 BuildKit 0.11 起支持。 |
10.6.1 示例:保持 .git 目录
当使用 Git 上下文时,默认情况下不会保留 .git
目录。如果你希望在构建过程中检索 git 信息,保留这个目录会很有用:
1 | # syntax=docker/dockerfile:1 |
然后通过以下命令构建:
1 | docker build --build-arg BUILDKIT_CONTEXT_KEEP_GIT_DIR=1 https://github.com/user/repo.git#main |
10.7 对构建缓存的影响
ARG
变量不像 ENV
变量那样被持久化到构建的镜像中。然而,ARG
变量确实以类似的方式影响构建缓存。如果 Dockerfile 中定义的 ARG
变量的值与之前的构建不同,则在其首次使用时会发生“缓存未命中”,而不是在其定义时。特别地,所有跟随 ARG
指令之后的 RUN
指令隐式地使用 ARG
变量(作为环境变量),因此可能会导致缓存未命中。所有预定义的 ARG
变量免于缓存,除非 Dockerfile 中有对应的 ARG
语句。
例如,考虑这两个 Dockerfile:
1 | FROM ubuntu |
1 | FROM ubuntu |
如果在命令行上指定 --build-arg CONT_IMG_VER=<value>
,在这两种情况下,第 2 行的定义不会导致缓存未命中;而第 3 行会导致缓存未命中。因为 ARG CONT_IMG_VER
导致 RUN
行被视为等同于运行 CONT_IMG_VER=<value> echo hello
,所以如果 <value>
改变,就会发生缓存未命中。
再考虑另一个相同命令行下的示例:
1 | FROM ubuntu |
在这个例子中,缓存未命中发生在第 3 行。因为 ENV
指令引用的变量值依赖于 ARG
变量,并且该变量通过命令行改变。此例中,ENV
命令导致镜像包含该值。
如果 ENV
指令覆盖了同名的 ARG
指令,如下所示的 Dockerfile:
1 | FROM ubuntu |
第 3 行不会导致缓存未命中,因为 CONT_IMG_VER
的值是一个常量(hello
)。结果是,在构建之间使用的环境变量和 RUN
(第 4 行)的值不变。
11、ONBUILD
12、RUN
13、LABEL
1 | LABEL <key>=<value> [<key>=<value>...] |
LABEL
指令为镜像添加元数据。一个 LABEL
是键值对。要在 LABEL
值中包含空格,可以使用引号和反斜杠,就像在命令行解析中一样。这里有一些使用示例:
1 | LABEL "com.example.vendor"="ACME Incorporated" |
一个镜像可以有多个 LABEL
。你可以在一条指令中指定多个标签。在 Docker 1.10 之前,这样做可以减少最终镜像的大小,但现在情况不再是这样了。不过,你仍然可以选择在一个指令中以以下两种方式之一指定多个标签:
1 | LABEL multi.label1="value1" multi.label2="value2" other="value3" |
1 | LABEL multi.label1="value1" \ |
注意:确保使用双引号而不是单引号。特别是在使用字符串插值时(例如 LABEL example="foo-$ENV_VAR"
),单引号会直接取用字符串而不解包变量的值。
基础镜像(FROM
行中的镜像)中包含的 LABEL
将被你的镜像继承。如果一个 LABEL
已经存在但具有不同的值,则最近应用的值将覆盖任何先前设置的值。
要查看镜像的 LABEL
,可以使用 docker image inspect
命令。你可以使用 --format
选项仅显示标签:
1 | docker image inspect --format='{{json .Config.Labels}}' myimage |
输出结果可能如下所示:
1 | { |
14、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 | EXPOSE 80/tcp |
在这种情况下,如果你在 docker run
中使用 -P
,端口将分别为 TCP 和 UDP 各暴露一次。记住,-P
使用主机上的临时高位端口,因此 TCP 和 UDP 不会使用相同的端口。
无论 EXPOSE
设置如何,你都可以通过使用 -p
标志在运行时覆盖它们。例如:
1 | docker run -p 80:80/tcp -p 80:80/udp ... |
要在主机系统上设置端口重定向,请参阅 使用 -P 标志 | docs.docker.com。docker network
命令支持创建供容器间通信的网络,而无需暴露或发布特定端口,因为连接到该网络的容器可以通过任何端口相互通信。有关详细信息,请参阅 Networking | Docker Docs
15、ENV
1 | ENV <key>=<value> [<key>=<value>...] |
ENV
指令用于设置环境变量 <key>
的值为 <value>
。这个值将在构建阶段的所有后续指令中出现在环境中,并且可以在许多地方内联替换。值会被解释以包含其他环境变量,因此如果未转义,引号字符将被移除。类似于命令行解析,可以使用引号和反斜杠来在值中包含空格。
例如:
1 | ENV MY_NAME="John Doe" |
ENV
指令允许一次性设置多个 <key>=<value>
变量,下面的例子将在最终镜像中产生相同的结果:
1 | ENV MY_NAME="John Doe" MY_DOG=Rex\ The\ Dog \ |
使用 ENV
设置的环境变量在从生成的镜像运行容器时会持续存在。你可以使用 docker inspect
查看这些值,并使用 docker run --env <key>=<value>
更改它们。
一个阶段会继承其父阶段或任何祖先阶段使用 ENV
设置的任何环境变量。更多详细信息,请参阅手册中的多阶段构建 Multi-stage | Docker Docs
环境变量的持久性可能会导致意外的副作用。例如,设置 ENV DEBIAN_FRONTEND=noninteractive
会改变 apt-get
的行为,并可能混淆你镜像的用户。
如果某个环境变量仅在构建过程中需要,而不在最终镜像中使用,考虑仅为单个命令设置值:
1 | RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y ... |
或者使用不会在最终镜像中持久化的 ARG
:
1 | ARG DEBIAN_FRONTEND=noninteractive |
15.1 替代语法
ENV
指令还允许一种替代语法 ENV <key> <value>
,省略了 =
。例如:
1 | ENV MY_VAR my-value |
这种语法不允许在一个 ENV
指令中设置多个环境变量,并且可能会造成混淆。例如,下面的代码设置了名为 ONE
的单一环境变量,其值为 "TWO= THREE=world"
:
1 | ENV ONE TWO= THREE=world |
为了向后兼容,支持这种替代语法,但由于上述原因不推荐使用,未来版本中可能会移除该语法。
相关链接
Dockerfile reference | Docker Docs
Custom Dockerfile syntax | Docker Docs
Here-documents Dockerfile reference | Docker Docs
RUN –mount=type=secret | docs.docker.com
SOURCE_DATE_EPOCH — reproducible-builds.org
add-or-copy | Best practices | Docker Docs
OB links
OB tags
#Docker