构建最佳实践
使用多阶段构建
多阶段构建可以通过在镜像构建和最终输出之间创建更清晰的分离来减小最终镜像的大小。将 Dockerfile 指令拆分为不同的阶段,确保最终输出只包含运行应用程序所需的文件。
使用多个阶段还可以让您通过并行执行构建步骤来更高效地进行构建。
更多信息请参见多阶段构建。
创建可复用阶段
如果您有多个具有很多共同点的镜像,请考虑创建一个包含共享组件的可复用阶段,并将您的独特阶段基于该阶段。Docker 只需构建一次通用阶段。这意味着您的派生镜像可以更有效地利用 Docker 主机上的内存并更快地加载。
维护一个通用的基础阶段(遵循“不要重复自己”的原则)比维护多个执行类似操作的不同阶段更容易。
选择合适的基础镜像
实现安全镜像的第一步是选择合适的基础镜像。选择镜像时,请确保它构建自可信来源,并保持其体积小巧。
Docker 官方镜像是精选的镜像集合,具有清晰的文档,推广最佳实践,并定期更新。它们为许多应用程序提供了可信赖的起点。
Verified Publisher 镜像是与 Docker 合作的组织发布和维护的高质量镜像,Docker 会验证其仓库中内容的真实性。
Docker 赞助的开源项目是由通过开源计划获得 Docker 赞助的开源项目发布和维护的。
选择基础镜像时,请留意表明该镜像属于这些计划的徽章。


当从 Dockerfile 构建自己的镜像时,请确保选择一个符合您需求的最小基础镜像。较小的基础镜像不仅提供可移植性和快速下载,还能减小您的镜像大小,并最大程度地减少通过依赖项引入的漏洞数量。
您还应该考虑使用两种类型的基础镜像:一种用于构建和单元测试,另一种(通常更精简)用于生产环境。在开发的后期阶段,您的镜像可能不需要编译器、构建系统和调试工具等构建工具。具有最小依赖项的小型镜像可以显著降低攻击面。
经常重建您的镜像
Docker 镜像是不可变的。构建镜像是在那一刻拍摄该镜像的快照。这包括您在构建中使用的任何基础镜像、库或其他软件。为了使您的镜像保持最新和安全,请确保经常使用更新的依赖项重建您的镜像。
为了确保您在构建中获取最新版本的依赖项,可以使用 --no-cache
选项来避免缓存命中。
$ docker build --no-cache -t my-image:my-tag .
以下 Dockerfile 使用 ubuntu
镜像的 24.04
标签。随着时间推移,该标签可能解析到不同的 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 定义的镜像应生成尽可能短暂的容器。短暂意味着容器可以被停止和销毁,然后以最少的设置和配置重建和替换。
请参阅《十二因素应用》方法论下的进程,以了解以无状态方式运行容器的动机。
不要安装不必要的包
避免只因为可能有用而安装额外的或不必要的包。例如,您无需在数据库镜像中包含文本编辑器。
当您避免安装额外或不必要的包时,您的镜像将具有较低的复杂度、较少的依赖项、较小的文件大小和较短的构建时间。
解耦应用
每个容器应该只关注一件事。将应用程序解耦到多个容器中,更容易进行水平扩展和重用容器。例如,一个 Web 应用程序栈可能包含三个独立的容器,每个容器都有自己独特的镜像,以解耦的方式管理 Web 应用程序、数据库和内存缓存。
将每个容器限制为一个进程是一个不错的经验法则,但这并非严格规定。例如,容器不仅可以使用 init 进程启动,有些程序自身也会生成额外的进程。例如,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.19
,则 3.19
会解析为 3.19
系列的最新补丁版本。
# syntax=docker/dockerfile:1
FROM alpine:3.19
在某个时间点,3.19
标签可能指向镜像的 3.19.1 版本。如果您在 3 个月后重建该镜像,同一个标签可能指向不同的版本,例如 3.19.4。这种发布工作流是最佳实践,大多数发布者都使用这种标签策略,但它并非强制要求。
这样做的缺点是无法保证每次构建都获得相同的镜像。这可能导致破坏性更改,并且意味着您也没有正在使用的确切镜像版本的审计跟踪。
为了完全确保供应链的完整性,您可以将镜像版本固定到特定的摘要(digest)。通过将镜像固定到摘要,即使发布者用新镜像替换了标签,您也始终保证使用相同的镜像版本。例如,以下 Dockerfile 将 Alpine 镜像固定到与之前相同的标签 3.19
,但这次也带有一个摘要引用。
# syntax=docker/dockerfile:1
FROM alpine:3.19@sha256:13b7e62e8df80264dbb747995705a986aa530415763a6c58f84a3ca8af9a5bcd
使用此 Dockerfile,即使发布者更新了 3.19
标签,您的构建仍将使用固定的镜像版本:13b7e62e8df80264dbb747995705a986aa530415763a6c58f84a3ca8af9a5bcd
。
虽然这有助于您避免意外更改,但每次想要更新基础镜像版本时都必须手动查找并包含其摘要,这也很繁琐。而且您放弃了自动化安全修复,这可能是您希望获得的功能。
Docker Scout 的默认基础镜像最新状态策略会检查您使用的基础镜像版本是否确实是最新版本。此策略还会检查 Dockerfile 中固定的摘要是否对应于正确的版本。如果发布者更新了您固定的镜像,策略评估会返回不符合状态,表示您应该更新镜像。
Docker Scout 还支持自动化修正(remediation)工作流,用于保持基础镜像的最新状态。当有新的镜像摘要可用时,Docker Scout 可以自动在您的仓库上发起拉取请求(pull request),以更新您的 Dockerfile 来使用最新版本。这比使用会自动更改版本的标签更好,因为您拥有控制权,并且拥有更改发生的时间和方式的审计跟踪。
有关如何使用 Docker Scout 自动更新基础镜像的更多信息,请参阅修正(Remediation)。
在 CI 中构建和测试您的镜像
当您将更改提交到源代码控制或创建拉取请求时,请使用GitHub Actions或其他 CI/CD 流水线来自动构建并标记 Docker 镜像,然后对其进行测试。
Dockerfile 指令
遵循这些建议,了解如何正确使用Dockerfile 指令来创建高效且易于维护的 Dockerfile。
提示
想要在 VS Code 中获得更好的 Dockerfile 编辑体验?请查看Docker VS Code 扩展(Beta 版),它提供了代码检查、代码导航和漏洞扫描功能。
FROM
尽可能使用最新的官方镜像作为您的镜像基础。Docker 推荐Alpine 镜像,因为它受到严格控制,体积小(目前小于 6 MB),同时仍然是一个完整的 Linux 发行版。
有关 FROM
指令的更多信息,请参阅Dockerfile 参考中的 FROM 指令。
LABEL
您可以向镜像添加标签,以按项目组织镜像、记录许可信息、辅助自动化或用于其他目的。对于每个标签,添加一个以 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
将长或复杂的 RUN
语句拆分成多行,用反斜杠分隔,使您的 Dockerfile 更易读、易懂和易维护。
例如,您可以使用 &&
运算符链接命令,并使用转义字符将长命令分解成多行。
RUN apt-get update && apt-get install -y --no-install-recommends \
package-bar \
package-baz \
package-foo
默认情况下,反斜杠会转义换行符,但您可以使用escape
指令来更改它。
您还可以使用 here-document 来运行多个命令,而无需使用管道运算符链接它们
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 update
与 apt-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
,您的构建可能会获得过时的 curl
和 nginx
软件包版本。
使用 RUN apt-get update && apt-get install -y --no-install-recommends
可确保您的 Dockerfile 安装最新的软件包版本,而无需进一步编码或手动干预。这种技术被称为缓存 busting(cache busting)。您也可以通过指定软件包版本来实现缓存 busting。这被称为版本锁定(version pinning)。例如
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
的缓存失效(cache bust),并确保安装新版本。将软件包列在每一行上还可以防止软件包重复的错误。
此外,通过删除 /var/lib/apt/lists
清理 apt 缓存会减小镜像大小,因为 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 等情况下,可以考虑使用RUN
的 exec 形式来显式选择支持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
很少应与ENTRYPOINT
结合使用 CMD ["param", "param"]
这种方式,除非您和您的预期用户都非常熟悉 ENTRYPOINT
的工作方式。
有关 CMD
的更多信息,请参阅Dockerfile 参考中的 CMD 指令。
EXPOSE
EXPOSE
指令指示容器监听连接的端口。因此,您应该为应用程序使用通用、传统的端口。例如,包含 Apache Web 服务器的镜像会使用 EXPOSE 80
,而包含 MongoDB 的镜像会使用 EXPOSE 27017
等等。
对于外部访问,您的用户可以执行带有标志的 docker run
命令,指示如何将指定端口映射到他们选择的端口。对于容器链接(container linking),Docker 为从接收容器到源容器的路径提供了环境变量(例如,MYSQL_PORT_3306_TCP
)。
有关 EXPOSE
的更多信息,请参阅Dockerfile 参考中的 EXPOSE 指令。
ENV
为了让新软件更易于运行,您可以使用 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
为了防止这种情况并真正取消设置环境变量,请使用一个 RUN
命令,其中包含 shell 命令,以便在一个层中设置、使用和取消设置变量。您可以使用 ;
或 &&
分隔命令。如果您使用后一种方法,并且其中一个命令失败,则 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 指令。
ADD 或 COPY
ADD
和 COPY
功能相似。COPY
支持将文件从构建上下文或多阶段构建中的一个阶段复制到容器中。ADD
支持从远程 HTTPS 和 Git URL 获取文件,以及在从构建上下文添加文件时自动解压 tar 文件。
在多阶段构建中,您主要会使用 COPY
将文件从一个阶段复制到另一个阶段。如果您需要暂时将文件从构建上下文添加到容器中以执行 RUN
指令,通常可以用绑定挂载(bind mount)代替 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
。
当您需要在构建过程中下载远程工件(artifact)时,ADD
指令是最佳选择。ADD
比手动使用 wget
和 tar
等工具添加文件更好,因为它能确保更精确的构建缓存。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
有关 ADD
或 COPY
的更多信息,请参阅以下内容
ENTRYPOINT
ENTRYPOINT
的最佳用途是设置镜像的主命令,允许该镜像像该命令一样运行,然后使用 CMD
作为默认标志。
以下是命令行工具 s3cmd
的镜像示例
ENTRYPOINT ["s3cmd"]
CMD ["--help"]
您可以使用以下命令运行镜像并显示命令的帮助信息
$ docker run s3cmd
或者,您可以使用正确的参数来执行命令,如下例所示
$ docker run s3cmd ls s3://mybucket
这非常有用,因为镜像名称可以作为二进制文件的引用,如上述命令所示。
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 "$@"
此脚本使用 Bash 的 exec
命令,以便最终运行的应用程序成为容器的 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
您应该使用 VOLUME
指令来暴露任何数据库存储区域、配置存储或由 Docker 容器创建的文件和文件夹。强烈建议您将 VOLUME
用于镜像中任何可变或用户可维护的部分的组合。
有关 VOLUME
的更多信息,请参阅Dockerfile 参考中的 VOLUME 指令。
USER
如果服务无需特权即可运行,请使用 USER
切换到非 root 用户。首先在 Dockerfile 中创建用户和组,如下例所示
RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres
注意
考虑使用显式的 UID/GID。
镜像中的用户和组被分配了非确定性的 UID/GID,这意味着分配的是“下一个” UID/GID,无论镜像是否重建。因此,如果 UID/GID 至关重要,您应该分配一个显式的 UID/GID。
注意
由于 Go archive/tar 包处理稀疏文件时存在一个未解决的 bug,在 Docker 容器内尝试创建一个具有非常大 UID 的用户可能会导致磁盘耗尽,因为容器层中的
/var/log/faillog
会被 NULL (\0) 字符填满。一个解决方法是向 useradd 传递--no-log-init
标志。Debian/Ubuntu 的adduser
包装器不支持此标志。
避免安装或使用 sudo
,因为它具有不可预测的 TTY 和信号转发行为,可能导致问题。如果您绝对需要类似 sudo
的功能,例如以 root
身份初始化守护进程但以非 root
身份运行它,请考虑使用“gosu”。
最后,为了减少层和复杂性,避免频繁地来回切换 USER
。
有关 USER
的更多信息,请参阅Dockerfile 参考中的 USER 指令。
WORKDIR
为了清晰度和可靠性,您应该始终对 WORKDIR
使用绝对路径。此外,您应该使用 WORKDIR
,而不是像 RUN cd … && do-something
这样难以阅读、排查和维护的重复指令。
有关 WORKDIR
的更多信息,请参阅Dockerfile 参考中的 WORKDIR 指令。
ONBUILD
ONBUILD
命令在当前 Dockerfile 构建完成后执行。ONBUILD
会在任何从当前镜像派生(FROM
)的子镜像中执行。可以将 ONBUILD
命令视为父 Dockerfile 给子 Dockerfile 的指令。
Docker 构建会在子 Dockerfile 中的任何命令之前执行 ONBUILD
命令。
ONBUILD
对于将从给定镜像构建的镜像很有用。例如,您会将 ONBUILD
用于语言栈镜像,该镜像会在 Dockerfile 内构建用该语言编写的任意用户软件,如Ruby 的 ONBUILD
变体所示。
使用 ONBUILD
构建的镜像应该获得单独的标签。例如,ruby:1.9-onbuild
或 ruby:2.0-onbuild
。
在 ONBUILD
中放置 ADD
或 COPY
时要小心。如果新构建的上下文中缺少要添加的资源,onbuild 镜像会发生灾难性失败。如上文建议的那样添加单独的标签有助于缓解此问题,让 Dockerfile 作者可以做出选择。
有关 ONBUILD
的更多信息,请参阅Dockerfile 参考中的 ONBUILD 指令。