本文介绍了用于构建高效镜像的推荐最佳实践和方法。
Docker通过读取Dockerfile中
的指令自动构建镜像,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
且此后不需要保存的情况下使用。
为了方便起见,本节中的示例使用Here 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从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将会失败。 以下示例说明了这一点:# 创建一个目录,并使用这个目录工作 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
使用此语法可使用本地文件系统上的文件,但使用stdin
传递的Dockerfile
构建镜像。 该语法使用-f
(或--file
)选项指定要使用的Dockerfile
,并使用连字符(-)作为文件名来指示Docker从stdin中读取Dockerfile:
docker build [OPTIONS] -f- PATH
下面的示例使用当前目录(.)作为构建上下文,并使用Dockerfile
构建镜像,并使用here文档通过stdin
传递该文件。
# 创建一个目录,并使用这个目录进行工作
mkdir example
cd example
# 创建一个示例文件
touch somefile.txt
# 构建一个镜像使用当前目录作为构建上下文,并通过stdin传递Dockerfile
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” 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
,并将这些文件作为构建上下文发送到后台程序。 此功能要求在运行docker build
命令的主机上安装git
。
使用.dockerignore排除
要排除与构建无关的文件(无需修改源仓库),请使用.dockerignore
文件。 该文件支持类似于.gitignore
文件的排除模式。 有关创建它的信息,请参见本文中的.dockerignore文件。
使用多阶段构建
多阶段构建使您可以大幅度减小最终镜像的大小,而不必努力减少中间层和文件的数量。
由于镜像是在构建过程的最后阶段生成的,因此可以利用构建缓存(本文后续部分后讲到)来最小化镜像层。
例如,如果您的构建包含多个层,则可以将它们从更改频率较低(以确保构建缓存可重用)到更改频率较高的顺序排序:
安装生成应用程序所需的工具
安装或更新依赖库
生成您的应用程序
Go应用程序的Dockerfile应该类似如下代码:
FROM golang:1.11-alpine AS build
# 安装项目需要的工具
# 运行 `docker build --no-cache .` 更新依赖
RUN apk add --no-cache git
RUN go get github.com/golang/dep/cmd/dep
# 列出Gopkg.toml和Gopkg.lock中的项目依赖
# 只有当Gopkg文件更新的时候才会重新构建这些层
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
# 安装依赖库
RUN dep ensure -vendor-only
# 复制整个项目并生成它
# 只有当项目目录中的文件更改的时候才会重新构建这个层
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开头并带有一个或多个键值对的行。 以下示例显示了不同的可接受格式。 内嵌包含解释性注释。
带空格的字符串必须用引号引起来,否则必须转义。 内部的引号字符(")也必须转义。
# 设置一个或多个单独的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指令,以防止创建额外的层。 现在不再需要此操作,但仍支持组合标签。
# 同一行设置多个标签
LABEL com.example.version="0.0.1-beta" com.example.release-date="2015-02-12"
上面也可以写成:
# 一次设置多个标签,使用换行符断开长行
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
语句中将RUN apt-get update
与apt-get install
结合在一起。 例如:
RUN apt-get update && apt-get install -y package-bar package-baz package-foo
在RUN
语句中单独使用apt-get update
会导致缓存问题,并且随后的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
,因此不需要显式调用。
使用pipes
某些RUN
命令取决于将一个命令的输出管道传输到另一个命令的能力,使用管道字符(|),如以下示例所示:
RUN wget -O - https://some.site | wc -l > /number
Docker使用/bin/sh -c
解释器执行这些命令,该解释器仅评估管道中最后一个操作的退出代码以确定成功。 在上面的示例中,即使wget
命令失败,只要wc -l
命令成功,该构建步骤也会成功并生成一个新镜像。
如果希望管道中的任何阶段的错误都导致命令失败,请在命令之前添加set -o pipefail &&
前缀,以确保意外的错误可以阻止构建成功。 例如:
RUN set -o pipefail && wget -O - https://some.site | wc -l > /number
并非所有的shell程序都支持
-o pipefail
选项。在诸如基于Debian的镜像上的
dash shell
的情况下,请考虑使用RUN的exec形式的来显式选择一个支持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
提供交互式shell,例如bash,python和perl。 例如,CMD ["perl","-de0"]
,CMD ["python"]
或CMD ["php","-a"]
。 使用此形式意味着执行docker run -it python
之类的操作时,您将进入可用的shell中,随时可以使用。 除非您和您的预期用户已经非常熟悉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的行连续字符可以提高可读性。 您也可以将所有命令放入shell脚本中,并让RUN
命令仅运行该shell脚本。
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
Dockerfile参考的Add指令 Dockerfile参考的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/
RUN步骤的缓存失效的机会更少,与COPY . /tmp/
放在RUN命令之前相比。
由于镜像大小很重要,因此强烈建议不要使用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
。
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 "$@"
将应用程序配置为PID 1
该脚本使用
exec
Bash命令,以使用最终运行的应用程序成为容器的PID1。这使该应用程序可以接收发送到该容器的所有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
Dockerfile的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存档(archive)/解压(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
中使用ADD
或COPY
时要小心。 如果新构建的上下文缺少要添加的资源,则“onbuild”镜像将灾难性地失败。 按推荐的做法,添加一个单独的标签,可以允许Dockerfile
作者做出选择来缓解这种情况。
官方镜像示例
这些官方镜像的示例性Dockerfile: