Dockerfile总述

Docker 可以通过读取 Dockerfile 中的指令自动构建镜像。Dockerfile 是一个文本文档,其中包含用户可以在命令行上调用以组合镜像的所有命令。使用 docker build,用户可以创建一个连续执行多个命令行指令的自动构建。

一、镜像构建用法

docker build 命令从 Dockerfile 和上下文构建镜像。构建的上下文是 PATH 或 URL 指定位置的文件集合。PATH 是本地文件系统上的目录。URL 是 Git Repository 地址。

上下文被递归处理。因此,PATH 包括其任何子目录,URL 包括 Repository 及其子模块。此示例显示了使用当前目录作为上下文的构建命令:

$ docker build .

构建由 Docker 守护程序运行,而不是由 CLI 运行。构建过程的第一件事是将整个上下文(递归地)发送到守护进程。在大多数情况下,最好从空目录作为上下文开始,并将 Dockerfile 保存在该目录中。仅添加构建 Dockerfile 所需的文件。

警告:不要将根目录 / 用作 PATH,因为它会导致构建将硬盘驱动器的全部内容传输到 Docker 守护程序。

该命令会将当前目录中的所有内容发送至 Docker 守护进程进行处理。Docker 守护进程会读取该目录下的 Dockerfile 文件,并根据文件内容构建镜像。

要在构建上下文中使用文件,Dockerfile 引用指令中指定的文件,例如 COPY 指令。要提高构建的性能,请通过将 .dockerignore 文件添加到上下文目录来排除文件和目录。

习惯上,Dockerfile 放在上下文的根目录中。你也可以将 -f 标记与 docker build 一起使用,以指向文件系统中任何位置的 Dockerfile。

$ docker build -f /path/to/a/Dockerfile .

如果构建成功,您可以指定一个 Repository 和标记以保存新镜像:

$ docker build -t practicemp/myapp .

要在构建后将镜像标记为多个 Repositories,在运行 build 命令时添加多个 -t 参数:

$ docker build -t practicemp/myapp:1.0.2 -t prcticemp/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 对下一条指令不会产生任何影响,切换目录应使用 WORKDIR 指令。

只要有可能,Docker将复用中间镜像(缓存),以显着加速 docker build 过程。这由控制台输出中的 Using cache 消息提示。(有关更多信息,请参阅 Dockerfile 最佳实践指南中的构建缓存部分):

$ 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

构建缓存仅用于具有本地父链的镜像。这意味着这些镜像是在以前版本的基础上构建的,或者使用 docker load 加载了整个镜像链。如果你希望使用指定镜像的构建缓存,可以使用 –cache-from 选项指定它。使用 –cache-from 指定的镜像不需要具有父链,可以从其它注册表中拉取。

二、格式

Dockerfile 基本格式如下:

# Comment
INSTRUCTION arguments

指令不区分大小写。但是,惯例是大写,以便更容易地将它们与参数区分开来。

Docker 按顺序在 Dockerfile 中运行指令。Dockerfile 必须由 “FROM” 指令开始。FROM 指令指定要构建的基础镜像。FROM 之前只允许存在一个或多个 ARG 指令,ARG 指令声明在 Dockerfile 中的 FROM 行中使用的参数。

Docker 将以 # 开头的行视为注释,除非该行是有效的解析器指令。行中任何其他位置的 # 标记都被视为参数。这允许这样的表达:

注释

RUN echo ‘我们运行一些 # 有趣的东西’ 注释行不支持续行符。

三、解析器指令 解析器指令是可选的,并且会影响处理 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 四、escape

escape=\

或者

