编写 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 的镜像上的dash
shell 等情况,请考虑使用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: