本文档介绍了用于构建有效镜像的推荐最佳实践和方法。
Docker通过从Dockerfile
文本文件中读取指令来自动构建映像,该 文本文件按顺序包含构建给定映像所需的所有命令。Dockerfile
遵循特定的格式和指令集,您可以在Dockerfile参考中找到。
Docker映像由只读层组成,每个只读层代表一个Dockerfile指令。这些层是堆叠的,每个层都是上一层的变化的增量。考虑一下Dockerfile
:
FROM ubuntu:18.04
COPY . /app
RUN make /app
CMD python /app/app.py
每条指令创建一层:
FROM
从ubuntu:18.04
Docker映像创建一个图层。COPY
从Docker客户端的当前目录添加文件。RUN
使用make
构建您的应用程序。CMD
指定要在容器中运行的命令。
运行图像并生成容器时,可以 在基础层之上添加一个新的可写层(“容器层”)。对运行中的容器所做的所有更改(例如写入新文件,修改现有文件和删除文件)都将写入此可写容器层。
有关图像层的更多信息(以及Docker如何构建和存储图像),请参阅 关于存储驱动程序。
一般准则和建议
创建临时容器
Dockerfile定义的映像应该生成尽可能短暂的容器。“短暂”是指可以停止并销毁容器,然后对其进行重建和替换,并使用绝对的最小限度的设置和配置。
请参阅“十二因子应用程序” 方法下的“ 过程 ” ,以了解以这种无状态方式运行容器的动机。
了解构建上下文
发出docker build
命令时,当前的工作目录称为构建上下文。默认情况下,假定Dockerfile位于此处,但是您可以使用文件标志(-f
)指定其他位置。无论Dockerfile
实际位于何处,当前目录中文件和目录的所有递归内容都将作为构建上下文发送到Docker守护程序。
构建上下文示例
为构建上下文创建一个目录并
cd
进入该目录。将“ hello”写入名为的hello
文本文件,然后创建一个Dockerfile在其上运行cat
。从构建上下文(.
)中构建图像:mkdir myproject && cd myproject echo "hello" > hello echo -e "FROM busybox\nCOPY /hello /\nRUN cat /hello" > Dockerfile docker build -t helloapp:v1 .
移动
Dockerfile
和hello到
单独的目录,并构建映像的第二个版本(不依赖于上次构建的缓存)。使用-f
以指向Dockerfile并指定构建上下文的目录:mkdir -p dockerfiles context mv Dockerfile dockerfiles && mv hello context docker build --no-cache -t helloapp:v2 -f dockerfiles/Dockerfile context
疏忽地包含了构建映像所不需要的文件会导致较大的构建上下文和较大的映像大小。这会增加生成图像的时间,拉动和推动图像的时间以及容器运行时的大小。要了解您的构建上下文有多大,请在构建您的时查找如下消息Dockerfile
:
Sending build context to Docker daemon 187.8MB
通过stdin
管道传递Dockerfile
Docker可以通过使用本地或远程构建上下文的stdin传递Dockerfile来构建映像。
通过stdin插入Dockerfile可以在不将Dockerfile写入磁盘的情况下执行一次性构建,或者在生成Dockerfile且以后不应持续的情况下使用。
为了方便起见,本节中的示例使用此处的文档,但是可以使用提供
Dockerfile
on的任何方法stdin
。例如,以下命令是等效的:
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从stdin
(仅包含Dockerfile
)而不是目录中读取构建上下文:
docker build [OPTIONS] -
以下示例使用stdin
传递的来Dockerfile
构建图像。没有文件作为构建上下文发送到守护程序。
docker build -t myimage:latest -<<EOF
FROM busybox
RUN echo "hello world"
EOF
在Dockerfile
不需要将文件复制到映像中的情况下,省略构建上下文会很有用,并且由于没有文件发送到守护程序,因此可以提高构建速度。
如果要通过从构建上下文中排除某些文件来提高构建速度,请使用.dockerignore进行排除。
注意:如果使用此语法,尝试构建使用COPY或ADD的Dockerfile将会失败。以下示例说明了这一点:
# create a directory to work in mkdir example cd example # create an example file touch somefile.txt docker build -t myimage:latest -<<EOF FROM busybox COPY somefile.txt . RUN cat /somefile.txt EOF # observe that the build fails ... Step 2/3 : COPY somefile.txt . COPY failed: stat /var/lib/docker/tmp/docker-builder249218248/somefile.txt: no such file or directory
使用STDIN的DOCKERFILE从本地构建上下文构建
使用此语法可使用本地文件系统上的文件,但使用来自stdin的Dockerfile构建映像。该语法使用-f(或--file)选项指定要使用的Dockerfile,并使用连字符(-)作为文件名来指示Docker从stdin中读取Dockerfile:
docker build [OPTIONS] -f- PATH
下面的示例使用当前目录(。)作为构建上下文,并使用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从远程构建上下文进行构建
使用此语法使用来自stdin的Dockerfile,使用来自远程git存储库中的文件来构建映像。该语法使用-f(或--file)选项指定要使用的Dockerfile,并使用连字符(-)作为文件名来指示Docker从stdin中读取Dockerfile:
docker build [OPTIONS] -f- PATH
如果您要从不包含Dockerfile的存储库中构建映像,或者想要使用自定义Dockerfile进行构建而又不维护自己的存储库分支,则此语法很有用。
下面的示例使用stdin中的Dockerfile构建映像,并从GitHub上“ hello-world” Git存储库中的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,并将这些文件作为构建上下文发送到守护程序。此功能要求将git安装在运行docker build命令的主机上。
用.dockerignore排除
要排除与构建无关的文件(无需重组源存储库),请使用.dockerignore
文件。该文件支持类似于.gitignore
文件的排除模式。有关创建一个的信息,请参见 .dockerignore文件。
使用多阶段构建
多阶段构建使您可以大幅度减小最终图像的大小,而不必努力减少中间层和文件的数量。
由于映像是在生成过程的最后阶段生成的,因此您可以利用生成缓存来最小化映像层。
例如,如果您的构建包含多个层,则可以将它们从更改频率较低(以确保生成缓存可重用)到更改频率较高的顺序排序:
-
安装构建应用程序所需的工具
-
安装或更新库依赖项
-
生成您的应用
Go应用程序的Dockerfile可能类似于:
FROM golang:1.11-alpine AS build
# Install tools required for project
# Run `docker build --no-cache .` to update dependencies
RUN apk add --no-cache git
RUN go get github.com/golang/dep/cmd/dep
# List project dependencies with Gopkg.toml and Gopkg.lock
# These layers are only re-built when Gopkg files are updated
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
# Install library dependencies
RUN dep ensure -vendor-only
# 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应用程序,数据库和内存中的缓存。
将每个容器限制为一个进程是一个很好的经验法则,但这并不是一成不变的规则。例如,不仅可以使用初始化进程来生成容器 ,而且某些程序还可以自行生成其他进程。例如,Celery可以产生多个工作进程,而Apache可以为每个请求创建一个进程。
根据您的最佳判断,使容器保持清洁和模块化。如果容器相互依赖,则可以使用Docker容器网络 来确保这些容器可以通信。
减少层数
在较旧的Docker版本中,务必最小化映像中的层数以确保其性能。添加了以下功能来减少此限制:
-
只有
RUN
,COPY
,ADD指令
创建图层。其他指令创建临时的中间映像,并且不会增加构建的大小。 -
如果可能,请使用多阶段构建,并仅将所需的工件复制到最终映像中。这使您可以在中间构建阶段中包含工具和调试信息,而无需增加最终映像的大小。
排序多行参数
尽可能通过字母数字排序多行参数来简化以后的更改。这有助于避免软件包重复,并使列表更易于更新。这也使PR易于阅读和查看。在反斜杠(\
)之前添加空格也有帮助。
下面是来自一个示例buildpack-deps
图像:
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion
利用构建缓存
构建映像时,Docker将逐步执行您的Dockerfile
指令, 并按指定的顺序执行每个指令。在检查每条指令时,Docker会在其缓存中查找可以重用的现有映像,而不是创建新的(重复的)映像。
如果根本不想使用缓存,则可以使用docker build
命令上的--no-cache=true
选项。但是,如果您确实让Docker使用其缓存,那么了解何时可以找到匹配的映像非常重要。Docker遵循的基本规则概述如下:
-
从已在缓存中的父映像开始,将下一条指令与从该基本映像派生的所有子映像进行比较,以查看是否其中一个是使用完全相同的指令构建的。如果不是,则高速缓存无效。
-
在大多数情况下,只需将中
Dockerfile
的指令与子图像之一进行比较就足够了。但是,某些指令需要更多的检查和解释。 -
对于
ADD
和COPY
指令,将检查图像中文件的内容,并为每个文件计算一个校验和。在这些校验和中不考虑文件的最后修改时间和最后访问时间。在高速缓存查找期间,将校验和与现有映像中的校验和进行比较。如果文件中的任何内容(例如内容和元数据)发生了更改,则缓存将无效。 -
除了
ADD
和COPY
命令之外,缓存检查不会查看容器中的文件来确定缓存是否匹配。例如,在处理RUN apt-get -y update
命令时,不检查容器中更新的文件以确定是否存在缓存命中。在这种情况下,仅使用命令字符串本身来查找匹配项。
一旦缓存无效,所有后续Dockerfile
命令都会生成新映像,并且不使用缓存。
Dockerfile说明
这些建议旨在帮助您创建高效且可维护的Dockerfile
。
FROM
尽可能使用当前的官方图像作为图像的基础。我们建议使用Alpine映像,因为它受到严格控制且尺寸较小(当前小于5 MB),同时仍是完整的Linux发行版。
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
更具可读性,可理解性和可维护性。
APT-GET
可能最常见的用例RUN
是的应用apt-get
。因为它安装了软件包,所以该RUN apt-get
命令需要注意一些陷阱。
避免RUN apt-get upgrade
和dist-upgrade
,因为许多从父图像的“基本”套餐的不能内部升级 特权的容器。如果父映像中包含的软件包已过期,请联系其维护者。如果您知道需要更新的特定软件包foo
,请使用 apt-get install -y foo
来自动更新。
始终在同 一条语句中结合RUN apt-get update
使用。例如:apt-get install
RUN
RUN apt-get update && apt-get install -y \
package-bar \
package-baz \
package-foo
apt-get update
在RUN
语句中单独使用会导致缓存问题,随后的apt-get install
指令也会失败。例如,假设您有一个Dockerfile:
FROM ubuntu:18.04
RUN apt-get update
RUN apt-get install -y curl
构建映像后,所有层都在Docker缓存中。假设您以后apt-get install
通过添加额外的程序包进行修改:
FROM ubuntu:18.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
并确保安装新版本。在每一行列出软件包还可以防止软件包重复中的错误。
另外,/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
并非所有的外壳程序都支持该
-o pipefail
选项。在诸如
dash
基于Debian的映像上的shell之类的情况下,请考虑使用exec形式的RUN
显式选择确实支持该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提供交互式外壳,例如bash,python和perl。例如,CMD [“ perl”,“-de0”],CMD [“ python”]或CMD [“ php”,“-a”]。使用这种形式意味着,当您执行诸如docker run -it python之类的操作时,您将进入可用的外壳中,随时可以使用。 除非您和您的预期用户已经非常熟悉ENTRYPOINT的工作原理,否则CMD很少以CMD [“ param”,“ param”]的形式与ENTRYPOINT结合使用。
EXPOSE
EXPOSE指令指示容器在其上侦听连接的端口。因此,应为应用程序使用通用的传统端口。例如,包含Apache Web服务器的映像将使用EXPOSE 80,而包含MongoDB的映像将使用EXPOSE 27017,依此类推。
对于外部访问,您的用户可以执行带有标志的docker run,该标志指示如何将指定端口映射到他们选择的端口。对于容器链接,Docker为从接收者容器到源容器的路径(即,MYSQL_PORT_3306_TCP)提供了环境变量。
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 http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
类似于在程序中具有常量变量(与硬编码值相反),此方法使您可以更改单个ENV指令以自动神奇地修改容器中软件的版本。
每ENV
行都创建一个新的中间层,就像RUN
命令一样。这意味着即使您在以后的层中取消设置环境变量,它也仍将保留在该层中,并且其值也无法转储。您可以通过创建如下所示的Dockerfile,然后对其进行构建来进行测试。
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的Dockerfiles续行符提高可读性。您还可以将所有命令放入外壳脚本中,并让该RUN
命令仅运行该外壳脚本。
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'
ADD或COPY
尽管ADD
和COPY
在功能上相似,但是一般来说COPY
是优选的。那是因为它比ADD
透明。COPY
仅支持将本地文件基本复制到容器中,同时ADD
具有一些功能(例如,本地tar提取和远程URL支持),这些功能并非立即显而易见。因此,最好的用途ADD
是将本地tar文件自动提取到映像中,如中所示ADD rootfs.tar.xz /.
如果您有多个Dockerfile
步骤使用与您的上下文不同的文件,COPY
则将它们单独而不是一次全部使用。这样可以确保仅在特别需要的文件发生更改时,才使每个步骤的构建缓存无效(强制重新运行该步骤)。
例如:
COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
COPY . /tmp/
与在RUN
步骤COPY . /tmp/
之前放置缓存相比,该步骤 导致更少的缓存失效。
由于图像大小很重要,因此强烈建议不要使用ADD
从远程URL获取软件包的方法。您应该使用curl
或wget
代替。这样,您可以在提取文件后删除不再需要的文件,而不必在图像中添加另一层。例如,您应该避免做以下事情:
ADD http://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 http://example.com/big.tar.xz \
| tar -xJC /usr/src/things \
&& make -C /usr/src/things all
对于不需要ADD
自动提取tar功能的其他项目(文件,目录),应始终使用COPY
。
ENTPYPOINT
最好的ENTRYPOINT
用法是设置映像的主命令,从而使该映像像该命令一样运行(然后CMD
用作默认标志)。
让我们从命令行工具的图像示例开始s3cmd
:
ENTRYPOINT ["s3cmd"]
CMD ["--help"]
现在可以像这样运行图像以显示命令的帮助:
$ docker run s3cmd
或使用正确的参数执行命令:
$ docker run s3cmd ls s3://mybucket
这很有用,因为映像名称可以用作对二进制文件的引用,如上面的命令所示。
该ENTRYPOINT
指令还可以与辅助脚本结合使用,即使启动该工具可能需要一个以上的步骤,也可以使其以与上述命令类似的方式起作用。
例如,Postgres Official Image 使用以下脚本作为其脚本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
此脚本使用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
VOLUME
该VOLUME
指令应用于公开由Docker容器创建的任何数据库存储区,配置存储或文件/文件夹。强烈建议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。
由于Go存档/ tar软件包处理稀疏文件中的一个未解决的错误,尝试在Docker容器内创建具有非常大的UID的用户可能会导致磁盘耗尽,因为容器层中的/ var / log / faillog充满了NULL(\ 0)字符。一种解决方法是将--no-log-init标志传递给useradd。Debian / Ubuntu adduser包装器不支持此标志。
避免安装或使用sudo
它具有不可预测的TTY和信号转发行为,这可能会引起问题。如果您绝对需要类似的功能sudo
,例如将守护进程初始化root
为非守护进程,但将其作为非守护进程运行root
,请考虑使用“ gosu”。
最后,为了减少层次和复杂性,请避免USER
频繁地来回切换。
WORKDIR
为了清楚和可靠起见,您应始终为使用绝对路径 WORKDIR
。另外,您应该使用WORKDIR
而不是像那样RUN cd … && do-something
繁琐的说明,这些说明难以阅读,排除故障和维护。
ONBUILD
当前Dockerfile构建完成后,将执行ONBUILD命令。ONBUILD在从当前图像派生的任何子图像中执行。将ONBUILD命令视为父Dockerfile给子Dockerfile的指令。
Docker构建在子Dockerfile中的任何命令之前执行ONBUILD命令。
对于要从给定图像构建的图像,ONBUILD非常有用。例如,您可以将ONBUILD用于语言堆栈映像,以在Dockerfile中构建用该语言编写的任意用户软件,如Ruby的ONBUILD变体所示。
使用ONBUILD构建的图像应获得单独的标签,例如:ruby:1.9-onbuild或ruby:2.0-onbuild。
将“添加”或“复制”设置为“启用”时要小心。如果新构建的上下文缺少要添加的资源,则“ onbuild”映像将灾难性地失败。如上所述,添加一个单独的标签,可以通过允许Dockerfile作者做出选择来缓解这种情况。
官方图片示例
这些官方图片具有示例性Dockerfile
s: