©
本文档使用 PHP中文网手册 发布
Docker可以通过从Dockerfile
,一个文本文件,包含所有命令,按照顺序,需要生成给定的图像。Dockerfile
S坚持特定的格式,并使用一组特定的指令。您可以学习Dockerfile引用一页。如果你刚开始写作Dockerfile
你应该从那儿开始。
本文档涵盖Docker公司和Docker社区推荐的最佳做法和方法,以创建易于使用、有效的产品。Dockerfile
我们强烈建议您遵循这些建议%28事实上,如果您正在创建一个官方形象,您必坚持这些实践。
构建包-depsDockerfile
...
注意:有关此处提到的任何Dockerfile命令的更详细说明,请访问Dockerfile引用一页。
由图像生成的容器Dockerfile
定义应该尽可能的短暂。所谓“短暂”,我们的意思是,它可以被停止和摧毁,一个新的建立和安置的绝对最小的设置和配置。您可能想看看过程12要素应用程序方法中的一节,以了解以这样一种无状态方式运行容器的动机。
在大多数情况下,最好将每个Dockerfile放在一个空目录中。然后,只向该目录添加构建Dockerfile所需的文件。若要提高生成的性能,可以通过添加.dockerignore
文件也放在那个目录下。此文件支持类似于.gitignore
档案。有关创建一个的信息,请参见.dockerignore文件...
为了减少复杂性、依赖性、文件大小和构建时间,您应该避免仅仅因为“拥有”额外的或不必要的包而安装它们。例如,不需要在数据库映像中包含文本编辑器。
将应用程序解耦到多个容器中,可以更容易地进行水平扩展和重用容器。例如,Web应用程序堆栈可能由三个单独的容器组成,每个容器都有自己独特的映像,以解耦的方式管理Web应用程序、数据库和内存中的缓存。
您可能听说过“每个容器应该有一个过程”。虽然这个咒语有良好的意图,但不一定每个容器只应该有一个操作系统进程。除了容器现在可以由init进程生成,一些程序可能会自动产生额外的进程。例如,芹菜可以生成多个工作进程,或阿帕奇可能会为每个请求创建一个进程。虽然“每个容器一个进程”通常是一个好的经验法则,但它并不是一个硬和快速的规则。用你最好的判断来保持容器尽可能的干净和模块化。
如果容器相互依赖,则可以使用码头集装箱网络以确保这些容器能够通信。
您需要找到可读性(以及长期可维护性)Dockerfile
与最小化其使用的层数之间的平衡。对您使用的图层数量保持战略性和谨慎。
只要有可能,可以通过对多行参数进行字母数字排序来简化以后的更改。这将帮助您避免包的重复,并使列表更容易更新。这也使PRs更容易阅读和审查。在反斜杠%28之前添加空格\
%29也有帮助。
下面是一个来自buildpack-deps
图像*
RUN apt-get update && apt-get install -y \ bzr \ cvs \ git \ mercurial \ subversion
在构建图像码头的过程中,您将逐步了解Dockerfile
按照指定的顺序执行每个。在检查每条指令时,Docker将在其缓存中寻找一个可以重用的现有映像,而不是创建一个新的%28重复%29映像。如果您根本不想使用缓存,则可以使用--no-cache=true
选项的docker build
命令。
但是,如果您确实让Docker使用它的缓存,那么非常重要的是要了解它什么时候会,并且不会找到匹配的映像。码头工人将遵循的基本规则概述如下:
从缓存中已经存在的父映像开始,将下一条指令与从该基本映像派生的所有子映像进行比较,以查看其中一个是使用完全相同的指令生成的。否则,缓存将失效。
在大多数情况下,只需比较Dockerfile
其中一个孩子的图像就足够了。然而,某些指示需要更多的检查和解释。
为ADD
和COPY
说明,检查图像中文件%28s%29的内容,并计算每个文件的校验和。在这些校验和中不考虑文件%28s%29的最后修改和最后访问次数。在缓存查找过程中,将校验和与现有图像中的校验和进行比较。如果文件%28s%29中有任何更改,如内容和元数据,则缓存无效。
除了ADD
和COPY
命令时,缓存检查将不会查看容器中的文件以确定缓存匹配。例如,当处理RUN apt-get -y update
命令不会检查容器中更新的文件以确定是否存在缓存命中。在这种情况下,仅使用命令字符串本身来查找匹配项。
一旦缓存失效,所有后续Dockerfile
命令将生成新图像,缓存将不被使用。
下面,您将找到关于编写各种可用说明的最佳方法的建议,以便在Dockerfile
...
FROM指令的Dockerfile引用
只要有可能,使用当前的官方存储库作为你形象的基础。我们推荐Debian图像因为它是非常严格控制和保持最小的%28目前低于150 MB%29,同时仍然是一个完整的发行版。
理解对象标签
您可以向图像中添加标签,以帮助按项目组织图像、记录许可信息、帮助自动化或其他原因。对于每个标签,添加一行以LABEL
和一个或多个键值对。下面的示例显示了不同的可接受格式。解释性评论包括内联。
注*如果字符串包含空格,则必须引用它或这些空间必须逃掉。如果字符串包含内部引号字符%28
"
%29,也要逃离他们。
# Set one or more individual labels LABEL com.example.version="0.0.1-beta"LABEL vendor="ACME Incorporated"LABEL com.example.release-date="2015-02-12"LABEL com.example.version.is-production=""# 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引用
一如既往,让你Dockerfile
更易读,更易理解,更易维护,分裂更长或更复杂RUN
语句在用反斜杠分隔的多行上。
可能是最常见的用例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:14.04 RUN apt-get update RUN apt-get install -y curl
生成图像后,所有层都在Docker缓存中。假设您稍后修改apt-get install
通过添加额外的包:
FROM ubuntu:14.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的官方图片自动运行
apt-get clean
,因此不需要显式调用。
一些RUN
命令依赖于使用管道字符%28将一个命令的输出管道输送到另一个命令的能力|
%29,如下例所示:
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
选择。在这种情况下,%28,例如dash
shell是基于debian的图像%29上的默认shell,请考虑使用主管形式RUN
若要显式选择确实支持pipefail
选择。例如:
RUN ["/bin/bash", "-c", "set -o pipefail && wget -O - https://some.site | wc -l > /number"]
CMD指令的Dockerfile引用
大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
,你会掉进一个可用的壳里,准备好了。CMD
应很少以下列方式使用:CMD [“param”, “param”]
与ENTRYPOINT
,除非您和您的预期用户已经非常熟悉ENTRYPOINT
起作用了。
公开指令的Dockerfile引用
大EXPOSE
指示容器将侦听连接的端口。因此,应用程序应该使用通用的传统端口。例如,包含apache web服务器的映像将使用EXPOSE 80
,而包含MongoDB的图像将使用EXPOSE 27017
诸若此类
对于外部访问,用户可以执行docker run
带有指示如何将指定端口映射到他们选择的端口的标志。对于容器链接,Docker为从收件人容器返回到源%28 ie的路径提供环境变量,MYSQL_PORT_3306_TCP
29%。
ENV指令的Dockerfile引用
为了使新软件更容易运行,您可以使用ENV
若要更新PATH
容器安装的软件的环境变量。例如,ENV PATH /usr/local/nginx/bin:$PATH
将确保CMD [“nginx”]
只是起作用了。
大ENV
指令对于提供特定于您希望容器化的服务所需的环境变量也很有用,例如Postgres的PGDATA
...
最后,ENV
还可以用来设置常用的版本号,以便更容易维护版本凸起,如下面的示例所示:
ENV PG_MAJOR 9.3ENV PG_VERSION 9.3.4RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && … ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
类似于程序%28中有常量变量,而不是硬编码值%29,这种方法允许您更改单个ENV
指令,自动-神奇地碰撞版本的软件在您的容器。
添加指令的Dockerfile引用
复制指令的Dockerfile引用
尽管ADD
和COPY
在功能上是相似的,一般来说,COPY
是首选。那是因为它比ADD
...COPY
只支持将本地文件基本复制到容器中,而ADD
有一些特性%28,如本地只提取焦油和远程URL支持%29,这不是立即明显。因此,对ADD
是本地tar文件自动提取到图像中,如ADD rootfs.tar.xz /
...
如果你有多重Dockerfile
使用与上下文不同的文件的步骤,COPY
他们是单独的,而不是一次性的。这将确保每个步骤的生成缓存仅无效%28,如果特殊需要的文件更改,强制步骤重新运行%29。
例如:
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
。
入口点指令的Dockerfile引用
最好的用法ENTRYPOINT
是设置图像的主要命令,允许该图像像该命令一样运行(然后CMD
用作默认标志)。
让我们从命令行工具的图像示例开始s3cmd
*
ENTRYPOINT ["s3cmd"]CMD ["--help"]
现在可以像这样运行映像,以显示命令的帮助:
$ docker run s3cmd
或者使用正确的参数来执行命令:
$ docker run s3cmd ls s3://mybucket
这很有用,因为图像名可以作为对二进制文件的引用加倍,如上面的命令所示。
大ENTRYPOINT
指令也可以与辅助脚本结合使用,允许它以类似于上面命令的方式工作,即使启动工具可能需要多个步骤。
例如,邮政总局官方形象使用以下脚本作为其ENTRYPOINT
*
#!/bin/bashset -eif [ "$1" = 'postgres' ]; then chown -R postgres "$PGDATA" if [ -z "$(ls -A "$PGDATA")" ]; then gosu postgres initdb fi exec gosu postgres "$@"fi exec "$@"
注*此脚本使用大
exec
巴什命令因此,最终运行的应用程序将成为容器的PID 1。这允许应用程序接收发送到容器的任何Unix信号。见ENTRYPOINT
了解更多细节。
将助手脚本复制到容器中,并通过ENTRYPOINT
在集装箱启动时:
COPY ./docker-entrypoint.sh /ENTRYPOINT ["/docker-entrypoint.sh"]
此脚本允许用户以多种方式与Postgres交互。
它只需启动Postgres:
$ docker run postgres
或者,它可以用于运行Postgres并将参数传递给服务器:
$ docker run postgres postgres --help
最后,它还可以用来启动一个完全不同的工具,比如Bash:
$ docker run --rm -it postgres bash
卷指令的Dockerfile引用
大VOLUME
指令应用于公开由停靠器容器创建的任何数据库存储区、配置存储区或文件/文件夹。强烈鼓励您使用VOLUME
对于图像中的任何可变和/或用户可用部分。
用户指令的Dockerfile引用
如果服务可以在没有特权的情况下运行,请使用USER
若要更改为非根用户,请执行以下操作。首先,在Dockerfile
像这样RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres
...
注:图像中的用户和组得到一个不确定的UID/GID,因为“下一步”UID/GID将被分配,而不管图像重建如何。因此,如果它是关键的,您应该分配一个显式的UID/GID。注*由于未解决的缺陷在Go存档/tar包处理稀疏文件时,试图在Docker容器中创建具有足够大的UID的用户会导致磁盘耗尽
/var/log/faillog
在容器层中填充NUL%28\0%29个字符。通过--no-log-init
要用户添加的标志可以解决此问题。Debian/Ubuntuadduser
包装器不支持--no-log-init
标志,应避免。
您应该避免安装或使用sudo
由于它具有不可预知的TTY和信号转发行为,因此可能会导致比它解决的更多的问题。如果您绝对需要类似于sudo
%28E.。如果将守护进程初始化为root,但以非root%29的形式运行它,则可以使用“天哪”...
最后,为了减少层数和复杂度,避免切换。USER
频繁地来回走动。
WORKDIR指令的Dockerfile引用
为了清晰可靠,您应该始终使用绝对路径WORKDIR
同时,你也应该用WORKDIR
而不是像RUN cd … && do-something
,它们很难阅读、故障排除和维护。
ONBUILD指令的Dockerfile引用
安ONBUILD
命令之后执行Dockerfile
构建完成。ONBUILD
在派生的任何子映像中执行。FROM
当前图像。想想ONBUILD
命令作为父级指令。Dockerfile
给孩子Dockerfile
...
执行Docker生成ONBUILD
在子命令之前的命令Dockerfile
...
ONBUILD
对于将要构建的图像是有用的。FROMONBUILD
中生成用该语言编写的任意用户软件的语言堆栈映像。Dockerfile
,如你所见鲁比氏ONBUILD
变体...
图像ONBUILD
应该得到一个单独的标记,例如:ruby:1.9-onbuild
或ruby:2.0-onbuild
...
放的时候要小心ADD
或COPY
在ONBUILD
如果新构建的上下文缺少要添加的资源,“onbuild”映像将灾难性地失败。如上面所建议的那样,添加一个单独的标记将有助于缓解这种情况,因为它允许Dockerfile
作者做出选择。
Dockerfile引用
更多关于基本图像的信息
有关自动生成的更多信息
创建官方存储库的指导方针