最近学习docker过程中,发现Dockerfile是一个非常重要的文档,本文系统学习一下。
文档是基于Docker v17.09 版本。
翻译作品,原文请见官网英文文档。
00 前言
Docker可以读取Dockerfile中的指令自动构建镜像,Dockerfile是一个文本文件,它包含很多命令,用户可以在命令行上调用这些命令组装镜像。用户可以使用docker build来自动构建镜像,它可以连续执行若干命令行指令。
本文将介绍在Dockerfile中你可以使用命令,你读完这篇文章之后,Dockerfile
Best Practices 是另一篇很好的指导。
01 用法
docker build命令根据Dockerfile和上下文来构建镜像,构建过程的上下文是通过PATH或者URL指定的一系列文件。PATH是一个本地文件系统的目录,URL是一个Git仓库的位置。
上下文是一个递归的处理过程。因此,PATH可以包含任何的子目录,URL`包括仓库和它的子模块。下面是一个构建镜像的命令的示例,使用当前目录作为上下文:
$ docker build . Sending build context to Docker daemon 6.51 MB ...
Build是通过Docker daemon(docker 守护进程),而不是 CLI(命令行界面)执行的。Build过程要做的第一件事是发送整个上下文(递归)到Docker的守护进程。最佳实践是,开始创建一个空的文件夹作为上下文,然后将你的Dockerfile文件放在那个文件夹下,仅添加一些你在编译Dockerfile过程中需要的文件。
注意:千万不要使用根路径
/作为PATH,这将导致Build会发送你的硬盘上的所有内容到Docker的守护进程。
在Build的上下文中为了使用Dockerfile中指定的一个文件,这个文件是某个指令(例如COPY指令)用到的。为了提高Build的性能,通过添加.dockerignore文件,可以排除上下文目录中的某些文件和目录,关于如何创建.dockerignore文件更多信息见本文的下面章节。
一般认为,Dockerfile文件都应该位于上下文的根目录下,你可以在docker build后使用-f标识来指定你的文件系统中任意位置的Dockerfile文件。
$ docker build -f /path/to/a/Dockerfile .
你还可以指定用来存储成功编译的镜像文件的仓库和标签:
$ docker build -t shykes/myapp .
Build的时候也可以为镜像添加多个仓库标签,在你执行Build命令的时候添加多个-t参数即可:
$ docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .
Docker守护进程在执行Dockerfile中的指令之前,会首先对Dockerfile做一个初步校验,如果有语法错误,它会返回一个错误:
$ docker build -t test/myapp . Sending build context to Docker daemon 2.048 kBError response from daemon: Unknown instruction: RUNCMD
Docker守护进程是逐步执行Dockerfile中的指令的,如果需要的话,会提交每个指令的结果到新的镜像中,最后输出新镜像的的ID。Docker的守护进程也会自动清除你发送的上下文。
注意,每一条指令都是独立执行的,因此在创建一个镜像的时候,RUN cd /tmp这条指令不会对下一条指令有任何影响。
无论任何可能的时候,Docker都将会重用中间状态(缓存)的镜像,这样能够明显地加速docker build的过程,这是通过控制台输出的信息Using cache来标识的。(更多信息参见,在Dockerfile的最佳实践指导中的Build cache section):
$ docker build -t svendowideit/ambassador . Sending build context to Docker daemon 15.36 kB Step 1/4 : FROM alpine:3.2 ---> 31f630c65071 Step 2/4 : MAINTAINER SvenDowideit@home.org.au ---> Using cache ---> 2a1c91448f5f Step 3/4 : RUN apk update && apk add socat && rm -r /var/cache/ ---> Using cache ---> 21ed6e7fbb73 Step 4/4 : CMD env | grep _TCP= | (sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat -t 100000000 TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' && echo wait) | sh ---> Using cache ---> 7ea8aef582cc Successfully built 7ea8aef582cc
仅在编译那些有具有本地主链的镜像时使用缓存,意思是这些镜像的创建依赖前面的Build,或者整个镜像链都已经通过docker load加载进来了。如果你希望对一个指定镜像使用build cache,你可以使用--cache-from来指定,通过--cache-from指定的镜像不需要有一个主链,也可能是从其他的中心拉取的。
当你编译完成的时候,你该学习 Pushing a repository to its registry。
02 格式
下面是Dockerfile文件的格式:
# CommentINSTRUCTION arguments
指令对字母大小写是不敏感的,但是,习惯上将它们大写,以便容易和参数区分开。
Docker是按照顺序来执行Dockerfile中的指令的。一个Dockerfile文件必须以FROM指令开始,FROM指令指定了你正在编译镜像的基础镜像。在Dockerfile文件中,FROM指令的前面仅可以是一个或者多个ARG指令,这些声明的参数被用于FROM指令。
Docker认为以#开头的行是注释,除非这一行是一个有效的转义的指令。#标识出现在一行的任何其它地方,都会被认为是一个参数。就像下面这段:
# CommentRUN echo 'we are running some # of cool things'
注释中不支持继续字符。
03 转义指令
转义指令是可选的,它会影响在Dockerfile中后续行的处理方式。转义指令并不会添加任何层到构建的镜像中,也不会作为构建一个步骤展示,转义指令是被写作一个特殊类型的注释,形式为# directive=value,一个指令可能只会被使用一次。
一旦有一行注释、空行或者编译指令被执行,Docker就不会再检查转义指令了,而是将任何格式的转义指令认为是注释,不会尝试去验证它是否是转义指令。因此所有的转义指令必须放在Dockerfile文件的第一行。
转义指令不是大小写敏感的,但是通常使用小写的形式,习惯上任何的转义指令后面都跟一个空行。转义指令不支持续行符。
根据上面这些规则,下面是一些无效的转义指令的例子:
由于续行符,导致无效:
# direc \tive=value
由于出现两次,导致无效:
# directive=value1# directive=value2FROM ImageName
由于出现在了编译指令之后,被当作了注释:
FROM ImageName# directive=value
由于出现在了注释之后,被当作了注释,而不是转义指令:
# About my dockerfile# directive=valueFROM ImageName
未知的指令由于无法识别被当作了注释,另外一个已知的指令由于出现在了注释的后面,被当作了注释而不是转义指令。
# unknowndirective=value# knowndirective=value
转义指令中允许出现非断行的空格,所以下面几行都是相同的:
#directive=value# directive =value# directive= value# directive = value# dIrEcTiVe=value
下面的转义指令是支持的:escape
04 转义符指令
# escape=\ (backslash)
或者
# escape=` (backtick)
escape指令是用来设置Dockerfile中转义字符的字符,如果不指定的话,默认的转义字符是\。
转义字符不仅用在一行中的转义字符上,也用在开启一个新行。Dockerfile中指令允许是多行的。注意,无论在Dockerfile中是否包含escape转义指令,在RUN命令中是不会执行转义的,除非是在一行的末尾。
在Windows环境下,设置转义字符为 ` ,是非常有用的,由于\是目录路径的分隔符,`和windows下的转义字符是一致的。
考虑下面的一个例子,在windows环境下是失败的,在第二行的第二个\被解释成了换行的转义符,而不是被第一个\转义了的目标,同样的,在第三行末尾的\也是,它们被认作是一个指令,\被认为是续行符。这个Dockerfile的结果就是第二行和第三行被认为是一行指令:
FROM microsoft/nanoserver COPY testfile.txt c:\\ RUN dir c:\
结果是:
PS C:\John> docker build -t cmd . Sending build context to Docker daemon 3.072 kB Step 1/2 : FROM microsoft/nanoserver ---> 22738ff49c6d Step 2/2 : COPY testfile.txt c:\RUN dir c: GetFileAttributesEx c:RUN: The system cannot find the file specified. PS C:\John>
一个解决办法是,上面都使用/作为COPY指令和dir的目标。然而,最好的情况下,这只是看着windows下的路径不自然,最坏的情况下,并不是所有的windows命令都支持/作为路径分隔符。
另一种解决办法,添加一个escape转义指令,下面的Dockerfile成功的执行,如预期的一样windows平台很自然路径表示语义:
# escape=`FROM microsoft/nanoserver COPY testfile.txt c:\ RUN dir c:\
结果是:
PS C:\John> docker build -t succeeds --no-cache=true . Sending build context to Docker daemon 3.072 kB Step 1/3 : FROM microsoft/nanoserver ---> 22738ff49c6d Step 2/3 : COPY testfile.txt c:\ ---> 96655de338de Removing intermediate container 4db9acbb1682 Step 3/3 : RUN dir c:\ ---> Running in a2c157f842f5 Volume in drive C has no label. Volume Serial Number is 7E6D-E0F7 Directory of c:\10/05/2016 05:04 PM 1,894 License.txt10/05/2016 02:22 PM <DIR> Program Files10/05/2016 02:14 PM <DIR> Program Files (x86)10/28/2016 11:18 AM 62 testfile.txt10/28/2016 11:20 AM <DIR> Users10/28/2016 11:20 AM <DIR> Windows 2 File(s) 1,956 bytes 4 Dir(s) 21,259,096,064 bytes free ---> 01c7f3bef04f Removing intermediate container a2c157f842f5 Successfully built 01c7f3bef04f PS C:\John>
05 环境变量占位符
环境变量(ENV声明)可以被用在某些指令中作为变量(可以被Dockerfile解释)。转义指令也可以用于处理语句中包含类似变量的语法。
环境变量在Dockerfile中表示为$variable_name 或者 ${variable_name},他们是等效的,大括号的语法通常用来强调没有空格的变量名,例如${foo}_bar。${variable_name}语法也支持一些标准的bash修饰符,例如下面:
- ${variable:-word}意思是,如果- variable被设置了,结果将是那个值,如果- variable没被设置,那个- word就是结果。
- ${variable:+word}意思是,如果- variable被设置了,- word就是结果,否则结果就是空。
以上所有情形,word可以是任何字符串,包括其它的环境变量。
转义可以在变量之前添加\:例如,\$foo或者\${foo}将被转义为$foo 和${foo}两个常量。
举个例子(转义之后的结果展示在#的后面):
FROM busybox
ENV foo /bar
WORKDIR ${foo}   # WORKDIR /barADD . $foo       # ADD . /barCOPY \$foo /quux # COPY $foo /quux环境变量在下面这些Dockerfile指令中都是支持的:
- ADD
- COPY
- ENV
- EXPOSE
- FROM
- LABEL
- STOPSIGNAL
- USER
- VOLUME
- WORKDIR
此外还有:
- ONBUILD(当与上面任何一个指令结合时)
注意:在1.4版本之前,
ONBUILD是不支持环境变量的,即使与上面列出的指令结合时。
在整个指令中环境变量的替换值都是用同一个值,换句话说,就是下面的例子:
ENV abc=hello ENV abc=bye def=$abcENV ghi=$abc
结果是,def的值是hello,而不是bye,ghi的值是bye,因为它不是设置abc为bye的指令的一部分。
06 .dockerignore文件
在docker命令行界面中发送上下文到docker的守护进程之前,它会检查上下文目录根路径下名为.dockerignore的文件,如果这个文件存在,命令行界面会修改上下文,排除那些被.dockerignore中的模式匹配到的文件和目录。这有助于避免一些不必要的(大的或者敏感的文件和目录)发送到守护进程,还能避免一些潜在的使用ADD 或者 COPY添加文件和目录到镜像中。
命令行解释.dockerignore文件为一个换行符分割的模式列表,类似于Unix shell的glob文件。由于这个匹配的目的,上下文的根被认为是工作目录和根目录。例如,模式 /foo/bar 和foo/bar都是在排除目录foo下面一个叫bar的文件或者目录,目录foo是PATH的子目录或者URL指定的git仓库下的子目录。不排除任何其它的。
如果在.dockerignore文件中有一行以#开头,那么这一行被认为是注释,命令行解释之前为忽略它。
下面是一个.dockerignore文件的例子:
# comment*/temp* */*/temp* temp?
这个文件将引发下面的构建行为:
| 规则 | 行为 | 
|---|---|
| # comment | 忽略。 | 
| */temp* | 排除根目录下的子目录中任何以 temp开头的文件和目录,例如,/somedir/temporary.txt这个文本文件会被排除,/somedir/temp这个目录也会被排除。 | 
| */*/temp* | 排除来自子目录的任何以 temp开头的文件和目录,这个子目录是根目录下两层,例如,/somedir/subdir/temporary.txt被排除的。 | 
| temp? | 排除那些根目录下名字以 temp开始拓展一个字符的文件和目录,例如,/tempa和/tempb是被排除的。 | 
完成这个匹配使用的是Go语言的文件路径匹配规则,在预处理步骤中会去除掉开头和结尾的空格,并清除.和..元素,在这个过程中使用的是Go语言的文件路径清理方法,预处理过程中会忽略掉空白行。
在Go语言的文件路径匹配规则之外,Docker还支持一个特殊的通配符**,用于匹配任意数量的目录(包括零),例如,**/*.go将排除所有以.go结尾的文件,它会在编译上下文的根目录的所有目录中找。
以感叹号!开始的行被用于标出排除中的异常文件,下面的这个.dockerignore文件的例子就使用了这种机制:
*.md!README.md
在上下文中除了README.md之外,所有markdown文件都会被排除。
异常规则!的位置影响行为:.dockerignore文件的最后一行匹配一个特定文件,它是包含还是排除呢?看下面的例子:
*.md!README*.mdREADME-secret.md
除了README 文件之外,没有任何markdown文件被包含进上下文,并没有README-secret.md。
现在看这个例子:
*.mdREADME-secret.md!README*.md
所有的README文件都会被包含进去,中间一行是没有任何影响的,因为!README*.md 能够匹配 README-secret.md,并且在后面。
你甚至可以用.dockerignore来排除Dockerfile文件和.dockerignore,这些文件仍然是会被送到守护进程的,因为需要它们做这些工作,但是ADD和COPY指令是不会copy它们到镜像中去的。
最后,你可能想要指定文件包含进上下文,而不是排除它们,为了实现这个目的,可以使用*作为第一个模式,下面使用一个或者多个!异常模式。
注意:由于历史原因,模式
.是被忽略的。
到此为止介绍Dockerfile文件中工作原理和一些语法,以及相关的一些东西,其中03和04节不太常用,翻译不是太好,请高手指正。Dockerfile中常用的指令下一篇文章再介绍。
作者:rabbitGYK
链接:https://www.jianshu.com/p/24327e27d6c3
共同学习,写下你的评论
评论加载中...
作者其他优质文章
 
                 
             
			 
					 
					