最近学习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
共同学习,写下你的评论
评论加载中...
作者其他优质文章