Dockerfile文件详解

前言

此文根据 Dockerfile Reference 翻译而来,其中包含少许自己的理解,由于能力有限,难免有错漏,如有发现,请不吝指正,谢谢!

正文

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

01 用法

docker build 命令从 Dockerfile 和一个 构建上下文(build context) 中构建docker镜像。构建上下文(build context) 是位于指定位置(PATHURL) 的文件集合。PATH 是本地文件系统上的一个目录,URL 是一个Git仓库的位置。

构建上下文 是递归处理的,所以,一个 PATH 包含所有子目录,而 URL 包含存储库及其子模块。
下面的例子展示了使用当前目录作为构建上下文进行镜像构建:

$ docker build .
Sending build context to Docker daumon 6.51 MB

构建由Docker守护进程执行,而不是命令行。构建过程所做的第一件事是将整个构建上下文(递归地)发送给守护进程。在大多数情况下,最好使用一个空目录作为构建上下文,并将Dockerfile保存在该目录中。并且只添加Dockerfile中的指令所需的文件。

警告:不要使用root目录 / 作为构建上下文,因为这样会导致硬盘上的所有内容都会被上传到Docker守护进程。

要使用构建上下文中的文件,必须在 Dockerfile 中的指令中指定文件的路径。要提高构建的性能,可以通过向构建上下文目录中添加.dockerignore文件来排除文件和目录。

一般来说,Dockerfile 的名称就是 Dockerfile,且位于构建上下文的根目录中,不过,也可以在 docker build 中使用 -f 标志指向位于文件系统上任意位置的 Dockerfile

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

可以通过 docker build-t 标志指定被成功创建的镜像要保存的仓库名称和标签:

$ docker build -t /shykes/myapp .

要在镜像构建成功之后将其标记到多个存储库,可以在 docker 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 中的指令的顺序,从上到下逐个执行,因此,应该根据需要合理安排 Dockerfile 中指令的顺序。每条指令都会创建一个新的镜像层(如果需要的话)并提交。Docker大体上按照如下流程执行 Dockerfile 中的指令:

  • Docker从基础镜像运行一个容器。
  • 执行一条指令,对容器作出修改。
  • 执行类似 docker commit 的操作,提交一个新的镜像层。
  • Docker再基于刚提交的镜像运行一个新容器。
  • 执行 Dockerfile 中的下一条指令,直到所有指令都执行完毕。

从上面也可以看出,如果你的 Dockerfile 由于某些原因(如某条指令失败了)没有正常结束,那么你将得到了一个可以使用的镜像。这对调试非常有帮助:可以基于该镜像运行一个具备交互功能的容器,使用最后创建的镜像对失败的指令进行调试。

只要可能,Docker将重用中间镜像(缓存),显著加快Docker构建过程。这由控制台输出中的Using cache消息进行指示。

$ 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 指定的映像不需要有父链,并且可以从其他注册中心提取。

构建完成后,就可以考虑将存储库推送到其注册中心了。


02 格式

如下,即为 Dockerfile 的格式:

# Comment
INSTRUCTION arguments

Dockerfile 中的指令是不区分大小写的。然而,习惯上要将它大写,以便更容易地与参数区分开来。

Docker按照Dockerfile 中指令的顺序从上到下逐一执行。一般来说,Dockerfile 必须以指定用于构建过程的基础镜像的 FROM 指令开始,而 ARG 指令是唯一可以在 FROM 指令之前使用的指令,FROM 指令之前可以有一条或多条 ARG 指令,用于声明 Dockerfile 中的 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

Dockerfile 中支持如下的解析器指令:

  • escape

04 转义指令

# escape=\ (backslash)

或者

# escape=` (backtick)

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>

解决上述问题的一种方式是使用 / 作为 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

大括号语法也支持如下的标准的 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

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, 而不是 bye ,而 ghi 的值将被设置为 bye ,为什么呢?因为在第一个 ENV 指令中,环境变量 abc 的值被设置为字符串 hello;在第二个 ENV 指令中,先将已经存在的环境变量 abc 的值重新设置为字符串 bye,此时,整个 ENV 指令还未执行结束,环境变量 abc 的值其实还是字符串 hello,然后再将环境变量 abc 的值赋给环境变量 def ,至此,第二个 ENV 指令执行完毕,环境变量 def 的值就是字符串 hello 了,而环境变量 abc 的值也变为字符串 bye;而第三个 ENV 指令将环境变量 abc 的值赋给环境变量 ghi ,所以,环境变量 ghi 的赋值即为字符串 bye

06 .dockerignore文件

docker CLI 将构建上下文发送到Docker的守护进程之前,会检查构建上下文的根路径下是否存在名为 .dockerignore 的文件,如果这个文件存在,docker CLI 在上传将构建上下文时会把能被 .dockerignore 中的匹配模式匹配到的文件和目录排除在外。这有助于避免将一些大的或者敏感的文件和目录发送到Docker守护进程,或者使用 ADD 或者 COPY 指令添加那些文件和目录到镜像中。

