编写 Dockerfile 的最佳实践


该主题涵盖了推荐的最佳实践和构建有效镜像的方法。

Docker 通过从 Dockerfile 读取指令来自动构建镜像 —— Dockerfile 是一个文本文件,其中包含构建给定镜像所需的所有命令。 Dockerfile 遵循特定的格式和指令集,您可以在 Dockerfile 参考中找到。

Docker 镜像由只读层组成,每一层代表一条 Dockerfile 指令。这些层是堆叠的,每一层都是前一层更改的三角洲。下面是示例 Dockerfile 的内容:

# syntax=docker/dockerfile:1
FROM ubuntu:22.04
COPY . /app
RUN make /app
CMD python /app/app.py

每个指令都会创建一层:

  • FROM 指令从 Docker 镜像ubuntu:22.04创建一个图层。
  • COPY 指令从 Docker 客户端的当前目录中添加文件。
  • RUN 指令运行 make 构建您的应用程序。
  • CMD 指定要在容器中运行的命令。

当您运行镜像并生成容器时,您会在基础层的顶部添加一个新的可写层(也称为容器层)。对正在运行的容器所做的所有更改(如写入新文件、修改现有文件和删除文件)都写入到此可写容器层。

有关镜像层的更多信息以及 Docker 如何构建和存储镜像,请参见有关存储驱动程序的信息。

Dockerfile 定义的镜像应该生成尽可能短暂的容器。临时性意味着可以停止和销毁容器,然后重建容器并用绝对最小的改动和配置替换容器。

请参阅 《十二个因素应用方法》下的 Process,以了解以这种无状态方式运行容器的动机。

关更多信息,请参见 构建上下文

Docker 构建镜像有能力通过 stdin 管道构接收 Dockerfile,因此支持本地或远程构建上下文。通过 stdin 管道传输 Dockerfile 对于执行一次性构建非常有用,无需将 Dockerfile 写入磁盘,或者在生成 Dockerfile 的情况下,不应该在之后持久化。

注意

以下部分中的示例使用这里的 documents 以方便使用,但是可以使用任何方法在 stdin 上提供 Dockerfile。

例如,以下命令是等效的:

echo -e 'FROM busybox\nRUN echo "hello world"' | docker build -
docker build -<<EOF
FROM busybox
RUN echo "hello world"
EOF

您可以用自己喜欢的方法或最适合用例的方法代替示例。

此语法使用来自 stdin 的 Dockerfile 来构建镜像,而无需发送额外文件作为构建上下文。 连字符( -)处于PATH的位置,并指示 Docker 读取仅包含 dockerfile 的构建上下文,从 stdin 而不是目录:

docker build [OPTIONS] -

下面的示例使用通过 stdin 传递的 Dockerfile 生成镜像。不会将任何文件作为构建上下文发送到守护进程。

docker build -t myimage:latest -<<EOF
FROM busybox
RUN echo "hello world"
EOF

在 Dockerfile 不需要将文件复制到镜像中的情况下,省略构建上下文可能很有用,并且可以提高构建速度,因为不会向守护进程发送任何文件。 如果要通过排除构建上下文中的某些文件来提高构建速度,请参考以 .dockerignore的排除

注意

如果您尝试使用来自 stdin 的 Dockerfile 构建镜像,而不发送构建上下文,则如果您使用ADDCOPY,则构建将失败。以下示例说明了这一点:

# create a directory to work in and example file
mkdir example
cd example
touch somefile.txt

构建:

docker build -t myimage:latest -<<EOF
FROM busybox
COPY somefile.txt ./
RUN cat /somefile.txt
EOF

报错如下:

...
Step 2/3 : COPY somefile.txt ./
COPY failed: stat /var/lib/docker/tmp/docker-builder249218248/somefile.txt: no such file or directory

此语法可以使用本地文件系统上的文件构建镜像,但是 Dockerfile 来自 stdin 。语法使用-f (或--file)选项来指定要使用的 Dockerfile,并使用连字符(-)作为文件名来指示 Docker 从 stdin 读取 Dockerfile:

