构建最佳实践

使用多阶段构建

多阶段构建使您能够通过在构建镜像和最终输出之间创建更清晰的分离,来减小最终镜像的大小。 将Dockerfile指令拆分为不同的阶段,以确保最终输出仅包含运行应用程序所需的文件。

使用多个阶段还可以通过并行执行构建步骤来更高效地构建。

查看多阶段构建以获取更多信息。

创建可重用阶段

如果你有多个具有很多共同点的镜像,考虑创建一个可重用的阶段,其中包含共享组件,并基于该阶段构建你的独特阶段。Docker 只需要构建一次公共阶段。这意味着你的派生镜像在 Docker 主机上更高效地使用内存,并且加载速度更快。

维护一个通用的基础阶段("不要重复自己")也更容易, 而不是有多个不同的阶段做类似的事情。

选择合适的基镜像

构建安全镜像的第一步是选择正确的基础镜像。在选择镜像时,请确保它来自可信来源并保持其体积小巧。

  • Docker 官方镜像 是 Docker Hub 上最安全、最可靠的镜像之一。通常情况下, Docker 官方镜像中包含的 CVE 软件包很少或没有,并且经过 Docker 和项目维护人员的全面审查。

  • Verified Publisher 镜像是由与 Docker 合作的组织发布和维护的高质量镜像,Docker 会验证其仓库中内容的真实性。

  • Docker赞助的开源项目 由Docker赞助的开源项目发布和维护 通过一个 开源项目

当你选择基础镜像时,留意徽章,它们表示该镜像属于这些项目。

Docker Hub Official and Verified Publisher images

在从Dockerfile构建自己的镜像时,请确保选择一个符合您要求的最小基础镜像。更小的基础镜像不仅提供可移植性和快速下载,还减小了镜像的大小,并通过依赖项最小化引入的漏洞数量。

你还需要考虑使用两种类型的基镜像:一种用于构建和单元测试,另一种(通常更精简)用于生产。在开发的后期阶段,你的镜像可能不需要构建工具,如编译器、构建系统和调试工具。一个具有最少依赖的小型镜像可以显著降低攻击面。

经常重建你的镜像

Docker 镜像是不可变的。构建镜像就是在那一刻对镜像进行快照。这包括你在构建中使用的任何基础镜像、库或其他软件。为了保持镜像的最新和安全,请确保经常使用更新后的依赖项重建镜像。

为了确保在构建过程中获取依赖项的最新版本, 您可以使用--no-cache选项来避免缓存命中。

$ docker build --no-cache -t my-image:my-tag .

以下 Dockerfile 使用了 24.04 标签的 ubuntu 镜像。随着时间的推移, 该标签可能会解析为 ubuntu 镜像的不同底层版本, 因为发布者会使用新的安全补丁和更新的库重新构建镜像。通过使用 --no-cache, 您可以避免缓存命中并确保基础镜像和依赖项的全新下载。

# syntax=docker/dockerfile:1
FROM ubuntu:24.04
RUN apt-get -y update && apt-get install -y --no-install-recommends python3

也请考虑 固定基础镜像版本

使用 .dockerignore 排除文件

为了在不重构源代码仓库的情况下排除与构建无关的文件,请使用 .dockerignore 文件。该文件支持与 .gitignore 文件类似的排除模式。

例如,要排除所有扩展名为 .md 的文件:

*.md

有关如何创建的详细信息,请参阅 Dockerignore 文件

创建临时容器

由您的Dockerfile定义的镜像应生成尽可能短暂的容器。短暂意味着容器可以停止并销毁,然后重建并替换为绝对最小的设置和配置。

参考 The Twelve-factor App 方法论中的 进程,以了解为何要以无状态的方式运行容器。

不要安装不必要的软件包

避免因为可能很好用而安装额外或不必要的软件包。例如,你不需要在数据库镜像中包含文本编辑器。

当你避免安装额外或不必要的软件包时,你的镜像具有降低的复杂性、减少的依赖性、减小的文件大小和缩短的构建时间。

解耦应用程序

每个容器应该只有一个关注点。将应用程序解耦为多个容器,可以更容易地横向扩展和重用容器。 例如,一个 Web 应用程序堆栈可能由三个独立的容器组成,每个容器都有其唯一的镜像,以解耦的方式管理 Web 应用程序、数据库和内存缓存。

