Dockerfile 指令的最佳实践
这些建议旨在帮助您创建高效且可维护的 Dockerfile。
只要有可能,请使用当前的官方图像作为图像的基础。 Docker 推荐使用 ,因为它受到严格控制且尺寸较小(目前低于 6 MB),同时仍然是一个完整的 Linux 发行版。
有关该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 参考中的
将长或复杂的RUN语句拆分为多行,并用反斜杠分隔,以使 Dockerfile 更易于阅读、理解和维护。
更多信息RUN,请参阅 Dockerfile 参考。
最常见的用例可能RUN是apt-get.由于该RUN apt-get命令会安装软件包,因此需要注意一些违反直觉的行为。
始终在同一个语句中结合RUN apt-get update使用 。例如:apt-get installRUN
RUN apt-get update && apt-get install -y \ package-bar \ package-baz \ package-foo \ && rm -rf /var/lib/apt/lists/*
apt-get update在语句中单独使用RUN会导致缓存问题和后续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 update未运行,您的构建可能会获得过时版本的curl和 nginx软件包。
使用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并确保安装新版本。在每行列出包还可以防止包重复中的错误。
此外,当您通过删除 apt 缓存来清理/var/lib/apt/lists它时,会减小图像大小,因为 apt 缓存不存储在图层中。由于该 RUN语句以 开头apt-get update,因此包缓存总是在 之前刷新apt-get install。
官方 Debian 和 Ubuntu 镜像 ,因此不需要显式调用。
某些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选项。
dash对于基于 Debian 的映像上的 shell等情况,请考虑使用exec形式RUN来显式选择支持该选项的 shellpipefail。例如:
RUN ["/bin/bash", "-c", "set -o pipefail && wget -O - https://some.site | wc -l > /number"]
该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
更多信息CMD,请参阅 Dockerfile 参考。
该EXPOSE指令指示容器侦听连接的端口。因此,您应该为您的应用程序使用通用的传统端口。例如,包含 Apache Web 服务器的映像将使用EXPOSE 80,而包含 MongoDB 的映像将使用EXPOSE 27017等等。
对于外部访问,您的用户可以docker run使用指示如何将指定端口映射到他们选择的端口的标志来执行。对于容器链接,Docker 提供了从接收容器到源容器的路径的环境变量(例如,MYSQL_PORT_3306_TCP)。
更多信息EXPOSE,请参阅 Dockerfile 参考。
为了使新软件更易于运行,您可以用来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 参考。
ADD并且COPY功能相似。支持从 中的阶段 COPY将文件基本复制到容器中 。 支持从远程 HTTPS 和 Git URL 获取文件的功能,以及从构建上下文添加文件时自动提取 tar 文件的功能。ADD
您最想用于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当您需要下载远程工件作为构建的一部分时,该说明最适合。比使用和ADD等手动添加文件更好,因为它可以确保更精确的构建缓存。 还内置了对远程资源校验和验证的支持,以及用于解析 中的分支、标签和子目录的协议。wgettarADD
以下示例用于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是设置图像的主命令,允许该图像像该命令一样运行,然后用作CMD默认标志。
以下是命令行工具的图像示例s3cmd:
ENTRYPOINT ["s3cmd"] CMD ["--help"]
您可以使用以下命令来运行映像并显示命令的帮助:
$ docker run s3cmd
或者,您可以使用正确的参数来执行命令,如下例所示:
$ docker run s3cmd ls s3://mybucket
这很有用,因为图像名称可以兼作对二进制文件的引用,如上面的命令所示。
该ENTRYPOINT指令还可以与帮助程序脚本结合使用,使其以与上述命令类似的方式运行,即使启动该工具可能需要多个步骤。
例如, 使用以下脚本作为其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 "$@"
该脚本使用 ,以便最终运行的应用程序成为容器的 PID 1。这允许应用程序接收发送到容器的任何 Unix 信号。有关详细信息,请参阅 。
在以下示例中,帮助程序脚本被复制到容器中并通过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 参考。
您应该使用该VOLUME指令公开 Docker 容器创建的任何数据库存储区域、配置存储或文件和文件夹。强烈建议您使用VOLUME图像的可变或用户可服务部分的任意组合。
更多信息VOLUME,请参阅 Dockerfile 参考。
如果服务可以在没有权限的情况下运行,请使用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/Ubuntuadduser包装器不支持此标志。
避免安装或使用,sudo因为它具有不可预测的 TTY 和信号转发行为,可能会导致问题。如果您绝对需要类似于的功能sudo,例如将守护进程初始化为非root守护进程root,请考虑使用 。
最后,为了减少层次和复杂性,避免USER频繁来回切换。
更多信息USER,请参阅 Dockerfile 参考。
为了清晰和可靠,您应该始终为您的 WORKDIR.另外,您应该使用WORKDIR而不是使用大量的指令RUN cd … && do-something,例如难以阅读、排除故障和维护的指令。
更多信息WORKDIR,请参阅 Dockerfile 参考。
ONBUILD当前 Dockerfile 构建完成后执行命令 。在派生当前图像ONBUILD的任何子图像中执行。FROM将该ONBUILD命令视为父 Dockerfile 向子 Dockerfile 发出的指令。
Docker 构建ONBUILD在子 Dockerfile 中的任何命令之前执行命令。
ONBUILD对于要构建FROM给定图像的图像很有用。例如,您可以使用ONBUILD语言堆栈映像来构建在 Dockerfile 中以该语言编写的任意用户软件,正如您在 中看到的那样。
使用构建的图像ONBUILD应该有一个单独的标签。例如, ruby:1.9-onbuild或ruby:2.0-onbuild。
ADD放入或COPY放入时要小心ONBUILD。如果新构建的上下文缺少正在添加的资源,则 onbuild 映像会发生灾难性的失败。按照上面的建议添加单独的标签,可以让 Dockerfile 作者做出选择,从而有助于缓解这种情况。
更多信息ONBUILD,请参阅 Dockerfile 参考。
