1. Dockerfile总述
Docker 可以通过读取 Dockerfile 中的指令自动构建镜像。Dockerfile 是一个文本文档,其中包含用户可以在命令行上调用以组合镜像的所有命令。使用 docker build
,用户可以创建一个连续执行多个命令行指令的自动构建。
1.1. 一、镜像构建用法
docker build
命令从 Dockerfile 和上下文构建镜像。构建的上下文是 PATH 或 URL 指定位置的文件集合。PATH 是本地文件系统上的目录。URL 是 Git Repository 地址。
上下文被递归处理。因此,PATH 包括其任何子目录,URL 包括 Repository 及其子模块。此示例显示了使用当前目录作为上下文的构建命令:
$ docker build .
构建由 Docker 守护程序运行,而不是由 CLI 运行。构建过程的第一件事是将整个上下文(递归地)发送到守护进程。在大多数情况下,最好从空目录作为上下文开始,并将 Dockerfile 保存在该目录中。仅添加构建 Dockerfile 所需的文件。
[WARN|style:flat] 警告:不要将根目录 / 用作 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 守护程序将自动清理你发送的上下文。
[NOTE|style:flat] 请注意,每条指令都是独立运行的,并且会导致创建新镜像 - 因此
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
指定的镜像不需要具有父链,可以从其它注册表中拉取。
1.2. 二、格式
Dockerfile 基本格式如下:
# Comment
INSTRUCTION arguments
指令不区分大小写。但是,惯例是大写,以便更容易地将它们与参数区分开来。
Docker 按顺序在 Dockerfile 中运行指令。Dockerfile 必须由 “FROM” 指令开始。FROM 指令指定要构建的基础镜像。FROM 之前只允许存在一个或多个 ARG 指令,ARG 指令声明在 Dockerfile 中的 FROM 行中使用的参数。
Docker 将以 # 开头的行视为注释,除非该行是有效的解析器指令。行中任何其他位置的 # 标记都被视为参数。这允许这样的表达:
# 注释
RUN echo '我们运行一些 # 有趣的东西'
注释行不支持续行符。
1.3. 三、解析器指令
解析器指令是可选的,并且会影响处理 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
1.4. 四、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 <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>
1.5. 五、环境切换
环境变量(使用 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 的那一行中。
1.6. 六、.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 指令则不会将这个两个文件拷贝到镜像。
最后,你可能想要指定哪些文件会在上下文中被包含,而不是被排除。要想这样,指定 * 作为第一个匹配规则,接着指定一个或多个 ! 规则。
注意:由于历史原因,.
模式会被忽略。