docker build [OPTIONS] -f- PATH

下面的例子使用了工作目录(.)作为构建上下文,并使用 Dockerfile 构建镜像,Dockerfile 通过 stdin 传递使用 这里的文档

# create a directory to work in
mkdir example
cd example

# create an example file
touch somefile.txt

# build an image using the current directory as context, and a Dockerfile passed through stdin
docker build -t myimage:latest -f- . <<EOF
FROM busybox
COPY somefile.txt ./
RUN cat /somefile.txt
EOF

此语法可以使用来自远程 Git 存储库的文件,通过来自 stdin 传递的 Dockerfile 来构建镜像。 语法使用-f (或--file)选项来指定要使用的 Dockerfile,使用连字符(-)作为文件名来指示 Docker 从 stdin 读取 Dockerfile:

docker build [OPTIONS] -f- PATH

如果希望从不包含 Dockerfile 的存储库构建镜像,或者希望使用自定义 Dockerfile 构建镜像,而不需要维护自己的存储库分支,那么这种语法可能非常有用。

下面的示例使用 stdin 传递的 Dockerfile 构建一个镜像,并从 GitHub 上的 hello-world 存储库添加 hello.c 文件。

docker build -t myimage:latest -f- https://github.com/docker-library/hello-world.git <<EOF
FROM busybox
COPY hello.c ./
EOF
注意:当使用远程 Git 存储库作为上下文构建镜像时,Docker 在本地机器上执行存储库的git clone,并将这些文件作为构建上下文发送给守护进程。 这个特性要求您在运行docker build命令的主机上安装 Git

要排除与构建无关的文件,而不重组您的源存储库,请使用.dockerignore文件。 该文件支持类似于.gitignore文件的排除模式。有关创建一个的信息,请参阅 .dockerignore 文件

多阶段构建可以使您大大减少最终镜像的大小,而无需努力减少中间层和文件的数量。

由于镜像是在构建过程的最后阶段构建的,因此您可以通过 利用构建缓存 来最大程度地减少镜像层。

例如,如果构建包含多个层,并且希望确保构建缓存可重用,则可以将更改频率较低的层放在频繁更改的层之前。下表是指令顺序的一个示例:

  1. 安装被构建应用程序的依赖工具
  2. 安装或更新依赖库
  3. 生成您的应用程序

Go 应用程序的 Dockerfile 可能类似于:

# syntax=docker/dockerfile:1
FROM golang:1.20-alpine AS build

# Install tools required for project
# Run `docker build --no-cache .` to update dependencies
RUN apk add --no-cache git

# List project dependencies with go.mod and go.sum
# These layers are only re-built when Gopkg files are updated
WORKDIR /go/src/project/
COPY go.mod go.sum /go/src/project/
# Install library dependencies
RUN go mod download

# Copy the entire project and build it
# This layer is rebuilt when a file changes in the project directory
COPY . /go/src/project/
RUN go build -o /bin/project

# This results in a single layer image
FROM scratch
COPY --from=build /bin/project /bin/project
ENTRYPOINT ["/bin/project"]
CMD ["--help"]

避免仅仅因为拥有它们可能会更好而安装额外或不必要的软件包。 例如,您不需要在数据库镜像中包含文本编辑器。

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

每个容器应该只有一个关注点。 将应用程序解耦到多个容器中可以更轻松地水平扩展和重用容器。 例如,Web 应用程序堆栈可能由三个独立的容器组成,每个容器都有自己独特的镜像,以解耦的方式管理 Web 应用程序、数据库和内存缓存。

将每个容器限制为一个进程是一个很好的经验法则,但这并不是一个硬性规定。 例如,不仅可以 使用 init 进程生成容器,某些程序还可能自行生成其他进程。 例如,Celery 可以生成多个工作进程,而 Apache 可以为每个请求创建一个进程。

运用您的最佳判断力,尽可能保持容器清洁和模块化。 如果容器之间相互依赖,可以使用 Docker 容器网络来确保这些容器可以通信。

在旧版本的 Docker 中,最大限度地减少镜像中的层数以确保它们的性能非常重要。 添加了以下功能来减少此限制:

  • 只有指令 RUNCOPYADD才能创建层。 其他指令创建临时中间镜像,并且不会增加构建的大小。
  • 如果可能,请使用 多阶段构建 ,并且仅将所需的 artifacts 复制到最终镜像中。 这使您可以在中间构建阶段包含工具和调试信息,而无需增加最终镜像的大小。

只要有可能,请按字母序对多行参数进行排序,以便于维护。 这有助于避免包重复并使列表更容易更新。 这也使得 PR 更容易阅读和审查。 使用反斜杠 (\) 续行符大有帮助。

下面是来自 buildpack-deps image 的例子:

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

构建镜像时,Docker 将逐步执行 Dockerfile 中的指令,并按照指定的顺序执行每个指令。检查每条指令时,Docker 会在其缓存中查找现有镜像,而不是创建新的重复镜像。

如果您根本不想使用缓存,可以在docker build命令上使用--no-cache=true选项。 但是,如果您确实让 Docker 使用其缓存,那么了解它何时能够以及何时不能找到匹配的镜像非常重要。 Docker 遵循的基本规则概述如下:

  • 从缓存中已经存在的父镜像开始,将下一个指令与从该基本镜像中派生的所有子镜像进行比较,以查看其中一个是不是使用完全相同的指令构建的。如果不是,则缓存无效。
  • 在大多数情况下,只需将 Dockerfile 中的指令与子镜像之一进行比较就足够了。然而,某些指令需要更多的检查和解释。
  • 对于ADDCOPY指令,将检查镜像中每个文件的内容并计算每个文件的校验和。 这些校验和中不考虑每个文件的上次修改时间和上次访问时间。 在缓存查找期间,会将校验和与现有镜像中的校验和进行比较。 如果任何文件中发生任何更改,例如内容和元数据,则缓存将失效。
  • 除了ADDCOPY指令之外,缓存检查不会查看容器中的文件来确定缓存匹配。 例如,在处理RUN apt-get -y update命令时,不会检查容器中更新的文件以确定是否存在缓存命中。 在这种情况下,仅使用命令字符串本身来查找匹配项。

一旦缓存失效,所有后续的 Dockerfile 命令都会生成新的镜像,并且缓存不会被使用。

这些建议旨在帮助您创建有效且可维护的 Dockerfile。

只要有可能,请使用当前的官方镜像作为镜像的基础。 Docker 推荐使用 Alpine 镜像,因为它受到严格控制且尺寸较小(目前小于 6 MB),同时仍然是一个完整的 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"

有关可接受的标签键和值的指南,请参阅 了解对象标签。 有关查询标签的信息,请参阅 管理对象标签 中与过滤相关的条目。

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

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

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

RUN最常见的用例可能是apt-get的应用程序。 由于RUN apt-get命令会安装软件包,因此需要注意一些违反直觉的行为。

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

# Set multiple labels at once, using line-continuation characters to break long lines
RUN apt-get update && apt-get install -y \
    package-bar \
    package-baz \
    package-foo  \
    && rm -rf /var/lib/apt/lists/*

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 curl

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

# syntax=docker/dockerfile:1

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

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

使用RUN apt-get update && apt-get install -y确保您的 Dockerfile 安装最新的软件包版本,无需进一步编码或手动干预。 这种技术称为缓存清除。 您还可以通过指定包版本来实现缓存清除。 这称为版本固定。 例如:

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

版本固定强制构建检索特定版本,无论缓存中有什么。此技术还可以减少由于所需包的意外更改而导致的故障。

下面是一个格式良好的RUN指令,它演示了所有apt-get建议。

RUN apt-get update && apt-get install -y \
    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缓存失效并确保安装新版本。 在每行列出包还可以防止出现包重复的错误。

此外,当您通过删除/var/lib/apt/lists来清理 apt 缓存时,它会减小镜像大小,因为 apt 缓存不存储在层中。 由于RUN语句以apt-get update开头,因此包缓存始终在apt-get install之前刷新。

官方 DebianUbuntu 镜像会 自动运行 apt-get clean,因此不需要显式调用。

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

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

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

如果您希望命令由于管道中任何阶段的错误而失败,请在前面添加set -o pipelinefail &&以确保意外错误阻止构建意外成功。例如:

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

注意

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

对于基于 Debian 的镜像上的dashshell 等情况,请考虑使用RUNexec 形式来显式选择支持pipelinefail选项的 shell。例如:

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

CMD指令应与任何参数一起用于运行镜像中包含的软件。CMD几乎总是以CMD ["executable", "param1", "param2"]的形式使用。 因此,如果镜像用于服务,例如 ApacheRails,您将运行CMD ["apache2","-DFOREGROUND"]之类的命令。 事实上,对于任何基于服务的镜像,都建议采用这种形式的指令。

在大多数其他情况下,应该为CMD提供交互式 shell,例如 bashpythonperl。 例如,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 提供了从接收容器返回源路径的环境变量(eg: 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

# syntax=docker/dockerfile:1
$ docker run --rm test sh -c 'echo $ADMIN_USER'

mark

为了防止这种情况发生,并真正取消设置环境变量,请使用RUN命令和 shell 命令,在单个层中设置、使用和取消设置变量。 您可以使用;分隔命令。 或者&&。 如果使用第二种方法,并且其中一个命令失败,则 docker 构建也会失败。 这通常是个好建议。 使用\作为 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更透明。COPY仅支持将本地文件基本复制到容器中,而ADD有一些功能(例如仅本地 tar 提取和远程 URL 支持)并不立即显而易见。 因此,ADD的最佳用途是将本地tar文件自动提取到镜像中,如ADD rootfs.tar.xz /中。

如果您的 Dockerfile 有多个步骤使用上下文中的不同文件,请单独复制它们,而不是一次全部复制。 如果特定所需的文件发生更改,则这可确保仅该步骤的构建缓存无效,从而强制仅再次运行该步骤。

例如:

COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
COPY . /tmp/

与将COPY . /tmp/放在RUN步骤之前相比,会导致更少的缓存失效。

由于镜像大小很重要,因此强烈建议不要使用ADD从远程 URL 获取包; 你应该使用curlwget代替。 这样,您可以在提取文件后删除不再需要的文件,并且无需在镜像中添加其他图层。 例如,您应该避免做以下事情:

ADD https://example.com/big.tar.xz /usr/src/things/
RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
RUN make -C /usr/src/things all

取而代之的是:

RUN mkdir -p /usr/src/things \
    && curl -SL https://example.com/big.tar.xz \
    | tar -xJC /usr/src/things \
    && make -C /usr/src/things all

对于其他不需要ADDtar 自动解压功能的项目,例如文件和目录,您应该始终使用COPY

有关ADDCOPY的详细信息,请参阅以下内容:

  • Dockerfile 参考中的 ADD
  • Dockerfile 参考中的 COPY

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 "$@"

此脚本使用 exec Bash 命令,以便最终运行的应用程序成为容器的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 包处理稀疏文件时存在 未解决的 bug,尝试在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,而不是像RUN cd … && do-something这样的大量指令,这些指令难以阅读、故障排除和维护。

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

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

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

ONBUILD对于将从给定镜像构建的镜像很有用。 例如,您可以将ONBUILD用于语言堆栈镜像,该镜像在 Dockerfile 中构建以该语言编写的任意用户软件,如 Ruby 的 ONBUILD 变体 中所示。

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

ADDCOPY放入ONBUILD时要小心。 如果新构建的上下文缺少正在添加的资源,则ONBUILD镜像会发生灾难性的失败。 按照上面的建议添加单独的标签,可以让 Dockerfile 作者做出选择,从而有助于缓解这种情况。

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

这些官方镜像具有模范的 Dockerfiles: