Dockerfile 参考手册(一)

博客原文

最近学习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 kB
Error 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文件的格式:

# Comment
INSTRUCTION arguments

指令对字母大小写是不敏感的,但是,习惯上将它们大写,以便容易和参数区分开。

Docker是按照顺序来执行Dockerfile中的指令的。一个Dockerfile文件必须以FROM指令开始,FROM指令指定了你正在编译镜像的基础镜像。在Dockerfile文件中,FROM指令的前面仅可以是一个或者多个ARG指令,这些声明的参数被用于FROM指令。

Docker认为以#开头的行是注释,除非这一行是一个有效的转义的指令。#标识出现在一行的任何其它地方,都会被认为是一个参数。就像下面这段:

# Comment
RUN echo 'we are running some # of cool things'

注释中不支持继续字符。

03 转义指令

转义指令是可选的,它会影响在Dockerfile中后续行的处理方式。转义指令并不会添加任何层到构建的镜像中,也不会作为构建一个步骤展示,转义指令是被写作一个特殊类型的注释,形式为# directive=value,一个指令可能只会被使用一次。

一旦有一行注释、空行或者编译指令被执行,Docker就不会再检查转义指令了,而是将任何格式的转义指令认为是注释,不会尝试去验证它是否是转义指令。因此所有的转义指令必须放在Dockerfile文件的第一行。

转义指令不是大小写敏感的,但是通常使用小写的形式,习惯上任何的转义指令后面都跟一个空行。转义指令不支持续行符。

根据上面这些规则,下面是一些无效的转义指令的例子:

由于续行符,导致无效:

# direc \
tive=value

由于出现两次,导致无效:

# directive=value1
# directive=value2

FROM ImageName

由于出现在了编译指令之后,被当作了注释:

FROM ImageName
# directive=value

由于出现在了注释之后,被当作了注释,而不是转义指令:

# About my dockerfile
# directive=value
FROM 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.txt
10/05/2016  02:22 PM    <DIR>          Program Files
10/05/2016  02:14 PM    <DIR>          Program Files (x86)
10/28/2016  11:18 AM                62 testfile.txt
10/28/2016  11:20 AM    <DIR>          Users
10/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 /bar
ADD . $foo       # ADD . /bar
COPY \$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=$abc
ENV ghi=$abc

结果是,def的值是hello,而不是byeghi的值是bye,因为它不是设置abcbye的指令的一部分。

06 .dockerignore文件

在docker命令行界面中发送上下文到docker的守护进程之前,它会检查上下文目录根路径下名为.dockerignore的文件,如果这个文件存在,命令行界面会修改上下文,排除那些被.dockerignore中的模式匹配到的文件和目录。这有助于避免一些不必要的(大的或者敏感的文件和目录)发送到守护进程,还能避免一些潜在的使用ADD 或者 COPY添加文件和目录到镜像中。

命令行解释.dockerignore文件为一个换行符分割的模式列表,类似于Unix shell的glob文件。由于这个匹配的目的,上下文的根被认为是工作目录和根目录。例如,模式 /foo/barfoo/bar都是在排除目录foo下面一个叫bar的文件或者目录,目录fooPATH的子目录或者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*.md
README-secret.md

除了README 文件之外,没有任何markdown文件被包含进上下文,并没有README-secret.md

现在看这个例子:

*.md
README-secret.md
!README*.md

所有的README文件都会被包含进去,中间一行是没有任何影响的,因为!README*.md 能够匹配 README-secret.md,并且在后面。

你甚至可以用.dockerignore来排除Dockerfile文件和.dockerignore,这些文件仍然是会被送到守护进程的,因为需要它们做这些工作,但是ADDCOPY指令是不会copy它们到镜像中去的。

最后,你可能想要指定文件包含进上下文,而不是排除它们,为了实现这个目的,可以使用*作为第一个模式,下面使用一个或者多个!异常模式。

注意:由于历史原因,模式.是被忽略的。

到此为止介绍Dockerfile文件中工作原理和一些语法,以及相关的一些东西,其中03和04节不太常用,翻译不是太好,请高手指正。Dockerfile中常用的指令下一篇文章再介绍。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容

  • 博客原文 本文接着上一篇文章《Dockerfile 参考手册(一)》接续Dockerfile相关的学习。本文主要介...
    rabbitGYK阅读 1,547评论 0 9
  • docker基本概念 1. Image Definition 镜像 Image 就是一堆只读层 read-only...
    慢清尘阅读 8,665评论 1 21
  • 昨天在实验室正在赶作业,朋友突然跑过来坐在我旁边,摘下眼镜,眼泪就哗啦啦地流下来了,我赶紧递上纸,一脸懵逼。几分钟...
    曾好看呐阅读 814评论 3 5
  • 一群孩童从她身边飞奔而过,带来了些许微风。她知道,孩子们是奔向街口的小卖部,那里有各种口味的冰淇淋和雪糕。 今天是...
    龙朱朱阅读 3,137评论 36 57
  • 才二月底,楼下的玉兰已长出了花骨朵。跃跃欲试,一副含苞待放的样子。街道两边的柳树远远望去,枝条随风摇摆,朦胧中若有...
    云水居阅读 619评论 2 3