docker CLI.dockerignore 文件解释为一个以换行符分割的模式列表。在进行模式匹配时,构建上下文的根目录被当作工作目录和根目录。例如,模式 /foo/barfoo/bar 的作用是相同的,都是排除 PATH的子目录或通过 URL 指定的git存储库中名为 foo 的目录下面一个叫 bar 的文件或者目录。除此之外,不排除任何其它的文件和目录。

.dockerignore 文件中以 # 开头行被认为是注释,docker CLI 会将其忽略。

下面是一个 .dockerignore 文件的例子:

# comment
*/temp*
*/*/temp*
temp?

该文件将导致以下构建行为:

匹配模式行为
# comment忽略
*/temp*排除根目录下的子目录中以 temp 开头的文件或目录。例如,文本文件 /somedir/temporary.txt 会被排除,目录 /somedir/temp 也会被排除。
*/*/temp*排除根目录下深度为2的子目录中任何以 temp 开头的文件或目录。例如,文本文件 /first_dir/second_dir/temporary.txt 会被排除,目录 /first_dir/second_dir/.temp 也会被排除。
temp?排除根目录下名字以 temp 开头,且后接1个字符的文件和目录,例如 /tempa.txt/tempb 均会被排除。

.dockerignore 文件中的模式匹配使用的是 Go 语言中的 filepath.Match 规则。在预处理步骤中,会使用 Go 语言中的 filepath.Clean 删除前导或后置的空格、 ... 。预处理步骤以后,空白行将被忽略。

除了 Go 语言中的 filepath.Match 规则,Docker还支持一个特殊的通配符 ** ,这个通配符可以匹配任意数量的目录(包括 0个)。例如,**/*.go 将排除构建上下文根目录及其所有子目录下以 .go 结尾的文件及目录。

以感叹号 ! 开始的行可用于排除的例外情况。下面是一个使用这种机制的例子:

*.md
!README.md

结果将会是除过 README.md 文件的所有其他 markdown 文件都会被从构建上下文中排除。

例外模式 ! 的位置会影响 .dockerignore 中的模式匹配的行为:.dockerignore 中匹配特定文件的最后一行(非注释行)将决定其匹配到的文件是被包含还是排除。考虑下面的这个例子:

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

结果将会是,所有名称不以 README 开头的 markdown 文件和 README-secret.md 这个文件都会被从构建上下文中排除。

再来看看下面这个例子:

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

结果将会是,所有名称以 README 开头的markdown 文件都会被包含近构建上下文中。上例中,中间一行 README-secret.md 是不起作用的,因为 !README*.md 这个匹配模式匹配到的结果中包含 README-secret.md

甚至可以使用 .dockerignore 文件来排除 Dockerfile.dockerignore 文件。但是这些文件仍然会被发送到Docker守护进程,因为Docker守护进程需要这些文件来正常工作。但是 ADDCOPY 指令不会将它们复制到镜像中去。

最后,您可能希望直接指定在构建上下文中包含哪些文件,而不是排除哪些文件。要实现这一点,请指定 * 作为第一个匹配模式,然后是一个或多个 ! 例外模式。

注意:由于历史原因,. 这种匹配模式将会被忽略。

  • 0
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Dockerfile 是用于构建 Docker 镜像的一种文本文件。它包含了一系列的指令,用于描述构建镜像的步骤和配置。 下面是 Dockerfile 的一些常见指令和解释: 1. FROM:指定基础镜像,用于构建当前镜像。例如,可以使用 `FROM ubuntu:latest` 表示基于最新版本的 Ubuntu 镜像构建。 2. RUN:在镜像中执行命令。可以使用多个 RUN 指令来执行多个命令,每个 RUN 指令都会在前一个指令的基础上创建新的镜像层。 3. COPY:将文件或目录从主机复制到镜像中。可以使用 `COPY <src> <dest>` 来指定源文件/目录和目标路径。 4. ADD:类似于 COPY,但功能更强大。它可以复制远程文件、解压缩文件等。尽量使用 COPY 来避免不必要的复杂性。 5. WORKDIR:设置工作目录,后续的命令都会在该目录下执行。可以使用 `WORKDIR /path/to/directory` 来指定工作目录。 6. ENV:设置环境变量。可以使用 `ENV <key>=<value>` 来设置环境变量的键值对。 7. EXPOSE:声明容器运行时需要监听的端口。例如,可以使用 `EXPOSE 80` 来声明容器将监听 80 端口。 8. CMD:指定容器启动时要执行的命令。可以使用多个 CMD 指令,但只有最后一个生效。如果在运行容器时提供了命令,则 CMD 中的命令会被覆盖。 以上是一些常见的 Dockerfile 指令,还有其他指令如 ENTRYPOINT、LABEL 等,可根据具体需求进行使用。编写一个 Dockerfile 可以根据应用的需求进行定制化配置和设置,最终可以通过 Dockerfile 构建出一个可执行的 Docker 镜像。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值