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` 指令开头**。这可能在 解析器指令、注释 和全局作用域 ARG 之后。`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 的顶部。
解析器指令键(例如 `syntax` 或 `check`)不区分大小写,但按照惯例它们是小写的。指令的值区分大小写,并且必须以指令的适当大小写编写。例如,`#check=skip=jsonargsrecommended` 是无效的,因为检查名称必须使用 Pascal 大小写,而不是小写。按照惯例,在任何解析器指令之后都包含一个空行。解析器指令不支持行延续字符。
由于这些规则,以下示例均无效
由于行延续而无效
# direc \
tive=value
由于出现两次而无效
# directive=value1
# directive=value2
FROM ImageName
由于它出现在构建器指令之后而被视为注释
FROM ImageName
# directive=value
由于它出现在不是解析器指令的注释之后而被视为注释
# About my dockerfile
# directive=value
FROM ImageName
以下 `unknowndirective` 被视为注释,因为它不被识别。已知的 `syntax` 指令被视为注释,因为它出现在不是解析器指令的注释之后。
# unknowndirective=value
# syntax=value
解析器指令中允许使用非换行空格。因此,以下行都被视为相同
#directive=value
# directive =value
# directive= value
# directive = value
# dIrEcTiVe=value
语法
使用 `syntax` 解析器指令声明要用于构建的 Dockerfile 语法版本。如果未指定,BuildKit 将使用捆绑版本的 Dockerfile 前端。声明语法版本允许您自动使用最新的 Dockerfile 版本,而无需升级 BuildKit 或 Docker Engine,甚至无需使用自定义 Dockerfile 实现。
大多数用户希望将此解析器指令设置为 `docker/dockerfile:1`,这将导致 BuildKit 在构建之前提取 Dockerfile 语法的最新稳定版本。
# syntax=docker/dockerfile:1
有关解析器指令工作方式的更多信息,请参阅 自定义 Dockerfile 语法。
转义
# escape=\
或者
# escape=`
`escape` 指令设置用于转义 Dockerfile 中字符的字符。如果未指定,则默认转义字符为 `\`。
转义字符用于转义一行中的字符以及转义换行符。这允许 Dockerfile 指令跨越多行。请注意,无论 Dockerfile 中是否包含 `escape` 解析器指令,`RUN` 命令中都不会执行转义,除非在行尾。
将转义字符设置为`
在Windows
系统上特别有用,因为\
是目录路径分隔符。`
与Windows PowerShell保持一致。
考虑以下示例,它在Windows上会以一种不明显的方式失败。第二行末尾的第二个\
会被解释为换行的转义符,而不是第一个\
转义符的目标。类似地,第三行末尾的\
(假设它实际上被处理为指令)会导致它被视为行延续。此Dockerfile的结果是第二行和第三行被视为单个指令。
FROM microsoft/nanoserver
COPY testfile.txt c:\\
RUN dir c:\
结果为
PS E:\myproject> docker build -t cmd .
Sending build context to Docker daemon 3.072 kB
Step 1/2 : FROM microsoft/nanoserver
---> 22738ff49c6d
Step 2/2 : COPY testfile.txt c:\RUN dir c:
GetFileAttributesEx c:RUN: The system cannot find the file specified.
PS E:\myproject>
上述问题的一种解决方案是使用/
作为COPY
指令和dir
的目标。但是,这种语法充其量令人困惑,因为它不是Windows路径的自然表示方式;最坏的情况下,它容易出错,因为并非所有Windows命令都支持/
作为路径分隔符。
通过添加escape
解析器指令,以下Dockerfile能够按预期成功,并使用Windows文件路径的自然平台语义。
# escape=`
FROM microsoft/nanoserver
COPY testfile.txt c:\
RUN dir c:\
结果为
PS E:\myproject> docker build -t succeeds --no-cache=true .
Sending build context to Docker daemon 3.072 kB
Step 1/3 : FROM microsoft/nanoserver
---> 22738ff49c6d
Step 2/3 : COPY testfile.txt c:\
---> 96655de338de
Removing intermediate container 4db9acbb1682
Step 3/3 : RUN dir c:\
---> Running in a2c157f842f5
Volume in drive C has no label.
Volume Serial Number is 7E6D-E0F7
Directory of c:\
10/05/2016 05:04 PM 1,894 License.txt
10/05/2016 02:22 PM <DIR> Program Files
10/05/2016 02:14 PM <DIR> Program Files (x86)
10/28/2016 11:18 AM 62 testfile.txt
10/28/2016 11:20 AM <DIR> Users
10/28/2016 11:20 AM <DIR> Windows
2 File(s) 1,956 bytes
4 Dir(s) 21,259,096,064 bytes free
---> 01c7f3bef04f
Removing intermediate container a2c157f842f5
Successfully built 01c7f3bef04f
PS E:\myproject>
检查
# check=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语法固定到特定版本。否则,当将来版本中添加新的检查时,您的构建可能会开始失败。
要同时组合skip
和error
选项,请使用分号分隔它们。
# 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
的最短匹配项,从字符串开头查找。str=foobarbaz echo ${str#f*b} # arbaz
${variable##pattern}
从variable
中删除pattern
的最长匹配项,从字符串开头查找。str=foobarbaz echo ${str##f*b} # az
${variable%pattern}
从variable
中删除pattern
的最短匹配项,从字符串结尾反向查找。string=foobarbaz echo ${string%b*} # foobar
${variable%%pattern}
从variable
中删除pattern
的最长匹配项,从字符串结尾反向查找。string=foobarbaz echo ${string%%b*} # foo
${variable/pattern/replacement}
将variable
中pattern
的第一次出现替换为replacement
。string=foobarbaz echo ${string/ba/fo} # fooforbaz
${variable//pattern/replacement}
将variable
中pattern
的所有出现替换为replacement
。string=foobarbaz echo ${string//ba/fo} # fooforfoz
在所有情况下,word
可以是任何字符串,包括其他环境变量。
pattern
是一个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
(与上述支持的指令之一组合使用)
您也可以将环境变量与RUN
、CMD
和ENTRYPOINT
指令一起使用,但在这些情况下,变量替换由命令shell处理,而不是构建器。请注意,使用exec形式的指令不会自动调用命令shell。请参阅变量替换。
环境变量替换在整个指令中对每个变量使用相同的值。更改变量的值仅在后续指令中生效。考虑以下示例:
ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc
def
的值变为hello
。ghi
的值变为bye
。
.dockerignore 文件
您可以使用.dockerignore
文件从构建上下文排除文件和目录。有关更多信息,请参阅.dockerignore文件。
Shell 和 exec 形式
RUN
、CMD
和ENTRYPOINT
指令都有两种可能的形式:
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
您也可以将heredoc与shell形式一起使用来分解支持的命令。
RUN <<EOF
source $HOME/.bashrc && \
echo $HOME
EOF
有关heredoc的更多信息,请参阅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
指令之前的 commit 输出的最后一个镜像 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
。默认情况下,使用构建请求的目标平台。全局构建参数可用于此标志的值,例如 自动平台 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 形式最常用,它允许您使用换行符转义或heredoc将较长的指令分解成多行。
RUN <<EOF
apt-get update
apt-get install -y curl
EOF
RUN
指令可用的 [OPTIONS]
为:
选项 | 最小 Dockerfile 版本 |
---|---|
--mount | 1.2 |
--network | 1.3 |
--security | 1.1.2-labs |
RUN 指令的缓存失效
RUN
指令的缓存不会在下次构建期间自动失效。诸如 RUN apt-get dist-upgrade -y
之类的指令的缓存将在下次构建期间重用。可以使用 --no-cache
标志使 RUN
指令的缓存失效,例如 docker build --no-cache
。
有关更多信息,请参阅 Dockerfile 最佳实践指南。
RUN
指令的缓存可以被 ADD
和 COPY
指令使失效。
RUN --mount
RUN --mount=[type=<TYPE>][,option=<value>[,option=<value>]...]
RUN --mount
允许您创建构建可以访问的文件系统挂载点。这可以用于:
- 创建到主机文件系统或其他构建阶段的绑定挂载
- 访问构建密钥或 ssh 代理套接字
- 使用持久化包管理缓存来加快构建速度
支持的挂载类型为:
类型 | 描述 |
---|---|
bind (默认) | 绑定挂载上下文目录(只读)。 |
cache | 挂载一个临时目录到编译器和包管理器的缓存目录。 |
tmpfs | 在构建容器中挂载一个 tmpfs 。 |
secret | 允许构建容器访问安全文件,例如私钥,而无需将其烘焙到镜像或构建缓存中。 |
ssh | 允许构建容器通过 SSH 代理访问 SSH 密钥,并支持密码短语。 |
RUN --mount=type=bind
此挂载类型允许将文件或目录绑定到构建容器。绑定挂载默认为只读。
选项 | 描述 |
---|---|
target 、dst 、destination 1 | 挂载路径。 |
source | from 中的源路径。默认为 from 的根目录。 |
from | 用作源根目录的构建阶段、上下文或镜像名称。默认为构建上下文。 |
rw 、readwrite | 允许对挂载进行写入。写入的数据将被丢弃。 |
RUN --mount=type=cache
此挂载类型允许构建容器缓存编译器和包管理器的目录。
选项 | 描述 |
---|---|
id | 用于标识单独/不同缓存的可选 ID。默认为 target 的值。 |
target 、dst 、destination 1 | 挂载路径。 |
ro 、readonly | 如果设置,则为只读。 |
sharing | shared 、private 或 locked 之一。默认为 shared 。shared 缓存挂载可以被多个写入器同时使用。private 如果有多个写入器,则会创建一个新的挂载。locked 将暂停第二个写入器,直到第一个写入器释放挂载。 |
from | 用作缓存挂载基础的构建阶段、上下文或镜像名称。默认为空目录。 |
source | 要挂载的 from 中的子路径。默认为 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 、destination 1 | 挂载路径。 |
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
# syntax=docker/dockerfile:1
FROM python:3
RUN pip install awscli
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials \
aws s3 cp s3://... ...
$ docker buildx build --secret id=aws,src=$HOME/.aws/credentials .
示例:作为环境变量挂载
以下示例获取密钥 API_KEY
并将其作为具有相同名称的环境变量挂载。
# syntax=docker/dockerfile:1
FROM alpine
RUN --mount=type=secret,id=API_KEY,env=API_KEY \
some-command --token-from-env $API_KEY
假设在构建环境中设置了 API_KEY
环境变量,您可以使用以下命令进行构建
$ docker buildx build --secret id=API_KEY .
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
# 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 [email protected] 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
允许控制在哪个网络环境中运行命令。
支持的网络类型为:
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
只能安装 tarfile 中提供的包,这可以通过较早的构建阶段来控制。
RUN --network=host
命令在主机的网络环境中运行(类似于 docker build --network=host
,但在每个指令的基础上)。
警告
使用
--network=host
需要network.host
权限,启动buildkitd守护进程时需要使用--allow-insecure-entitlement network.host
标志启用该权限,或者在buildkitd 配置文件中进行设置,并且对于带有--allow network.host
标志的构建请求。
RUN --security
注意
稳定语法中尚不可用,请使用
docker/dockerfile:1-labs
版本。
RUN --security=<sandbox|insecure>
默认安全模式为sandbox
。使用--security=insecure
,构建器将在不安全模式下运行命令,无需沙箱,这允许运行需要提升权限的流程(例如 containerd)。这等效于运行docker run --privileged
。
警告
要访问此功能,启动buildkitd守护进程时应启用
security.insecure
权限,方法是使用--allow-insecure-entitlement security.insecure
标志,或者在buildkitd 配置文件中进行设置,并且对于带有--allow security.insecure
标志的构建请求。
可以通过--security=sandbox
激活默认沙箱模式,但这没有作用。
示例:检查权限
# syntax=docker/dockerfile:1-labs
FROM ubuntu
RUN --security=insecure cat /proc/self/status | grep CapEff
#84 0.093 CapEff: 0000003fffffffff
CMD
CMD
指令设置从镜像运行容器时要执行的命令。
可以使用shell形式或exec形式指定CMD
指令。
CMD ["executable","param1","param2"]
(exec形式)CMD ["param1","param2"]
(exec形式,作为ENTRYPOINT
的默认参数)CMD command param1 param2
(shell形式)
Dockerfile中只能有一个CMD
指令。如果列出多个CMD
,则只有最后一个生效。
CMD
的目的是为正在执行的容器提供默认值。这些默认值可以包含可执行文件,也可以省略可执行文件,在这种情况下,还必须指定ENTRYPOINT
指令。
如果希望容器每次运行相同的可执行文件,则应考虑结合使用ENTRYPOINT
和CMD
。请参阅ENTRYPOINT
。如果用户为docker run
指定参数,则这些参数将覆盖CMD
中指定的默认值,但仍然使用默认的ENTRYPOINT
。
如果使用CMD
为ENTRYPOINT
指令提供默认参数,则应以exec形式指定CMD
和ENTRYPOINT
指令。
注意
不要混淆
RUN
和CMD
。RUN
实际上运行一个命令并提交结果;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
指令设置生成的镜像的作者字段。LABEL
指令是这个指令更灵活的版本,您应该改用它,因为它可以设置所需的任何元数据,并且可以轻松查看,例如使用docker inspect
。要设置与MAINTAINER
字段对应的标签,可以使用
LABEL org.opencontainers.image.authors="[email protected]"
然后可以使用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
在这种情况下,如果使用-P
和docker run
,则该端口将为TCP和UDP分别公开一次。请记住,-P
在主机上使用短暂的高位主机端口,因此TCP和UDP不使用相同的端口。
无论EXPOSE
设置如何,都可以使用-p
标志在运行时覆盖它们。例如
$ docker run -p 80:80/tcp -p 80:80/udp ...
要设置主机系统的端口重定向,请参阅使用-P标志。docker network
命令支持创建网络以便在容器之间进行通信,而无需公开或发布特定端口,因为连接到网络的容器可以通过任何端口相互通信。有关详细信息,请参阅此功能概述。
ENV
ENV <key>=<value> [<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-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
指定多个源文件或目录。最后一个参数必须始终是目标。例如,要将构建上下文中的两个文件file1.txt
和file2.txt
添加到构建容器中/usr/src/things/
ADD file1.txt file2.txt /usr/src/things/
如果您直接或使用通配符指定多个源文件,则目标必须是目录(必须以斜杠/
结尾)。
要添加来自远程位置的文件,您可以指定 URL 或 Git 仓库的地址作为源。例如
ADD https://example.com/archive.zip /usr/src/things/
ADD [email protected]:user/repo.git /usr/src/things/
BuildKit 检测<src>
的类型并相应地处理它。
- 如果
<src>
是本地文件或目录,则目录的内容将复制到指定的目标。参见从构建上下文添加文件。 - 如果
<src>
是本地 tar 归档文件,则将其解压缩并解压到指定的目标。参见添加本地 tar 归档文件。 - 如果
<src>
是 URL,则下载 URL 的内容并将其放置在指定的目标位置。参见从 URL 添加文件。 - 如果
<src>
是 Git 仓库,则将仓库克隆到指定的目标。参见从 Git 仓库添加文件。
从构建上下文添加文件
任何不以http://
、https://
或git@
协议前缀开头的相对或本地路径都被视为本地文件路径。本地文件路径相对于构建上下文。例如,如果构建上下文是当前目录,则ADD file.txt /
会将./file.txt
中的文件添加到构建容器文件系统的根目录。
从构建上下文添加源文件时,它们的路径被解释为相对于上下文的根目录。如果您指定了超出构建上下文的相对路径,例如ADD ../something /something
,则父目录路径将自动被剥离。此示例中的有效源路径变为ADD something /something
。
如果源是目录,则复制目录的内容,包括文件系统元数据。目录本身不会被复制,只有其内容会被复制。如果它包含子目录,这些子目录也会被复制,并与目标中任何现有目录合并。任何冲突都将以文件为单位,支持添加的内容,除非您尝试将目录复制到现有文件上,否则会引发错误。
如果源是文件,则将文件及其元数据复制到目标。文件权限将被保留。如果源是文件,并且目标位置存在同名目录,则会引发错误。
如果您通过标准输入将 Dockerfile 传递给构建(docker build - < Dockerfile
),则没有构建上下文。在这种情况下,您只能使用ADD
指令复制远程文件。您也可以通过标准输入传递 tar 归档文件:(docker build - < archive.tar
),归档文件根目录中的 Dockerfile 和归档文件的其余部分将用作构建的上下文。
模式匹配
对于本地文件,每个<src>
可能包含通配符,并且将使用 Go 的filepath.Match规则进行匹配。
例如,要添加构建上下文根目录中以.png
结尾的所有文件和目录
ADD *.png /dest/
在下面的示例中,?
是一个单字符通配符,例如匹配index.js
和index.ts
。
ADD index.?s /dest/
添加包含特殊字符(例如[
和]
)的文件或目录时,需要按照 Golang 规则转义这些路径,以防止将其视为匹配模式。例如,要添加名为arr[0].txt
的文件,请使用以下方法:
ADD arr[[]0].txt /dest/
添加本地 tar 归档文件
当使用本地 tar 归档文件作为ADD
的源时,并且归档文件采用已识别的压缩格式(gzip
、bzip2
或xz
,或未压缩),则该归档文件将被解压缩并解压到指定的目标。仅解压本地 tar 归档文件。如果 tar 归档文件是远程 URL,则不会解压归档文件,而是将其下载并放置在目标位置。
解压目录时,其行为与tar -x
相同。结果是以下内容的并集:
- 目标路径上已存在的内容,以及
- 源树的内容,冲突以文件为单位,支持添加的内容。
注意
是否将文件识别为已识别的压缩格式,仅基于文件的内容,而不是文件名。例如,如果一个空文件恰好以
.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 wget
、RUN curl
或使用容器中的其他工具,因为ADD
指令不支持身份验证。
从 Git 仓库添加文件
要使用 Git 仓库作为ADD
的源,您可以将仓库的 HTTP 或 SSH 地址作为源引用。该仓库将被克隆到镜像中的指定目标。
ADD https://github.com/user/repo.git /mydir/
您可以使用 URL 片段来指定特定的分支、标签、提交或子目录。例如,要添加buildkit
仓库的v0.14.1
标签的docs
目录
ADD [email protected]: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 [email protected]:foo/bar.git /bar
要构建此 Dockerfile,请将--ssh
标志传递给docker build
以将 SSH 代理套接字安装到构建中。例如
$ docker build --ssh default .
有关使用密钥构建的更多信息,请参阅构建密钥。
目标
如果目标路径以正斜杠开头,则将其解释为绝对路径,源文件将复制到当前构建阶段根目录下指定的目的地。
# create /abs/test.txt
ADD test.txt /abs/
尾部斜杠很重要。例如,ADD test.txt /abs
在 /abs
创建一个文件,而 ADD test.txt /abs/
创建 /abs/test.txt
。
如果目标路径不以正斜杠开头,则将其解释为相对于构建容器的工作目录。
WORKDIR /usr/src/app
# create /usr/src/app/rel/test.txt
ADD test.txt rel/
如果目标路径不存在,则会创建它,以及其路径中所有缺失的目录。
如果源文件是一个文件,并且目标路径不以尾部斜杠结尾,则源文件将作为文件写入目标路径。
ADD --keep-git-dir
ADD [--keep-git-dir=<boolean>] <src> ... <dir>
当 <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
标志允许您验证远程资源的校验和。校验和的格式为 <algorithm>:<hash>
。支持的算法为 sha256
、sha384
和 sha512
。
ADD --checksum=sha256:24454f830cdb571e2c4ad15481119c43b3cafd48dd869a9b2945d1036d1dc68d https://mirrors.edge.kernel.org/pub/linux/kernel/Historic/linux-0.01.tar.gz /
--checksum
标志仅支持 HTTP(S) 源。
ADD --chown --chmod
ADD --link
参见 COPY --link
。
ADD --exclude
参见 COPY --exclude
。
COPY
COPY 有两种形式。对于包含空格的路径,需要使用后一种形式。
COPY [OPTIONS] <src> ... <dest>
COPY [OPTIONS] ["<src>", ... "<dest>"]
可用的[OPTIONS]
是
COPY
指令将来自 <src>
的新文件或目录复制并添加到镜像文件系统的 <dest>
路径下。文件和目录可以从构建上下文、构建阶段、命名上下文或镜像中复制。
ADD
和COPY
指令的功能相似,但用途略有不同。了解更多关于ADD
和COPY
的区别。
源
您可以使用 COPY
指定多个源文件或目录。最后一个参数必须始终是目标路径。例如,要将构建上下文中的两个文件 file1.txt
和 file2.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
。
如果源是目录,则复制目录的内容,包括文件系统元数据。目录本身不会被复制,只有其内容会被复制。如果它包含子目录,这些子目录也会被复制,并与目标中任何现有目录合并。任何冲突都将以文件为单位,支持添加的内容,除非您尝试将目录复制到现有文件上,否则会引发错误。
如果源是文件,则将文件及其元数据复制到目标。文件权限将被保留。如果源是文件,并且目标位置存在同名目录,则会引发错误。
如果您通过标准输入将 Dockerfile 传递给构建 (docker build - < Dockerfile
),则没有构建上下文。在这种情况下,您只能使用 COPY
指令从其他阶段、命名上下文或镜像中复制文件,方法是使用 --from
标志。您也可以通过标准输入传递 tar 归档文件:(docker build - < archive.tar
),归档文件根目录下的 Dockerfile 和归档文件的其余部分将用作构建的上下文。
使用 Git 仓库作为构建上下文时,复制文件的权限位为 644。如果仓库中的文件设置了可执行位,则其权限将设置为 755。目录的权限设置为 755。
模式匹配
对于本地文件,每个<src>
可能包含通配符,并且将使用 Go 的filepath.Match规则进行匹配。
例如,要添加构建上下文根目录中以.png
结尾的所有文件和目录
COPY *.png /dest/
在下面的示例中,?
是一个单字符通配符,例如匹配index.js
和index.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 操作系统的容器。
除非可选的 --chown
标志指定给定的用户名、组名或 UID/GID 组合来请求复制内容的特定所有权,否则从构建上下文复制的所有文件和目录都将使用 0
的 UID 和 GID 创建。--chown
标志的格式允许使用用户名和组名字符串或直接使用整数 UID 和 GID 的任意组合。提供用户名而没有组名或提供 UID 而没有 GID 将使用与 GID 相同的数字 UID。如果提供了用户名或组名,则将使用容器的根文件系统 /etc/passwd
和 /etc/group
文件来执行从名称到整数 UID 或 GID 的转换。以下示例显示了 --chown
标志的有效定义
COPY --chown=55:mygroup files* /somedir/
COPY --chown=bin files* /somedir/
COPY --chown=1 files* /somedir/
COPY --chown=10:11 files* /somedir/
COPY --chown=myuser:mygroup --chmod=644 files* /somedir/
如果容器根文件系统不包含 /etc/passwd
或 /etc/group
文件,并且在 --chown
标志中使用了用户或组名,则 COPY
操作将失败。使用数字 ID 无需查找,也不依赖于容器根文件系统内容。
使用 Dockerfile 语法版本 1.10.0 及更高版本,--chmod
标志支持变量插值,允许您使用构建参数定义权限位
# syntax=docker/dockerfile:1.10
FROM alpine
WORKDIR /src
ARG MODE=440
COPY --chmod=$MODE . .
COPY --link
COPY [--link[=<boolean>]] <src> ... <dest>
在 COPY
或 ADD
命令中启用此标志,您可以复制具有增强语义的文件,这些文件保持独立于其自身层,并且在更改先前层的命令时不会失效。
当使用 --link
时,您的源文件将复制到一个空的目标目录。该目录将变成一个层,该层链接到您先前状态的顶部。
# syntax=docker/dockerfile:1
FROM alpine
COPY --link /foo /bar
相当于进行两次构建
FROM alpine
和
FROM scratch
COPY /foo /bar
并将两个镜像的所有层合并在一起。
使用 --link
的好处
使用 --link
即使之前的层已更改,也可以在后续构建中使用 --cache-from
重用已构建的层。这对于多阶段构建尤其重要,在多阶段构建中,如果同一阶段中的任何先前命令发生更改,则 COPY --from
语句以前会失效,导致需要再次重建中间阶段。使用 --link
,先前构建生成的层将被重用并合并到新层的顶部。这也意味着当基础镜像收到更新时,您可以轻松地重新定位您的镜像,而无需再次执行整个构建。在支持它的后端中,BuildKit 可以执行此重新定位操作,而无需在客户端和注册表之间推送或拉取任何层。BuildKit 将检测这种情况,并且只创建包含新层和旧层(按正确的顺序)的新镜像清单。
当使用 --link
并且没有其他需要访问基础镜像中文件的命令时,BuildKit 可以避免下载基础镜像的相同行为也会发生。在这种情况下,BuildKit 只会构建 COPY
命令的层,并将它们直接推送到基础镜像层的顶部注册表。
与 --link=false
的不兼容性
使用 --link
时,COPY/ADD
命令不允许读取先前状态中的任何文件。这意味着如果在先前状态下目标目录是一个包含符号链接的路径,则 COPY/ADD
无法跟踪它。在最终镜像中,使用 --link
创建的目标路径将始终是一个仅包含目录的路径。
如果您不依赖于在目标路径中跟踪符号链接的行为,则始终建议使用 --link
。--link
的性能等于或优于默认行为,并且它为缓存重用创建了更好的条件。
COPY --parents
注意
稳定语法中尚不可用,请使用
docker/dockerfile:1.7-labs
版本。
COPY [--parents[=<boolean>]] <src> ... <dest>
--parents
标志保留 src
条目的父目录。此标志默认为 false
。
# syntax=docker/dockerfile:1-labs
FROM scratch
COPY ./x/a.txt ./y/a.txt /no_parents/
COPY --parents ./x/a.txt ./y/a.txt /parents/
# /no_parents/a.txt
# /parents/x/a.txt
# /parents/y/a.txt
此行为类似于Linux cp
实用程序 的--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>
中指定的模式匹配,也不复制与它的模式匹配的文件。
# 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"]
如果需要为单个可执行文件编写启动脚本,可以使用exec
和gosu
命令确保最终可执行文件接收 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 exec
或docker 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 环境变量,并将忽略任何CMD
或docker run
命令行参数。为了确保docker stop
能够正确地向任何长时间运行的ENTRYPOINT
可执行文件发出信号,您需要记住以exec
启动它
FROM ubuntu
ENTRYPOINT exec top -b
运行此镜像时,您将看到单个PID 1
进程
$ docker run -it --rm --name test top
Mem: 1704520K used, 352148K free, 0K shrd, 0K buff, 140368121167873K cached
CPU: 5% usr 0% sys 0% nic 94% idle 0% io 0% irq 0% sirq
Load average: 0.08 0.03 0.05 2/98 6
PID PPID USER STAT VSZ %VSZ %CPU COMMAND
1 0 root R 3164 0% 0% top -b
它在docker stop
时干净退出
$ /usr/bin/time docker stop test
test
real 0m 0.20s
user 0m 0.02s
sys 0m 0.04s
如果您忘记在ENTRYPOINT
的开头添加exec
FROM ubuntu
ENTRYPOINT top -b
CMD -- --ignored-param1
然后您可以运行它(为下一步命名)
$ docker run -it --name test top --ignored-param2
top - 13:58:24 up 17 min, 0 users, load average: 0.00, 0.00, 0.00
Tasks: 2 total, 1 running, 1 sleeping, 0 stopped, 0 zombie
%Cpu(s): 16.7 us, 33.3 sy, 0.0 ni, 50.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
MiB Mem : 1990.8 total, 1354.6 free, 231.4 used, 404.7 buff/cache
MiB Swap: 1024.0 total, 1024.0 free, 0.0 used. 1639.8 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 2612 604 536 S 0.0 0.0 0:00.02 sh
6 root 20 0 5956 3188 2768 R 0.0 0.2 0:00.00 top
您可以从top
的输出中看到,指定的ENTRYPOINT
不是PID 1
。
如果然后运行docker stop test
,容器将不会干净地退出——stop
命令将在超时后强制发送SIGKILL
。
$ docker exec -it test ps waux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.4 0.0 2612 604 pts/0 Ss+ 13:58 0:00 /bin/sh -c top -b --ignored-param2
root 6 0.0 0.1 5956 3188 pts/0 S+ 13:58 0:00 top -b
root 7 0.0 0.1 5884 2816 pts/1 Rs+ 13:58 0:00 ps waux
$ /usr/bin/time docker stop test
test
real 0m 10.19s
user 0m 0.04s
sys 0m 0.03s
理解 CMD 和 ENTRYPOINT 的交互
CMD
和ENTRYPOINT
指令都定义了运行容器时执行的命令。有一些规则描述了它们的协作。
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
才能使其具有值。
VOLUME
VOLUME ["/data"]
VOLUME
指令创建具有指定名称的挂载点,并将其标记为保存来自本地主机或其他容器的外部挂载卷。该值可以是 JSON 数组,VOLUME ["/var/log/"]
,或具有多个参数的普通字符串,例如VOLUME /var/log
或VOLUME /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
指令和运行时,运行相关的 ENTRYPOINT
和 CMD
命令。
请注意,当为用户指定一个组时,用户将*只有*指定的组成员资格。任何其他已配置的组成员资格都将被忽略。
警告
当用户没有主组时,镜像(或后续指令)将以
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 中其后任何 RUN
、CMD
、ENTRYPOINT
、COPY
和 ADD
指令的工作目录。如果 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>
标志在构建时将其传递给构建器。
警告
不建议使用构建参数来传递机密信息,例如用户凭据、API 令牌等。构建参数在
docker history
命令和max
模式来源证明中可见,如果您使用 Buildx GitHub Actions 并且您的 GitHub 存储库是公开的,则默认情况下这些证明会附加到镜像。请参阅
RUN --mount=type=secret
部分,了解在构建镜像时安全使用机密的方法。
Dockerfile 可以包含一个或多个 ARG
指令。例如,以下是有效的 Dockerfile
FROM busybox
ARG user1
ARG buildno
# ...
默认值
ARG
指令可以包含一个可选的默认值
FROM busybox
ARG user1=someuser
ARG buildno=1
# ...
如果 ARG
指令具有默认值,并且在构建时没有传递任何值,则构建器将使用默认值。
作用域
ARG
变量从其在 Dockerfile 中声明的行开始生效。例如,考虑此 Dockerfile
FROM busybox
USER ${username:-some_user}
ARG username
USER $username
# ...
用户通过调用以下命令构建此文件:
$ docker build --build-arg username=what_user .
- 第 2 行的
USER
指令将评估为some_user
备用值,因为username
变量尚未声明。 username
变量在第 3 行声明,并可从那时起在 Dockerfile 指令中引用。- 第 4 行的
USER
指令将评估为what_user
,因为此时username
参数的值为what_user
,该值是在命令行上传递的。在通过ARG
指令定义之前,任何变量的使用都会导致空字符串。
在构建阶段内声明的 ARG
变量会自动由基于该阶段的其他阶段继承。不相关的构建阶段无法访问该变量。要在多个不同的阶段使用参数,每个阶段都必须包含 ARG
指令,或者它们都必须基于同一 Dockerfile 中声明变量的共享基础阶段。
有关更多信息,请参阅 变量作用域。
使用 ARG 变量
您可以使用 ARG
或 ENV
指令来指定可用于 RUN
指令的变量。使用 ENV
指令定义的环境变量始终会覆盖同名的 ARG
指令。考虑此包含 ENV
和 ARG
指令的 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
规范,您可以创建 ARG
和 ENV
指令之间更有用的交互。
FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=${CONT_IMG_VER:-v1.0.0}
RUN echo $CONT_IMG_VER
与 ARG
指令不同,ENV
值始终保留在构建的镜像中。考虑一个不带 --build-arg
标志的 docker build
$ docker build .
使用此 Dockerfile 示例,CONT_IMG_VER
仍然保留在镜像中,但其值将为 v1.0.0
,因为它是第 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:[email protected]
构建以下 Dockerfile
FROM ubuntu
RUN echo "Hello World"
在这种情况下,HTTP_PROXY
变量的值在 docker history
中不可用,也不会被缓存。如果您要更改位置,并且您的代理服务器更改为 http://user:[email protected]
,则后续构建不会导致缓存未命中。
如果您需要覆盖此行为,则可以通过在 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_CACHE 2 | 布尔值 | 是否将内联缓存元数据添加到镜像配置。 |
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应用程序构建器,它将需要将应用程序源代码添加到特定目录中,并且可能需要在之后调用构建脚本。您现在不能只调用ADD
和RUN
,因为您还没有访问应用程序源代码,并且每个应用程序构建的源代码都不同。您可以简单地为应用程序开发人员提供一个样板Dockerfile,让他们复制粘贴到他们的应用程序中,但这效率低下,容易出错,并且难以更新,因为它与应用程序特定的代码混合在一起。
解决方案是使用ONBUILD
注册稍后在下一个构建阶段运行的高级指令。
以下是它的工作原理
- 当构建器遇到
ONBUILD
指令时,它会将一个触发器添加到正在构建的镜像的元数据中。该指令不会以其他方式影响当前构建。 - 构建结束时,所有触发器的列表都存储在镜像清单中,位于键
OnBuild
下。可以使用docker inspect
命令检查它们。 - 稍后,可以使用
FROM
指令将该镜像用作新构建的基础。在下游构建器处理FROM
指令的过程中,它会查找ONBUILD
触发器,并按照注册它们的顺序执行它们。如果任何触发器失败,则FROM
指令将中止,这反过来会导致构建失败。如果所有触发器都成功,则FROM
指令完成,构建将照常继续。 - 触发器在执行后会从最终镜像中清除。换句话说,它们不会被“子孙”构建继承。
例如,您可以添加如下内容
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
指令可能不会触发FROM
或MAINTAINER
指令。
STOPSIGNAL
STOPSIGNAL signal
STOPSIGNAL
指令设置将发送到容器以使其退出的系统调用信号。此信号可以是格式为SIG
的信号名称,例如SIGKILL
,或者与内核的syscall表中的位置匹配的无符号数字,例如9
。如果未定义,则默认为SIGTERM
。
可以使用docker run
和docker create
上的--stop-signal
标志,按容器覆盖镜像的默认stopsignal。
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秒,则该检查被认为失败。
容器被认为unhealthy
需要retries次连续的健康检查失败。
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 https://127.0.0.1/ || exit 1
为了帮助调试失败的探测,命令在stdout或stderr上写入的任何输出文本(UTF-8编码)都将存储在健康状态中,并且可以使用docker inspect
查询。此类输出应保持简短(目前仅存储前4096个字节)。
当容器的健康状态更改时,会生成一个带有新状态的health_status
事件。
SHELL
SHELL ["executable", "parameters"]
SHELL
指令允许覆盖用于命令的shell形式的默认shell。Linux上的默认shell是["/bin/sh", "-c"]
,Windows上的默认shell是["cmd", "/S", "/C"]
。SHELL
指令必须以JSON形式写在Dockerfile中。
SHELL
指令在Windows上特别有用,因为Windows上有两个常用且截然不同的本机shell:cmd
和powershell
,以及其他可用的shell,包括sh
。
SHELL
指令可以多次出现。每个SHELL
指令都会覆盖所有之前的SHELL
指令,并影响所有后续指令。例如
FROM microsoft/windowsservercore
# Executed as cmd /S /C echo default
RUN echo default
# Executed as cmd /S /C powershell -command Write-Host default
RUN powershell -command Write-Host default
# Executed as powershell -command Write-Host hello
SHELL ["powershell", "-command"]
RUN Write-Host hello
# Executed as cmd /S /C echo hello
SHELL ["cmd", "/S", "/C"]
RUN echo hello
当在Dockerfile中使用shell形式时,以下指令可能会受到SHELL
指令的影响:RUN
、CMD
和ENTRYPOINT
。
以下示例是Windows上常见的模式,可以通过使用SHELL
指令来简化
RUN powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"
构建器调用的命令将是
cmd /S /C powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"
这效率低下的原因有两个。首先,调用了不必要的cmd.exe
命令处理器(又名shell)。其次,shell形式中的每个RUN
指令都需要一个额外的powershell -command
作为命令的前缀。
为了提高效率,可以使用两种机制之一。一种是使用RUN
命令的JSON形式,例如
RUN ["powershell", "-command", "Execute-MyCmdlet", "-param1 \"c:\\foo.txt\""]
虽然JSON形式是明确的,并且不使用不必要的cmd.exe
,但它确实需要通过双引号和转义来提高冗余性。另一种机制是使用SHELL
指令和shell形式,为Windows用户创建更自然的语法,尤其是在与escape
解析器指令结合使用时
# escape=`
FROM microsoft/nanoserver
SHELL ["powershell","-command"]
RUN New-Item -ItemType Directory C:\Example
ADD Execute-MyCmdlet.ps1 c:\example\
RUN c:\example\Execute-MyCmdlet -sample 'hello world'
结果是
PS E:\myproject> docker build -t shell .
Sending build context to Docker daemon 4.096 kB
Step 1/5 : FROM microsoft/nanoserver
---> 22738ff49c6d
Step 2/5 : SHELL powershell -command
---> Running in 6fcdb6855ae2
---> 6331462d4300
Removing intermediate container 6fcdb6855ae2
Step 3/5 : RUN New-Item -ItemType Directory C:\Example
---> Running in d0eef8386e97
Directory: C:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 10/28/2016 11:26 AM Example
---> 3f2fbf1395d9
Removing intermediate container d0eef8386e97
Step 4/5 : ADD Execute-MyCmdlet.ps1 c:\example\
---> a955b2621c31
Removing intermediate container b825593d39fc
Step 5/5 : RUN c:\example\Execute-MyCmdlet 'hello world'
---> Running in be6d8e63fe75
hello world
---> 8e559e9bf424
Removing intermediate container be6d8e63fe75
Successfully built 8e559e9bf424
PS E:\myproject>
SHELL
指令也可用于修改shell的操作方式。例如,在Windows上使用SHELL cmd /S /C /V:ON|OFF
,可以修改延迟环境变量扩展语义。
如果需要替代shell,例如zsh
、csh
、tcsh
和其他shell,则也可以在Linux上使用SHELL
指令。
Here-Documents
Here-documents允许将后续Dockerfile行重定向到RUN
或COPY
命令的输入。如果此类命令包含here-document,Dockerfile 将仅包含 here-doc 分隔符的行之前的下一行视为同一命令的一部分。
示例:运行多行脚本
# syntax=docker/dockerfile:1
FROM debian
RUN <<EOT bash
set -ex
apt-get update
apt-get install -y vim
EOT
如果命令只包含here-document,则其内容将使用默认shell进行评估。
# syntax=docker/dockerfile:1
FROM debian
RUN <<EOT
mkdir -p foo/bar
EOT
或者,可以使用shebang头定义解释器。
# syntax=docker/dockerfile:1
FROM python:3.6
RUN <<EOT
#!/usr/bin/env python
print("hello world")
EOT
更复杂的示例可以使用多个here-documents。
# syntax=docker/dockerfile:1
FROM alpine
RUN <<FILE1 cat > file1 && <<FILE2 cat > file2
I am
first
FILE1
I am
second
FILE2
示例:创建内联文件
使用COPY
指令,您可以用here-doc指示符替换源参数,以将here-document的内容直接写入文件。以下示例使用COPY
指令创建一个包含hello world
的greeting.txt
文件。
# syntax=docker/dockerfile:1
FROM alpine
COPY <<EOF greeting.txt
hello world
EOF
此处使用了常规的heredoc 变量扩展和制表符去除规则。以下示例展示了一个小型Dockerfile,它使用带有heredoc的`COPY`指令创建一个`hello.sh`脚本文件。
# syntax=docker/dockerfile:1
FROM alpine
ARG FOO=bar
COPY <<-EOT /script.sh
echo "hello ${FOO}"
EOT
ENTRYPOINT ash /script.sh
在这种情况下,文件脚本打印“hello bar”,因为变量在`COPY`指令执行时被扩展。
$ docker build -t heredoc .
$ docker run heredoc
hello bar
如果您对heredoc单词`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示例,请参考: