编写 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,以了解以这种无状态方式运行容器的动机。
理解构建上下文
关更多信息,请参见 构建上下文。
通过 stdin 管道传递 Dockerfile
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 构建镜像,而无需发送构建上下文
此语法使用来自 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 构建镜像,而不发送构建上下文,则如果您使用ADD或COPY,则构建将失败。以下示例说明了这一点:
# 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
使用来自 stdin 的 Dockerfile 从本地上下文构建
此语法可以使用本地文件系统上的文件构建镜像,但是 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
使用来自 stdin 的 Dockerfile 从远程上下文构建
此语法可以使用来自远程 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 排除
要排除与构建无关的文件,而不重组您的源存储库,请使用.dockerignore文件。
该文件支持类似于.gitignore文件的排除模式。有关创建一个的信息,请参阅 .dockerignore 文件 。
使用多阶段构建
多阶段构建可以使您大大减少最终镜像的大小,而无需努力减少中间层和文件的数量。
由于镜像是在构建过程的最后阶段构建的,因此您可以通过 利用构建缓存 来最大程度地减少镜像层。
例如,如果构建包含多个层,并且希望确保构建缓存可重用,则可以将更改频率较低的层放在频繁更改的层之前。下表是指令顺序的一个示例:
- 安装被构建应用程序的依赖工具
- 安装或更新依赖库
- 生成您的应用程序
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 中,最大限度地减少镜像中的层数以确保它们的性能非常重要。 添加了以下功能来减少此限制:
- 只有指令
RUN、COPY、ADD才能创建层。 其他指令创建临时中间镜像,并且不会增加构建的大小。 - 如果可能,请使用 多阶段构建 ,并且仅将所需的
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 中的指令与子镜像之一进行比较就足够了。然而,某些指令需要更多的检查和解释。
- 对于
ADD和COPY指令,将检查镜像中每个文件的内容并计算每个文件的校验和。 这些校验和中不考虑每个文件的上次修改时间和上次访问时间。 在缓存查找期间,会将校验和与现有镜像中的校验和进行比较。 如果任何文件中发生任何更改,例如内容和元数据,则缓存将失效。 - 除了
ADD和COPY指令之外,缓存检查不会查看容器中的文件来确定缓存匹配。 例如,在处理RUN apt-get -y update命令时,不会检查容器中更新的文件以确定是否存在缓存命中。 在这种情况下,仅使用命令字符串本身来查找匹配项。
一旦缓存失效,所有后续的 Dockerfile 命令都会生成新的镜像,并且缓存不会被使用。
Dockerfile 指令
这些建议旨在帮助您创建有效且可维护的 Dockerfile。
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"
有关可接受的标签键和值的指南,请参阅 了解对象标签。 有关查询标签的信息,请参阅 管理对象标签 中与过滤相关的条目。
有关LABEL的更多信息,请参阅 Dockerfile 参考中的 LABEL 指令
RUN
将长或复杂的RUN语句拆分为多行,并用反斜杠分隔,以使 Dockerfile 更具可读性、易于理解和可维护。
有关RUN的更多信息,请参阅 Dockerfile 参考中的 RUN 指令
apt-get
RUN最常见的用例可能是apt-get的应用程序。
由于RUN apt-get命令会安装软件包,因此需要注意一些违反直觉的行为。
始终将RUN apt-get update与apt-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更新未运行,因此您的构建可能会获得过时版本的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缓存失效并确保安装新版本。
在每行列出包还可以防止出现包重复的错误。
此外,当您通过删除/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 pipelinefail &&以确保意外错误阻止构建意外成功。例如:
RUN set -o pipefail && wget -O - https://some.site | wc -l > /number
并非所有的 shell 都支持-o pipefall选项。
对于基于 Debian 的镜像上的dashshell 等情况,请考虑使用RUN的 exec 形式来显式选择支持pipelinefail选项的 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
EXPOSE指令指示容器侦听连接的端口。 因此,您应该为您的应用程序使用通用的传统端口。
例如,包含 Apache Web 服务器的镜像将使用EXPOSE 80,而包含 MongoDB 的镜像将使用EXPOSE 27017等等。
对于外部访问,您的用户可以使用参数将指定端口映射到他们所选择的端口在执行docker run的时候。
对于容器链接,Docker 提供了从接收容器返回源路径的环境变量(eg: 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
# 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 指令。
ADD 或 COPY
虽然ADD和COPY在功能上相似,但一般来说,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 获取包; 你应该使用curl或wget代替。
这样,您可以在提取文件后删除不再需要的文件,并且无需在镜像中添加其他图层。 例如,您应该避免做以下事情:
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
对于其他不需要ADD的 tar 自动解压功能的项目,例如文件和目录,您应该始终使用COPY。
有关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 "$@"
此脚本使用 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
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。
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使用绝对路径。 另外,您应该使用WORKDIR,而不是像RUN cd … && do-something这样的大量指令,这些指令难以阅读、故障排除和维护。
有关WORKDIR的更多信息,请参阅 Dockerfile 参考中的 WORKDIR 指令
ONBUILD
ONBUILD命令在当前 Dockerfile 构建完成后执行。ONBUILD在从当前镜像派生的任何子镜像中执行。 将ONBUILD命令视为父 Dockerfile 向子 Dockerfile 发出的指令。
Docker 构建在子 Dockerfile 中的任何命令之前执行ONBUILD命令。
ONBUILD对于将从给定镜像构建的镜像很有用。
例如,您可以将ONBUILD用于语言堆栈镜像,该镜像在 Dockerfile 中构建以该语言编写的任意用户软件,如 Ruby 的 ONBUILD 变体 中所示。
使用ONBUILD构建的镜像应该有一个单独的标签。 例如,ruby:1.9-onbuild 或 ruby:2.0-onbuild 。
将ADD或COPY放入ONBUILD时要小心。
如果新构建的上下文缺少正在添加的资源,则ONBUILD镜像会发生灾难性的失败。
按照上面的建议添加单独的标签,可以让 Dockerfile 作者做出选择,从而有助于缓解这种情况。
有关ONBUILD的更多信息,请参阅 Dockerfile 参考中的 ONBUILD 指令
Docker 官方镜像的示例
这些官方镜像具有模范的 Dockerfiles: