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语法固定到特定版本。否则,当将来版本中添加新的检查时,您的构建可能会开始失败。

要同时组合skiperror选项,请使用分号分隔它们。

# check=skip=JSONArgsRecommended;error=true

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

环境变量替换

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

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

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

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

在Dockerfile语法的预发行版本中,当在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}variablepattern的第一次出现替换为replacement

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

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

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

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

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

示例(解析后的表示形式显示在#之后)

FROM busybox
ENV FOO=/bar
WORKDIR ${FOO}   # WORKDIR /bar
ADD . $FOO       # ADD . /bar
COPY \$FOO /quux # COPY $FOO /quux

Dockerfile中的以下指令支持环境变量。

  • ADD
  • COPY
  • ENV
  • EXPOSE
  • FROM
  • LABEL
  • STOPSIGNAL
  • USER
  • VOLUME
  • WORKDIR
  • ONBUILD(与上述支持的指令之一组合使用)

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

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

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

.dockerignore 文件

您可以使用.dockerignore文件从构建上下文排除文件和目录。有关更多信息,请参阅.dockerignore文件

Shell 和 exec 形式

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

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

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

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

Exec 形式

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

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

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

变量替换

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

如果您需要shell处理,则可以使用shell形式,或者使用exec形式直接执行shell,例如:RUN [ "sh", "-c", "echo $HOME" ]。当使用exec形式并直接执行shell时(例如shell形式),进行环境变量替换的是shell,而不是构建器。

反斜杠

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

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

此示例的正确语法是:

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

Shell 形式

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

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

它们等效于以下行:

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

您也可以将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> 指令中使用,以引用在此阶段构建的镜像。
  • tagdigest 值是可选的。如果省略两者中的任何一个,构建器默认假定为 latest 标签。如果构建器找不到 tag 值,则会返回错误。

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

理解 ARG 和 FROM 的交互

FROM 指令支持由出现在第一个 FROM 之前的任何 ARG 指令声明的变量。

ARG  CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD  /code/run-app

FROM extras:${CODE_VERSION}
CMD  /code/run-extras

FROM 之前声明的 ARG 位于构建阶段之外,因此无法在 FROM 之后的任何指令中使用它。要在构建阶段内使用在第一个 FROM 之前声明的 ARG 的默认值,请使用不带值的 ARG 指令。

ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version

RUN

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

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

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

shell 形式最常用,它允许您使用换行符转义heredoc将较长的指令分解成多行。

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

RUN 指令可用的 [OPTIONS] 为:

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

RUN 指令的缓存失效

RUN 指令的缓存不会在下次构建期间自动失效。诸如 RUN apt-get dist-upgrade -y 之类的指令的缓存将在下次构建期间重用。可以使用 --no-cache 标志使 RUN 指令的缓存失效,例如 docker build --no-cache

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

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

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

此挂载类型允许将文件或目录绑定到构建容器。绑定挂载默认为只读。

选项描述
targetdstdestination1挂载路径。
sourcefrom 中的源路径。默认为 from 的根目录。
from用作源根目录的构建阶段、上下文或镜像名称。默认为构建上下文。
rwreadwrite允许对挂载进行写入。写入的数据将被丢弃。

RUN --mount=type=cache

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

选项描述
id用于标识单独/不同缓存的可选 ID。默认为 target 的值。
targetdstdestination1挂载路径。
roreadonly如果设置,则为只读。
sharingsharedprivatelocked 之一。默认为 sharedshared 缓存挂载可以被多个写入器同时使用。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

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

RUN --mount=type=secret

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

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

选项描述
id密钥的 ID。默认为目标路径的基名。
targetdstdestination将密钥挂载到指定的路径。如果未设置并且 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 密钥,并支持密码短语。

选项描述
idSSH 代理套接字或密钥的 ID。默认为“default”。
targetdstdestinationSSH 代理套接字路径。默认为 /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 允许控制在哪个网络环境中运行命令。

支持的网络类型为:

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

RUN --network=default

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

RUN --network=none

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

示例:隔离外部影响

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

pip 只能安装 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指令。

如果希望容器每次运行相同的可执行文件,则应考虑结合使用ENTRYPOINTCMD。请参阅ENTRYPOINT。如果用户为docker run指定参数,则这些参数将覆盖CMD中指定的默认值,但仍然使用默认的ENTRYPOINT

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

注意

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

LABEL

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

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

LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."

一个镜像可以有多个标签。可以在一行中指定多个标签。在Docker 1.10之前,这会减小最终镜像的大小,但现在不再如此。您仍然可以选择在一项指令中以以下两种方式之一指定多个标签。

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

注意

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

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

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

$ docker image inspect --format='{{json .Config.Labels}}' myimage
{
  "com.example.vendor": "ACME Incorporated",
  "com.example.label-with-value": "foo",
  "version": "1.0",
  "description": "This text illustrates that label-values can span multiple lines.",
  "multi.label1": "value1",
  "multi.label2": "value2",
  "other": "value3"
}

MAINTAINER(已弃用)

MAINTAINER <name>

MAINTAINER指令设置生成的镜像的作者字段。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

在这种情况下,如果使用-Pdocker 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-dir1.1
--checksum1.6
--chown
--chmod1.2
--link1.4
--exclude1.7-labs

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

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

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

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

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

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

ADD https://example.com/archive.zip /usr/src/things/
ADD [email protected]:user/repo.git /usr/src/things/

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

从构建上下文添加文件

任何不以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.jsindex.ts

ADD index.?s /dest/

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

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

添加本地 tar 归档文件

当使用本地 tar 归档文件作为ADD的源时,并且归档文件采用已识别的压缩格式(gzipbzip2xz,或未压缩),则该归档文件将被解压缩并解压到指定的目标。仅解压本地 tar 归档文件。如果 tar 归档文件是远程 URL,则不会解压归档文件,而是将其下载并放置在目标位置。

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

  1. 目标路径上已存在的内容,以及
  2. 源树的内容,冲突以文件为单位,支持添加的内容。

注意

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

从 URL 添加文件

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

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

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

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

从 Git 仓库添加文件

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

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

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

ADD [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>。支持的算法为 sha256sha384sha512

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

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

ADD --chown --chmod

参见 COPY --chown --chmod

参见 COPY --link

ADD --exclude

参见 COPY --exclude

COPY

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

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

可用的[OPTIONS]

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

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

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

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

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

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

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

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

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

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

从构建上下文复制

从构建上下文复制源文件时,它们的路径将被解释为相对于上下文的根目录。如果您指定了一个指向构建上下文外部的相对路径,例如 COPY ../something /something,则父目录路径将被自动去除。此示例中的有效源路径变为 COPY something /something

如果源是目录,则复制目录的内容,包括文件系统元数据。目录本身不会被复制,只有其内容会被复制。如果它包含子目录,这些子目录也会被复制,并与目标中任何现有目录合并。任何冲突都将以文件为单位,支持添加的内容,除非您尝试将目录复制到现有文件上,否则会引发错误。

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

如果您通过标准输入将 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.jsindex.ts

COPY index.?s /dest/

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

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

目标

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

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

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

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

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

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

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

COPY --from

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

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

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

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

FROM scratch
COPY --from=build /hello /

您还可以直接从命名上下文(使用 --build-context <name>=<source> 指定)或镜像中复制文件。以下示例从官方 Nginx 镜像复制 nginx.conf 文件。

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

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

COPY --chown --chmod

注意

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

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

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

除非可选的 --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[=<boolean>]] <src> ... <dest>

COPYADD 命令中启用此标志,您可以复制具有增强语义的文件,这些文件保持独立于其自身层,并且在更改先前层的命令时不会失效。

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

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

相当于进行两次构建

FROM alpine

FROM scratch
COPY /foo /bar

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

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

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

--link=false 的不兼容性

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

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

COPY --parents

注意

稳定语法中尚不可用,请使用 docker/dockerfile:1.7-labs 版本。

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

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

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

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

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

此行为类似于Linux cp 实用程序--parentsrsync--relative 标志。

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

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

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

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

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

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

COPY --exclude

注意

稳定语法中尚不可用,请使用 docker/dockerfile:1.7-labs 版本。

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

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

路径表达式的格式与<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"]

如果需要为单个可执行文件编写启动脚本,可以使用execgosu命令确保最终可执行文件接收 Unix 信号

#!/usr/bin/env bash
set -e

if [ "$1" = 'postgres' ]; then
    chown -R postgres "$PGDATA"

    if [ -z "$(ls -A "$PGDATA")" ]; then
        gosu postgres initdb
    fi

    exec gosu postgres "$@"
fi

exec "$@"

最后,如果需要在关机时进行一些额外的清理(或与其他容器通信),或者协调多个可执行文件,则可能需要确保ENTRYPOINT脚本接收 Unix 信号,将它们传递下去,然后执行更多工作

#!/bin/sh
# Note: I've written this using sh so it works in the busybox container too

# USE the trap if you need to also do manual cleanup after the service is stopped,
#     or need to start multiple services in the one container
trap "echo TRAPed signal" HUP INT QUIT TERM

# start service in background here
/usr/sbin/apachectl start

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

# stop service and clean up here
echo "stopping apache"
/usr/sbin/apachectl stop

echo "exited $0"

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

$ docker exec -it test ps aux

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.1  0.0   4448   692 ?        Ss+  00:42   0:00 /bin/sh /run.sh 123 cmd cmd2
root        19  0.0  0.2  71304  4440 ?        Ss   00:42   0:00 /usr/sbin/apache2 -k start
www-data    20  0.2  0.2 360468  6004 ?        Sl   00:42   0:00 /usr/sbin/apache2 -k start
www-data    21  0.2  0.2 360468  6000 ?        Sl   00:42   0:00 /usr/sbin/apache2 -k start
root        81  0.0  0.1  15572  2140 ?        R+   00:44   0:00 ps aux

$ docker top test

PID                 USER                COMMAND
10035               root                {run.sh} /bin/sh /run.sh 123 cmd cmd2
10054               root                /usr/sbin/apache2 -k start
10055               33                  /usr/sbin/apache2 -k start
10056               33                  /usr/sbin/apache2 -k start

$ /usr/bin/time docker stop test

test
real	0m 0.27s
user	0m 0.03s
sys	0m 0.03s

注意

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

Shell 形式 ENTRYPOINT 示例

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

FROM ubuntu
ENTRYPOINT exec top -b

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

$ docker run -it --rm --name test top

Mem: 1704520K used, 352148K free, 0K shrd, 0K buff, 140368121167873K cached
CPU:   5% usr   0% sys   0% nic  94% idle   0% io   0% irq   0% sirq
Load average: 0.08 0.03 0.05 2/98 6
  PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
    1     0 root     R     3164   0%   0% top -b

它在docker stop时干净退出

$ /usr/bin/time docker stop test

test
real	0m 0.20s
user	0m 0.02s
sys	0m 0.04s

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

FROM ubuntu
ENTRYPOINT top -b
CMD -- --ignored-param1

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

$ docker run -it --name test top --ignored-param2

top - 13:58:24 up 17 min,  0 users,  load average: 0.00, 0.00, 0.00
Tasks:   2 total,   1 running,   1 sleeping,   0 stopped,   0 zombie
%Cpu(s): 16.7 us, 33.3 sy,  0.0 ni, 50.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   1990.8 total,   1354.6 free,    231.4 used,    404.7 buff/cache
MiB Swap:   1024.0 total,   1024.0 free,      0.0 used.   1639.8 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
    1 root      20   0    2612    604    536 S   0.0   0.0   0:00.02 sh
    6 root      20   0    5956   3188   2768 R   0.0   0.2   0:00.00 top

您可以从top的输出中看到,指定的ENTRYPOINT不是PID 1

如果然后运行docker stop test,容器将不会干净地退出——stop命令将在超时后强制发送SIGKILL

$ docker exec -it test ps waux

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.4  0.0   2612   604 pts/0    Ss+  13:58   0:00 /bin/sh -c top -b --ignored-param2
root         6  0.0  0.1   5956  3188 pts/0    S+   13:58   0:00 top -b
root         7  0.0  0.1   5884  2816 pts/1    Rs+  13:58   0:00 ps waux

$ /usr/bin/time docker stop test

test
real	0m 10.19s
user	0m 0.04s
sys	0m 0.03s

理解 CMD 和 ENTRYPOINT 的交互

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

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

  2. 当将容器用作可执行文件时,应定义ENTRYPOINT

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

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

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

无 ENTRYPOINTENTRYPOINT exec_entry p1_entryENTRYPOINT ["exec_entry", "p1_entry"]
无 CMD错误,不允许/bin/sh -c exec_entry p1_entryexec_entry p1_entry
CMD ["exec_cmd", "p1_cmd"]exec_cmd p1_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry exec_cmd p1_cmd
CMD exec_cmd p1_cmd/bin/sh -c exec_cmd p1_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry /bin/sh -c exec_cmd p1_cmd

注意

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

VOLUME

VOLUME ["/data"]

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

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

FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol

此 Dockerfile 会生成一个镜像,该镜像会导致docker run/myvol处创建一个新的挂载点,并将greeting文件复制到新创建的卷中。

关于指定卷的说明

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

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

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

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

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

USER

USER <user>[:<group>]

USER <UID>[:<GID>]

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

请注意,当为用户指定一个组时,用户将*只有*指定的组成员资格。任何其他已配置的组成员资格都将被忽略。

警告

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

在 Windows 上,如果用户不是内置帐户,则必须先创建用户。这可以使用作为 Dockerfile 部分调用的 net user 命令完成。

FROM microsoft/windowsservercore
# Create Windows user in the container
RUN net user /add patrick
# Set it for subsequent commands
USER patrick

WORKDIR

WORKDIR /path/to/workdir

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

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

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

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

WORKDIR 指令可以解析之前使用 ENV 设置的环境变量。您只能使用在 Dockerfile 中显式设置的环境变量。例如

ENV DIRPATH=/path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd

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

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

因此,为了避免在未知目录中进行意外操作,最好显式设置您的 WORKDIR

ARG

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

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

警告

不建议使用构建参数来传递机密信息,例如用户凭据、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 变量

您可以使用 ARGENV 指令来指定可用于 RUN 指令的变量。使用 ENV 指令定义的环境变量始终会覆盖同名的 ARG 指令。考虑此包含 ENVARG 指令的 Dockerfile。

FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=v1.0.0
RUN echo $CONT_IMG_VER

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

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

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

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

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

ARG 指令不同,ENV 值始终保留在构建的镜像中。考虑一个不带 --build-arg 标志的 docker 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/amd64linux/arm/v7windows/amd64
  • TARGETOS - TARGETPLATFORM 的操作系统组件
  • TARGETARCH - TARGETPLATFORM 的架构组件
  • TARGETVARIANT - TARGETPLATFORM 的变体组件
  • BUILDPLATFORM - 执行构建的节点的平台。
  • BUILDOS - BUILDPLATFORM 的操作系统组件
  • BUILDARCH - BUILDPLATFORM 的架构组件
  • BUILDVARIANT - BUILDPLATFORM 的变体组件

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

例如

FROM alpine
ARG TARGETPLATFORM
RUN echo "I'm building for $TARGETPLATFORM"

BuildKit 内置构建参数

参数类型描述
BUILDKIT_CACHE_MOUNT_NS字符串设置可选的缓存 ID 命名空间。
BUILDKIT_CONTEXT_KEEP_GIT_DIR布尔值触发 Git 上下文以保留 .git 目录。
BUILDKIT_INLINE_CACHE2布尔值是否将内联缓存元数据添加到镜像配置。
BUILDKIT_MULTI_PLATFORM布尔值无论多平台输出与否,都选择确定性输出。
BUILDKIT_SANDBOX_HOSTNAME字符串设置主机名(默认为 buildkitsandbox
BUILDKIT_SYNTAX字符串设置前端镜像
SOURCE_DATE_EPOCH整数设置创建的镜像和层的 Unix 时间戳。更多信息请参见 可重复构建。自 Dockerfile 1.5、BuildKit 0.11 起支持。

示例:保留 .git 目录

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

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

对构建缓存的影响

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

例如,考虑这两个 Dockerfile:

FROM ubuntu
ARG CONT_IMG_VER
RUN echo $CONT_IMG_VER
FROM ubuntu
ARG CONT_IMG_VER
RUN echo hello

如果您在命令行上指定 --build-arg CONT_IMG_VER=<value>,在这两种情况下,第 2 行的规范都不会导致缓存未命中;第 3 行会导致缓存未命中。ARG CONT_IMG_VER 会导致 RUN 行被识别为与运行 CONT_IMG_VER=<value> echo hello 相同,因此如果 <value> 更改,则会发生缓存未命中。

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

FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=$CONT_IMG_VER
RUN echo $CONT_IMG_VER

在这个例子中,缓存未命中发生在第 3 行。未命中发生是因为ENV中变量的值引用了ARG变量,而该变量通过命令行进行了更改。在这个例子中,ENV命令导致镜像包含该值。

如果ENV指令覆盖了同名的ARG指令,就像这个Dockerfile一样

FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=hello
RUN echo $CONT_IMG_VER

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

ONBUILD

ONBUILD <INSTRUCTION>

ONBUILD指令向镜像添加一个触发器指令,该指令将在以后使用该镜像作为另一个构建的基础时执行。触发器将在下游构建的上下文中执行,就像它已在下游Dockerfile中的FROM指令之后立即插入一样。

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

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

解决方案是使用ONBUILD注册稍后在下一个构建阶段运行的高级指令。

以下是它的工作原理

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

例如,您可以添加如下内容

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

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

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

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

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

ONBUILD 限制

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

STOPSIGNAL

STOPSIGNAL signal

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

可以使用docker rundocker 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:cmdpowershell,以及其他可用的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指令的影响:RUNCMDENTRYPOINT

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

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

构建器调用的命令将是

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

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

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

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

虽然JSON形式是明确的,并且不使用不必要的cmd.exe,但它确实需要通过双引号和转义来提高冗余性。另一种机制是使用SHELL指令和shell形式,为Windows用户创建更自然的语法,尤其是在与escape解析器指令结合使用时

# escape=`

FROM microsoft/nanoserver
SHELL ["powershell","-command"]
RUN New-Item -ItemType Directory C:\Example
ADD Execute-MyCmdlet.ps1 c:\example\
RUN c:\example\Execute-MyCmdlet -sample 'hello world'

结果是

PS E:\myproject> docker build -t shell .

Sending build context to Docker daemon 4.096 kB
Step 1/5 : FROM microsoft/nanoserver
 ---> 22738ff49c6d
Step 2/5 : SHELL powershell -command
 ---> Running in 6fcdb6855ae2
 ---> 6331462d4300
Removing intermediate container 6fcdb6855ae2
Step 3/5 : RUN New-Item -ItemType Directory C:\Example
 ---> Running in d0eef8386e97


    Directory: C:\


Mode         LastWriteTime              Length Name
----         -------------              ------ ----
d-----       10/28/2016  11:26 AM              Example


 ---> 3f2fbf1395d9
Removing intermediate container d0eef8386e97
Step 4/5 : ADD Execute-MyCmdlet.ps1 c:\example\
 ---> a955b2621c31
Removing intermediate container b825593d39fc
Step 5/5 : RUN c:\example\Execute-MyCmdlet 'hello world'
 ---> Running in be6d8e63fe75
hello world
 ---> 8e559e9bf424
Removing intermediate container be6d8e63fe75
Successfully built 8e559e9bf424
PS E:\myproject>

SHELL指令也可用于修改shell的操作方式。例如,在Windows上使用SHELL cmd /S /C /V:ON|OFF,可以修改延迟环境变量扩展语义。

如果需要替代shell,例如zshcshtcsh和其他shell,则也可以在Linux上使用SHELL指令。

Here-Documents

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

示例:运行多行脚本

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

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

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

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

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

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

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

示例:创建内联文件

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

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

此处使用了常规的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示例,请参考:


  1. 需要的值 ↩︎ ↩︎ ↩︎

  2. 对于Docker集成的 BuildKit 和 `docker buildx build` ↩︎