escape=`

escape指令指定用于转义 Dockerfile 中字符的字符。如果未指定,则默认转义字符为 \。

转义字符即可用于转义,也用来当做续行符。这允许 Dockerfile 指令跨越多行。请注意,无论 escape 解析器指令是否包含在 Dockerfile 中,都不会在 RUN 命令中执行转义,除非在该行的末尾作为续行符使用。

对于 Windows 镜像来说,将转义字符设置为 ` 非常有用,因为 \ 被用来当做路径分隔符。Windows PowerShell 中的转义字符正是 `。

参考以下示例,该示例在 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> 解决方案之一是使用 / 作为路径分隔符。但是这种写法对于 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

Program Files 10/05/2016 02:14 PM Program Files (x86) 10/28/2016 11:18 AM 62 testfile.txt 10/28/2016 11:20 AM Users 10/28/2016 11:20 AM 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> 五、环境切换 环境变量(使用 ENV 语句声明)可以被某些指令所引用。如果要使用变量格式的字符串,可以使用转义进行处理。

使用 $variable_name 或者 ${variable_name} 来引用环境变量。两种写法是等效的。大括号写法通常用于解决变量名没有空格的地址问题,如 ${foo}_bar。

${variable_name} 语法还支持以下指定的一些标准 bash 修饰符:

${variable:-word} 表示:如果 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 环境变量可以被下面的指令所引用:

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,而不是 bye。然而,ghi 的值是 bye,因为它不在将 abc 设置为 bye 的那一行中。

六、.dockerignore 文件 在 Docker CLI 将上下文发送给 Docker 守护进程之前,它将会在上下文的根目录中寻找名为 .dockerignore 的文件。如果存在该文件,CLI 将会修改上下文以排除在该文件中匹配到的文件和目录。这有助于避免将大型或敏感的文件或目录发送给 Docker 守护进程,并且避免使用 ADD 或者 COPY 将其添加到镜像中。

CLI 将 .dockerignore 文件内容解析为由其每行模式组成的列表,模式类似于 Unix shell 中的 file globs。出于匹配的目的,上下文的根被当做是工作目录和根目录。例如,/foo/bar 和 foo/bar 模式都会在 PATH 的 foo 子目录中或位于 URL 的 git repository 的根目录中排除名为 bar 的文件或目录。

如果 .dockerignore 中的一行以 # 开头,则这一行被视为注释并在 CLI 解析之前被忽略。

这是一个 .dockerignore 文件的示例:

comment

/temp //temp* temp? 这个文件会引起下面的构建行为:

规则 行为

comment 被忽略

/temp 在根目录的直接子目录中排除所有以 temp 开头的文件或者目录。例如,纯文本文件 /somedir/temporary.txt 会被排除,同样目录 /somedir/temp 也会被排除。 //temp* 在根目录的二级子目录中排除所有以 temp 开头的文件或者目录。例如,/somedir/subdir/temporary.txt 会被排除。 temp? ? 匹配单字符,在根目录中,排除所有以 temp 开头的 5 字符文件和目录。例如,/tempa 和 /tempb 会被排除。 匹配是使用 Go 语言的 filepath.Match 规则完成的。在匹配之前会进行预处理,预处理会移除首尾的空格,并使用 Go 语言的 filepath.Clean 消除 . 和 .. 元素。空行在预处理后将被忽略。

除了 Go 语言的 filepath.Match 规则之外,Docker 还支持一个特别的通配符 :匹配任何层级的目录。例如,/*.go 将排除在所有目录中找到的以 .go 结尾的文件,包括上下文的根中。

以 ! (感叹号)开头的行用来标记排除的例外规则。下面是一个使用该机制的示例:

*.md !README.md 这将会排除上下文中所有 markdown 文件,除了 README.md。

另外,! 规则的位置会影响其行为:.dockerignore 最后一行匹配到文件将会决定 ! 规则是包括还是被排除。考虑下面的例子:

.md !README.md README-secret.md 除了 README-secret.md 之外的 README 文件,所有 markdown 文件都将被排除。

再考虑下面的例子:

.md README-secret.md !README.md 所有的 README 文件都不会被排除。第二行不起作用,因为 !README*.md 匹配到了 README-secret.md 并且更靠后。

你甚至可以用 .dockerignore 文件来排除 Dockerfile 和 .dockerignore本身。这两个文件还是会被发送给 Docker 守护进程,因为守护进程的需要它们来完成工作。但是 ADD 和 COPY 指令则不会将这个两个文件拷贝到镜像。

最后,你可能想要指定哪些文件会在上下文中被包含,而不是被排除。要想这样,指定 * 作为第一个匹配规则,接着指定一个或多个 ! 规则。

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