将每个容器限制为一个进程是一个很好的经验法则,但并不是一个不可更改的规则。例如,容器不仅可以 使用初始化进程启动, 某些程序可能会自行启动其他进程。例如, Celery 可以启动多个工作进程,而 Apache 可以为每个请求创建一个进程。

运用你的最佳判断,尽量使容器保持干净和模块化。如果容器相互依赖,你可以使用 Docker 容器网络 确保这些容器可以相互通信。

按多行参数排序

在可能的情况下,按字母数字顺序对多行参数进行排序,以便更容易维护。 这有助于避免重复安装包并使 列表更易于更新。这也会使 PR 更容易阅读和 审核。在反斜杠(\)前添加空格也有帮助。

这是一个来自 buildpack-deps 镜像

RUN apt-get update && apt-get install -y --no-install-recommends \
  bzr \
  cvs \
  git \
  mercurial \
  subversion \
  && rm -rf /var/lib/apt/lists/*

利用构建缓存

在构建镜像时,Docker 会按照您在 Dockerfile 中指定的顺序逐步执行每条指令。对于每条指令,Docker 会检查是否可以重用构建缓存中的该指令。

理解构建缓存的工作原理以及缓存失效的触发机制, 对于确保更快的构建至关重要。 有关 Docker 构建缓存的更多信息以及如何优化构建, 请参阅 Docker 构建缓存

固定基础镜像版本

镜像标签是可变的,这意味着发布者可以更新标签以指向新的镜像。这很有用,因为它允许发布者将标签更新为指向镜像的新版本。作为镜像的使用者,这意味着当你重新构建镜像时,你会自动获取新版本。

例如,如果你在 Dockerfile 中指定 FROM alpine:3.193.19 将解析为 3.19 的最新补丁版本。

# syntax=docker/dockerfile:1
FROM alpine:3.19

在某一时间点,3.19 标签可能指向版本 3.19.1 的镜像。如果你在三个月后重新构建镜像,相同的标签可能指向不同的版本,例如 3.19.4。这种发布工作流程是最佳实践,大多数发布者都采用这种标签策略,但并未强制执行。

这样做的缺点是,你不能保证每次构建都能得到相同的版本。这可能会导致破坏性更改,并且意味着你也没有正在使用的镜像的确切版本的审计跟踪。

为了完全确保供应链的完整性,您可以将镜像版本固定到特定的摘要上。通过将镜像固定到摘要,您可以保证始终使用相同的镜像版本,即使发布者用新镜像替换了标签。例如,以下 Dockerfile 将 Alpine 镜像固定到与前面相同的标签 3.19,但这次也使用了摘要引用。

# syntax=docker/dockerfile:1
FROM alpine:3.19@sha256:13b7e62e8df80264dbb747995705a986aa530415763a6c58f84a3ca8af9a5bcd

使用此 Dockerfile,即使发布者更新了 3.19 标签,您的构建仍将使用固定版本的镜像:13b7e62e8df80264dbb747995705a986aa530415763a6c58f84a3ca8af9a5bcd

虽然这可以帮助您避免意外的更改,但每次想要更新时都必须手动查找并包含基础镜像版本的镜像摘要,这也会更加繁琐。而且您放弃了自动化的安全修复,这可能是您想要获得的。

Docker Scout 的默认 最新基础镜像 策略 检查您使用的基础镜像版本是否确实是最新版本。此策略还检查 Dockerfile 中的固定摘要是否对应正确的版本。如果发布者更新了您已固定的镜像,策略评估将返回不符合状态,表明您应更新镜像。

Docker Scout 还支持自动修复工作流,用于保持您的基础镜像最新。当有新的镜像摘要可用时,Docker Scout 可以在您的仓库中自动创建拉取请求,以将您的 Dockerfile 更新为使用最新版本。这比使用会自动更改版本的标签更好,因为您可以完全控制,并且可以追溯更改发生的时间和方式。

有关使用 Docker Scout 自动更新基础镜像的更多信息,请参阅 修复

在 CI 中构建和测试您的镜像

当你向源代码控制提交更改或创建拉取请求时,使用 GitHub Actions 或其他 CI/CD 管道自动构建并标记 Docker 镜像并进行测试。

Dockerfile 指令

按照以下建议,正确使用 Dockerfile 指令 来创建一个高效且易于维护的 Dockerfile。

只要有可能,使用当前官方镜像作为您镜像的基础。Docker推荐使用Alpine镜像,因为它受到严格控制且体积小(目前小于6MB),同时仍然是一个完整的Linux发行版。

有关FROM指令的更多信息,请参阅 Dockerfile 中 FROM 指令的参考

标签

您可以为镜像添加标签,以帮助按项目组织镜像、记录许可信息、便于自动化或其他原因。对于每个标签,请添加一行以LABEL开头,包含一个或多个键值对。以下示例展示了不同的可接受格式。说明性注释已内联包含。

包含空格的字符串必须加引号或对空格进行转义。内部的引号字符("),也必须进行转义。例如:

# Set one or more individual labels
LABEL com.example.version="0.0.1-beta"
LABEL vendor1="ACME Incorporated"
LABEL vendor2=ZENITH\ Incorporated
LABEL com.example.release-date="2015-02-12"
LABEL com.example.version.is-production=""

一个镜像可以有多个标签。在Docker 1.10之前,建议将所有标签合并到一个LABEL指令中,以防止创建额外的层。这已不再必要,但合并标签仍然受支持。例如:

# Set multiple labels on one line
LABEL com.example.version="0.0.1-beta" com.example.release-date="2015-02-12"

以上示例也可以写成:

# Set multiple labels at once, using line-continuation characters to break long lines
LABEL vendor=ACME\ Incorporated \
      com.example.is-beta= \
      com.example.is-production="" \
      com.example.version="0.0.1-beta" \
      com.example.release-date="2015-02-12"

查看了解对象标签 以获取有关可接受标签键和值的指南。有关查询标签的信息,请参阅与过滤相关的项目在 管理对象上的标签。 另请参阅Dockerfile参考中的 LABEL

运行

将复杂的 RUN 语句拆分成多行,用反斜杠分隔,以使 Dockerfile 更具可读性、可理解性和可维护性。

例如,您可以使用 && 运算符串联命令,并使用转义字符将长命令拆分为多行。

RUN apt-get update && apt-get install -y --no-install-recommends \
    package-bar \
    package-baz \
    package-foo

默认情况下,反斜杠转义换行符,但您可以使用 escape 指令进行更改。

你还可以使用此处文档来运行多个命令,而无需使用管道操作符进行链接:

RUN <<EOF
apt-get update
apt-get install -y --no-install-recommends \
    package-bar \
    package-baz \
    package-foo
EOF

有关 RUN 的更多信息,请参阅 Dockerfile 中 RUN 指令的参考文档

apt-get

Debian 基础镜像中使用 RUN 指令的一个常见用例是使用 apt-get 安装软件。由于 apt-get 会安装包,因此 RUN apt-get 命令存在一些需要留意的反直觉行为。

始终在同一 RUN 语句中将 RUN apt-get updateapt-get install 结合使用。例如:

RUN apt-get update && apt-get install -y --no-install-recommends \
    package-bar \
    package-baz \
    package-foo

RUN 语句中单独使用 apt-get update 会导致缓存问题,并使后续的 apt-get install 指令失败。例如,以下 Dockerfile 会出现此问题:

# syntax=docker/dockerfile:1

FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y --no-install-recommends curl

在构建镜像后,所有层都存储在 Docker 缓存中。假设你稍后修改 apt-get install,通过添加一个额外的包,如以下 Dockerfile 所示:

# syntax=docker/dockerfile:1

FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y --no-install-recommends curl nginx

Docker 将初始和修改后的指令视为相同,并重用之前步骤中的缓存。因此,apt-get update 不会被执行,因为构建使用了缓存的版本。由于 apt-get update 没有运行,您的构建可能会获取到 curlnginx 包的过时版本。

使用 RUN apt-get update && apt-get install -y --no-install-recommends 可确保你的 Dockerfile 以无需进一步编码或手动干预的方式安装最新版本的包。这种技术被称为缓存破坏。你也可以通过指定包版本来实现缓存破坏。这被称为版本固定。例如:

RUN apt-get update && apt-get install -y --no-install-recommends \
    package-bar \
    package-baz \
    package-foo=1.3.*

版本固定会强制构建从缓存中检索特定版本,而不管缓存中有什么。此技术还可以减少由于所需软件包的意外更改而导致的失败。

下面是一个格式良好的 RUN 指令,演示了所有 apt-get 推荐内容。

RUN apt-get update && apt-get install -y --no-install-recommends \
    aufs-tools \
    automake \
    build-essential \
    curl \
    dpkg-sig \
    libcap-dev \
    libsqlite3-dev \
    mercurial \
    reprepro \
    ruby1.9.1 \
    ruby1.9.1-dev \
    s3cmd=1.1.* \
    && rm -rf /var/lib/apt/lists/*

s3cmd 参数指定了版本 1.1.*。如果镜像之前使用的是旧版本,指定新版本会导致 apt-get update 的缓存失效,并确保新版本的安装。在每行列出软件包还可以防止软件包重复的错误。

此外,当你清理 apt 缓存并移除 /var/lib/apt/lists 时, 由于 apt 缓存不会存储在单独的层中,这会减小镜像大小。由于 RUN 语句以 apt-get update 开头, 在执行 apt-get install 之前,包缓存总是会被刷新。

官方 Debian 和 Ubuntu 镜像 自动运行 apt-get clean,因此无需显式调用。

使用管道

一些 RUN 命令依赖于将一个命令的输出通过管道字符(|)传递给另一个命令的能力,如下例所示:

RUN wget -O - https://some.site | wc -l > /number

Docker 使用 /bin/sh -c 解释器执行这些命令,该解释器仅根据管道中最后一个操作的退出码来判断成功与否。 在上面的示例中,只要 wc -l 命令成功,即使 wget 命令失败,此构建步骤也会成功并生成新的镜像。

如果你希望在管道的任何阶段出现错误时命令失败, 在命令前加上set -o pipefail &&以确保意外错误不会导致构建无意中成功。例如:

RUN set -o pipefail && wget -O - https://some.site | wc -l > /number

注意

并非所有 shell 都支持 -o pipefail 选项。

在基于 Debian 的镜像中,例如 dash shell,考虑使用 RUNexec 形式来显式选择一个支持 pipefail 选项的 shell。例如:

RUN ["/bin/bash", "-c", "set -o pipefail && wget -O - https://some.site | wc -l > /number"]

命令(CMD)

应使用 CMD 指令运行镜像中包含的软件以及任何参数。 CMD 应几乎总是以 CMD ["executable", "param1", "param2"] 的形式使用。因此,如果镜像是用于服务(例如 Apache 和 Rails),则会运行类似 CMD ["apache2","-DFOREGROUND"] 的内容。实际上,对于任何基于服务的镜像,都推荐使用此形式的指令。

在大多数其他情况下,CMD 应该提供一个交互式 shell,例如 bash、python 和 perl。例如,CMD ["perl", "-de0"]CMD ["python"]CMD ["php", "-a"]。使用这种形式意味着当你执行类似 docker run -it python 的命令时,你会进入一个可用的 shell,随时准备使用。 CMD 很少像 CMD ["param", "param"] 那样与 ENTRYPOINT 结合使用,除非你和预期的用户已经相当熟悉 ENTRYPOINT 的工作原理。

有关 CMD 的更多信息,请参阅 Dockerfile 中 CMD 指令的参考文档

暴露端口

EXPOSE 指令表示容器监听连接的端口。因此,您应该为应用程序使用通用的传统端口。例如,包含 Apache Web 服务器的镜像将使用 EXPOSE 80,而包含 MongoDB 的镜像将使用 EXPOSE 27017,依此类推。

对于外部访问,您的用户可以执行docker run,并使用一个标志指示如何将指定端口映射到他们选择的端口。 对于容器链接,Docker 为从接收容器到源容器的路径提供环境变量(例如,MYSQL_PORT_3306_TCP)。

有关 EXPOSE 的更多信息,请参阅 Dockerfile 中 EXPOSE 指令的参考

环境变量

为了使新软件更容易运行,您可以使用 ENV 来更新容器中安装的软件的 PATH 环境变量。例如,ENV PATH=/usr/local/nginx/bin:$PATH 确保 CMD ["nginx"] 能够正常工作。

ENV 指令也可用于提供要容器化的服务所需的环境变量,例如 Postgres 的 PGDATA

最后,ENV 还可用于设置常用的版本号,以便更轻松地维护版本更新,如下例所示:

ENV PG_MAJOR=9.3
ENV PG_VERSION=9.3.4
RUN curl -SL https://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgres &&
ENV PATH=/usr/local/postgres-$PG_MAJOR/bin:$PATH

类似于在程序中使用常量变量,而不是硬编码值,这种方法允许您只需更改一个ENV指令,即可自动提升容器中软件的版本。

每行 ENV 都会像 RUN 命令一样创建一个新的中间层。这意味着即使你在后续层中取消设置了环境变量,它仍然会保留在这一层中,并且可以导出其值。你可以通过创建如下所示的 Dockerfile 并构建它来测试这一点。

# syntax=docker/dockerfile:1
FROM alpine
ENV ADMIN_USER="mark"
RUN echo $ADMIN_USER > ./mark
RUN unset ADMIN_USER
$ docker run --rm test sh -c 'echo $ADMIN_USER'

mark

为防止这种情况并真正取消设置环境变量,请使用带 shell 命令的 RUN, 在同一层中设置、使用并取消设置变量。 您可以使用 ;&& 分隔命令。如果使用第二种方法, 且其中一个命令失败,则 docker build 也会失败。这通常是个好主意。 在 Linux Dockerfile 中将 \ 用作行续行字符可提高可读性。 您还可以将所有命令放入 shell 脚本中,并让 RUN 命令仅运行该 shell 脚本。

# syntax=docker/dockerfile:1
FROM alpine
RUN export ADMIN_USER="mark" \
    && echo $ADMIN_USER > ./mark \
    && unset ADMIN_USER
CMD sh
$ docker run --rm test sh -c 'echo $ADMIN_USER'

有关 ENV 的更多信息,请参阅 Dockerfile 中 ENV 指令的参考文档

添加或复制

ADDCOPY 在功能上是相似的。 COPY 支持将文件从 构建上下文 或从 多阶段构建 中的阶段复制到容器中。 ADD 支持从远程 HTTPS 和 Git URL 获取文件的功能,并在从构建上下文添加文件时自动解压缩 tar 文件。

在多阶段构建中,你通常希望使用 COPY 来在不同阶段之间复制文件。如果你需要临时将构建上下文中的文件添加到容器中以执行 RUN 指令,你通常可以用绑定挂载替换 COPY 指令。例如,为了临时添加一个 requirements.txt 文件用于 RUN pip install 指令:

RUN --mount=type=bind,source=requirements.txt,target=/tmp/requirements.txt \
    pip install --requirement /tmp/requirements.txt

绑定挂载比 COPY 更高效,用于将构建上下文中的文件包含到容器中。请注意,绑定挂载的文件仅在单个 RUN 指令期间临时添加,并不会保留在最终镜像中。如果需要将构建上下文中的文件包含到最终镜像中,请使用 COPY

ADD 指令最适合在构建过程中需要下载远程构件时使用。与手动使用类似 wgettar 的方式添加文件相比,ADD 能确保更精确的构建缓存。 ADD 还内置了对远程资源校验和验证的支持,并提供了解析来自 Git URL 的分支、标签和子目录的协议。

以下示例使用 ADD 下载 .NET 安装程序。与多阶段构建结合使用时,最终阶段中仅保留 .NET 运行时,没有中间文件。

# syntax=docker/dockerfile:1

FROM scratch AS src
ARG DOTNET_VERSION=8.0.0-preview.6.23329.7
ADD --checksum=sha256:270d731bd08040c6a3228115de1f74b91cf441c584139ff8f8f6503447cebdbb \
    https://dotnetcli.azureedge.net/dotnet/Runtime/$DOTNET_VERSION/dotnet-runtime-$DOTNET_VERSION-linux-arm64.tar.gz /dotnet.tar.gz

FROM mcr.microsoft.com/dotnet/runtime-deps:8.0.0-preview.6-bookworm-slim-arm64v8 AS installer

# Retrieve .NET Runtime
RUN --mount=from=src,target=/src <<EOF
mkdir -p /dotnet
tar -oxzf /src/dotnet.tar.gz -C /dotnet
EOF

FROM mcr.microsoft.com/dotnet/runtime-deps:8.0.0-preview.6-bookworm-slim-arm64v8

COPY --from=installer /dotnet /usr/share/dotnet
RUN ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet

有关 ADDCOPY 的更多信息,请参阅以下内容:

入口点(ENTRYPOINT)

ENTRYPOINT的最佳用途是设置镜像的主要命令,使得该镜像可以像该命令一样运行,然后使用CMD作为默认标志。

以下是一个用于命令行工具 s3cmd 的镜像示例:

ENTRYPOINT ["s3cmd"]
CMD ["--help"]

您可以使用以下命令运行镜像并显示命令的帮助:

$ docker run s3cmd

或者,您可以使用正确的参数来执行命令,如下例所示:

$ docker run s3cmd ls s3://mybucket

这很有用,因为镜像名称可以同时作为Binaries的引用,如上面的命令所示。

ENTRYPOINT 指令还可以与辅助脚本结合使用,使其在启动工具需要多个步骤时,也能像上面的命令一样工作。

例如, Postgres 官方镜像 使用以下脚本作为其 ENTRYPOINT

#!/bin/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 "$@"

此脚本使用 命令execBash 以便最终运行的应用程序成为容器的 PID 1。这允许应用程序接收发送到容器的任何 Unix 信号。有关更多信息,请参阅 ENTRYPOINT参考文档

在以下示例中,辅助脚本被复制到容器中,并在容器启动时通过 ENTRYPOINT 运行:

COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["postgres"]

此脚本可让您以多种方式与 Postgres 进行交互。

它可以轻松启动Postgres:

$ docker run postgres

或者,您可以使用它来运行 Postgres 并向服务器传递参数:

$ docker run postgres postgres --help

最后,您可以使用它来启动一个完全不同的工具,例如 Bash:

$ docker run --rm -it postgres bash

有关 ENTRYPOINT 的更多信息,请参阅 Dockerfile 中 ENTRYPOINT 指令的参考

您应该使用VOLUME指令来公开由您的 Docker 容器创建的任何数据库存储区域、配置存储或文件和文件夹。强烈建议您对镜像中可变或用户可维护的部分使用VOLUME

有关 VOLUME 的更多信息,请参阅 Dockerfile 中 VOLUME 指令的参考

用户

如果服务可以在没有特权的情况下运行,请使用 USER 切换到非 root 用户。首先在 Dockerfile 中使用类似以下示例的内容创建用户和组:

RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres

注意

考虑一个显式的 UID/GID。

镜像中的用户和组被分配了非确定性的UID/GID,因为“下一个”UID/GID会被分配,而不管镜像是否重建。因此,如果这是关键的,你应该分配一个明确的UID/GID。

注意

由于 Go archive/tar 包在处理稀疏文件时存在一个 未解决的漏洞,尝试在 Docker 容器中创建具有相当大 UID 的用户可能会导致磁盘耗尽,因为容器层中的 /var/log/faillog 被 NULL(\0)字符填充。一个解决方法是将 --no-log-init 标志传递给 useradd。Debian/Ubuntu 的 adduser 包装器不支持此标志。

避免安装或使用 sudo,因为其不可预测的 TTY 和信号转发行为可能会导致问题。如果您确实需要类似 sudo 的功能,例如以 root 身份初始化守护程序,但以非 root 身份运行,可以考虑使用 “gosu”

最后,为了减少层数和复杂性,请避免频繁切换 USER

有关 USER 的更多信息,请参阅 Dockerfile 中 USER 指令的参考

工作目录(WORKDIR)

为了清晰和可靠,您应该始终为您的WORKDIR使用绝对路径。此外,您应该使用WORKDIR,而不是像RUN cd … && do-something这样的指令泛滥,因为它们难以阅读、排查和维护。

有关 WORKDIR 的更多信息,请参阅 Dockerfile 中 WORKDIR 指令的参考

ONBUILD

An ONBUILD 命令在当前 Dockerfile 构建完成后执行。 ONBUILD 在从当前镜像派生的任何子镜像中执行。将 ONBUILD 命令视为父 Dockerfile 给子 Dockerfile 的指令。

Docker 构建会在子 Dockerfile 中的任何命令之前执行 ONBUILD 条命令。

ONBUILD 对于将要基于 FROM 特定镜像构建的镜像很有用。例如,你会在 Dockerfile 中使用 ONBUILD 为用该语言编写的任意用户软件构建语言栈镜像,正如你在 Ruby 的 ONBUILD 变体 中看到的。

使用 ONBUILD 构建的镜像应使用单独的标签。例如, ruby:1.9-onbuildruby:2.0-onbuild

在将 ADDCOPY 放入 ONBUILD 时要小心。如果新构建的上下文中缺少正在添加的资源,onbuild 镜像会灾难性地失败。如上所述,添加一个单独的标签有助于缓解这种情况,因为它允许 Dockerfile 作者做出选择。

有关 ONBUILD 的更多信息,请参阅 Dockerfile 中 ONBUILD 指令的参考