Too young, too simple. Sometimes, naive & stupid

Dockerfile参考

用法

docker build命令Dockerfile根据上下文构建image。构建的上下文是指定位置PATH或的文件URL。这PATH是本地文件系统上的目录。URL是一个Git存储库位置。

递归处理上下文。因此,a PATH包括所有子目录,URL包括存储库及其子模块。此示例显示了使用当前目录作为上下文的构建命令:

1
2
3
$ docker build .
Sending build context to Docker daemon 6.51 MB
...

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

警告:不要用你的根目录下,/作为PATH因为它会导致生成到您的硬盘驱动器的全部内容传输到docker守护进程。

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

Dockerfile被调用Dockerfile并位于上下文的根中。您可以使用-f标志docker build指向文件系统中任何位置的Dockerfile。

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

如果构建成功,您可以指定存储库和标记以保存新image:

1
$ docker build -t shykes/myapp .

要在构建后将映像标记为多个存储库,请在-t运行build命令时添加多个参数:

1
$ docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .

在Docker守护程序运行其中的指令之前Dockerfile,它会执行初步验证Dockerfile并在语法不正确时返回错误:

1
2
3
$ docker build -t test/myapp .
Sending build context to Docker daemon 2.048 kB
Error response from daemon: Unknown instruction: RUNCMD

Docker守护程序Dockerfile逐个运行指令,在必要时将每条指令的结果提交给新image,最后输出新image的ID。Docker守护程序将自动清理您发送的上下文。

请注意,每条指令都是独立运行的,会导致创建新image - 因此RUN cd /tmp不会对下一条指令产生任何影响。

只要有可能,Docker将重新使用中间image(缓存),以docker build显着加速该过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ 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 xxx@xxx.xx
---> 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

构建缓存仅用于具有本地父链的。这意味着这些image是由以前的版本创建的,或者加载了整个image链docker load。如果您希望使用特定image的构建缓存,可以使用--cache-from选项指定它。指定的image --cache-from不需要具有父链,可以从其他注册表中提取。

格式

这是以下格式Dockerfile

1
2
# Comment
INSTRUCTION arguments

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

Docker Dockerfile按顺序运行指令。一个Dockerfile 必须用FROM指令启动。该FROM指令指定您正在构建的BaseimageFROM可以仅由一个或多个前面ARG的指令,其声明了在使用的参数FROM中的行Dockerfile

Docker 将以条目开头的#视为注释,除非该行是有效的parser-directives#行中任何其他位置的标记都被视为参数。这允许这样的陈述:

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

注释中不支持行继续符。

directive

解析器指令是可选的,并且影响Dockerfile处理a 中后续行的方式。解析器指令不会向构建添加图层,也不会显示为构建步骤。解析器指令在表单中被写为特殊类型的注释# directive=value。单个指令只能使用一次。

一旦处理了注释,空行或构建器指令,Docker就不再查找解析器指令。相反,它将格式化为解析器指令的任何内容视为注释,并且不会尝试验证它是否可能是解析器指令。因此,所有解析器指令必须位于a的顶部Dockerfile

解析器器指令不区分大小写。但是,惯例是它们是小写的。约定还包括任何解析器指令后面的空行。解析器指令不支持行继续符。

由于这些规则,以下示例均无效:

由于行继续而无效:

1
2
# direc \
tive=value

由于出现两次无效:

1
2
3
4
# directive=value1
# directive=value2

FROM ImageName

由于在构建器指令后出现而被视为注释:

1
2
FROM ImageName
# directive=value

由于在不是解析器指令的注释之后出现而被视为注释:

1
2
3
# About my dockerfile
# directive=value
FROM ImageName

由于未被识别,未知指令被视为注释。此外,由于出现在不是解析器指令的注释之后,已知指令被视为注释。

1
2
# unknowndirective=value
# knowndirective=value

解析器指令中允许使用非换行空格。因此,以下几行都是相同的:

1
2
3
4
5
#directive=value
# directive =value
# directive= value
# directive = value
# dIrEcTiVe=value

支持以下解析器指令:

  • escape

escape

1
# escape=\ (backslash)

要么

1
# escape=` (backtick)

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

1
2
3
FROM microsoft/nanoserver
COPY testfile.txt c:\\
RUN dir c:\

结果是:

1
2
3
4
5
6
7
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

1
2
3
4
5
# escape=`

FROM microsoft/nanoserver
COPY testfile.txt c:\
RUN dir c:\

结果是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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>

Environment replacement

环境变量(声明的ENV),也可以在特定指令作为变量用来被解释 Dockerfile。还会处理转义,以便将类似变量的语法包含在字面上。

环境变量Dockerfile$variable_nameor表示${variable_name}。它们被等效地处理,并且括号语法通常用于解决具有没有空格的变量名称的问题,例如${foo}_bar

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

  • ${variable:-word}表示如果variable设置后,结果将是该值。如果variable未设置则结果将是word
  • ${variable:+word}表示如果variable设置,那么word将是结果,否则结果是空字符串。

在所有情况下,word可以是任何字符串,包括其他环境变量。

通过\在变量之前添加来实现转义:\$foo或者\${foo},例如,将分别转换为$foo${foo}文本。

示例(解析后的表示显示在#)之后:

1
2
3
4
5
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指令支持环境变量,即使与上面列出的任何指令结合使用也是如此。

环境变量替换将在整个指令中对每个变量使用相同的值。换句话说,在这个例子中:

1
2
3
ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc

将导致def具有值hello,而不是bye。但是, ghi将具有值,bye因为它不是设置abc为的相同指令的一部分bye

.dockerignore

在docker CLI将上下文发送到docker守护程序之前,它会查找.dockerignore在上下文的根目录中指定的文件。如果此文件存在,CLI将修改上下文以排除与其中的模式匹配的文件和目录。这有助于避免不必要地将大型或敏感文件和目录发送到守护程序,并可能使用ADD或将它们添加到映像中COPY

CLI将.dockerignore文件解释为新行分隔的模式列表,类似于Unix shell的文件globs。出于匹配的目的,上下文的根被认为是工作目录和根目录。例如,模式 /foo/barfoo/bar两者都排除bar 在位于的git存储库的foo子目录PATH或根目录中命名的文件或目录URL。两者都不包括任何其他内容。

如果.dockerignore文件中的行以第#1列开头,则此行被视为注释,并在CLI解释之前被忽略。

这是一个示例.dockerignore文件:

1
2
3
4
# comment
*/temp*
*/*/temp*
temp?

此文件导致以下构建行为:

规则 行为
# comment 忽略。
*/temp* 排除名称以temp根目录的任何直接子目录开头的文件和目录。例如,/somedir/temporary.txt排除普通文件,目录也是如此/somedir/temp
*/*/temp* 排除temp从根目录下两级开始的任何子目录开始的文件和目录。例如,/somedir/subdir/temporary.txt被排除在外。
temp? 排除根目录中的文件和目录,其名称是单字符扩展名temp。例如,/tempa/tempb被排除在外。

匹配是使用Go的 filepath.Match规则完成的。预处理步骤去除了开头和结尾的空白,并消除...使用Go的元素 filepath.Clean。预处理后为空的行将被忽略。

除了Go的filepath.Match规则,Docker还支持一个**匹配任意数量目录(包括零)的特殊通配符字符串。例如,**/*.go将排除.go 在所有目录中找到的以该结尾的所有文件,包括构建上下文的根。

!(感叹号)开头的行可用于对排除项进行例外处理。以下是.dockerignore使用此机制的示例文件:

1
2
*.md
!README.md

README.md上下文,所有降价文件除外

!异常规则的放置会影响行为:.dockerignore匹配特定文件的最后一行确定是包含还是排除。请考虑以下示例:

1
2
3
*.md
!README*.md
README-secret.md

除了以外的README文件,上下文中不包含markdown文件 README-secret.md

现在考虑这个例子:

1
2
3
*.md
README-secret.md
!README*.md

包含所有README文件。中间线没有效果,因为 !README*.md匹配README-secret.md并且最后。

您甚至可以使用.dockerignore文件来排除Dockerfile.dockerignore文件。这些文件仍然发送到守护程序,因为它需要它们来完成它的工作。但是ADDCOPY指令不会将它们复制到image中。

最后,您可能希望指定要包含在上下文中的文件,而不是要排除的文件。要实现此目的,请指定*第一个模式,然后指定一个或多个!异常模式。

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

FROM

1
FROM <image> [AS <name>]

要么

1
FROM <image>[:<tag>] [AS <name>]

要么

1
FROM <image>[@<digest>] [AS <name>]

FROM指令初始化新的构建阶段并为后续指令设置Baseimage。因此,有效Dockerfile必须以FROM指令开始。image可以是任何有效image - 通过从dockerrepos提取image来启动它尤其容易。

  • ARG是先于仅指示FROMDockerfile
  • FROM可以在单个内容中多次出现Dockerfile以创建多个image,或者使用一个构建阶段作为另一个构建阶段的依赖项。只需在每条新FROM指令之前记下提交输出的最后一个imageID 。每条FROM指令都清除先前指令创建的任何状态。
  • 可选地,可以通过添加AS nameFROM指令来将 名称赋予新的构建阶段。该名称可用于后续FROMCOPY --from=<name|index>指令,以引用此阶段构建的image。
  • tagdigest值是可选的。如果省略其中任何一个,则构建器默认采用latest标记。如果找不到tag值,构建器将返回错误。

了解ARG和FROM如何交互

FROM说明支持由ARG 第一个之前发生的任何指令声明的变量FROM

1
2
3
4
5
6
ARG  CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD /code/run-app

FROM extras:${CODE_VERSION}
CMD /code/run-extras

ARG生命之前,FROM是一个构建阶段之外,因此它不能在之后的任何指令使用FROM。要ARG在第一次FROM使用之前使用声明的默认值,请在ARG构建阶段内使用没有值的指令:

1
2
3
4
ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version

RUN

RUN有两种形式:

  • RUN <command>shell表单,该命令在shell中运行,默认情况下/bin/sh -c在Linux或cmd /S /CWindows 上运行)
  • RUN ["executable", "param1", "param2"]执行形式)

RUN指令将在当前image之上的新层中执行任何命令并提交结果。生成的提交image将用于下一步Dockerfile

分层RUN指令和生成提交符合Docker的核心概念,可以从image历史中的任何点创建容器,就像源代码控制一样。

EXEC形式使得能够避免壳串改写(munging),RUN 使用不包含指定壳可执行基本image命令。

可以使用 SHELL命令更改shell表单的默认shell。

shell形式中,您可以使用\(反斜杠)将单个RUN指令继续到下一行。例如,考虑以下两行:

1
2
RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'

它们一起相当于这一行:

1
RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'

注意:要使用除“/ bin / sh”之外的其他shell,请使用传入所需shell 的exec表单。例如,RUN ["/bin/bash", "-c", "echo hello"]

注意exec表单被解析为JSON数组,这意味着您必须使用双引号(“)来围绕单词而不是单引号(’)。

注意:与shell表单不同,exec表单不会调用命令shell。这意味着不会发生正常的shell处理。例如, RUN [ "echo", "$HOME" ]不会对变量进行替换$HOME。如果你想要shell处理,那么要么使用shell表单,要么直接执行shell,例如:RUN [ "sh", "-c", "echo $HOME" ]。当使用exec表单并直接执行shell时,就像shell表单的情况一样,它是执行环境变量扩展的shell,而不是docker。

注意:在JSON表单中,必须转义反斜杠。这在反斜杠是路径分隔符的Windows上尤为重要。由于不是有效的JSON,以下行将被视为shell表单,并以意外方式失败:RUN ["c:\windows\system32\tasklist.exe"] 此示例的正确语法是:RUN ["c:\\windows\\system32\\tasklist.exe"]

RUN在下一次构建期间,指令的缓存不会自动失效。类似指令的缓存RUN apt-get dist-upgrade -y将在下一次构建期间重用。例如,RUN可以通过使用--no-cache 标志使指令的高速缓存无效docker build --no-cache

RUN指令的高速缓存可以通过ADD

CMD

CMD指令有三种形式:

  • CMD ["executable","param1","param2"]执行形式,这是首选形式)
  • CMD ["param1","param2"](作为ENTRYPOINT的默认参数
  • CMD command param1 param2贝壳形式)

a中只能有一条CMD指令Dockerfile。如果列出多个,CMD 则只有最后一个CMD生效。

a的主要目的CMD是为执行容器提供默认值。这些默认值可以包含可执行文件,也可以省略可执行文件,在这种情况下,您还必须指定一条ENTRYPOINT 指令。

注意:如果CMD用于为ENTRYPOINT 指令提供默认参数,则应使用JSON数组格式指定CMDENTRYPOINT指令。

注意exec表单被解析为JSON数组,这意味着您必须使用双引号(“)来围绕单词而不是单引号(’)。

注意:与shell表单不同,exec表单不会调用命令shell。这意味着不会发生正常的shell处理。例如, CMD [ "echo", "$HOME" ]不会对变量进行替换$HOME。如果你想要shell处理,那么要么使用shell表单,要么直接执行shell,例如:CMD [ "sh", "-c", "echo $HOME" ]。当使用exec表单并直接执行shell时,就像shell表单的情况一样,它是执行环境变量扩展的shell,而不是docker。

在shell或exec格式中使用时,该CMD指令设置在运行映像时要执行的命令。

如果你使用的是shell的形式CMD,那么<command>将执行 /bin/sh -c

1
2
FROM ubuntu
CMD echo "This is a test." | wc -

如果要在 <command> 没有 shell 的情况下运行,则必须将该命令表示为JSON数组,并提供可执行文件的完整路径。 此数组形式是首选格式CMD。任何其他参数必须在数组中单独表示为字符串:

1
2
FROM ubuntu
CMD ["/usr/bin/wc","--help"]

如果您希望容器每次都运行相同的可执行文件,那么您应该考虑ENTRYPOINT结合使用CMD

如果用户指定了参数,docker run那么它们将覆盖指定的默认值CMD

注意:不要混淆RUN使用CMDRUN实际上运行一个命令并提交结果; CMD在构建时不执行任何操作,但指定image的预期命令。

LABEL

1
LABEL <key>=<value> <key>=<value> <key>=<value> ...

LABEL指令将元数据添加到image。 LABEL是键值对。要在LABEL值中包含空格,请使用引号和反斜杠,就像在命令行解析中一样。一些用法示例:

1
2
3
4
5
LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."

image可以有多个标签。您可以在一行中指定多个标签。在Docker 1.10之前,这减小了最终image的大小,但现在不再是这种情况了。您仍然可以选择在单个指令中指定多个标签,方法有以下两种:

1
2
3
4
LABEL multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \
multi.label2="value2" \
other="value3"

基本或父image中包含的标签(FROM线中的image)由image继承。如果标签已存在但具有不同的值,则最近应用的值将覆盖任何先前设置的值。

要查看image的标签,请使用该docker inspect命令。

1
2
3
4
5
6
7
8
9
"Labels": {
"com.example.vendor": "ACME Incorporated"
"com.example.label-with-value": "foo",
"version": "1.0",
"description": "This text illustrates that label-values can span multiple lines.",
"multi.label1": "value1",
"multi.label2": "value2",
"other": "value3"
},

EXPOSE

1
EXPOSE <port> [<port>/<protocol>...]

EXPOSE指令通知Docker容器在运行时侦听指定的网络端口。您可以指定端口是侦听TCP还是UDP,如果未指定协议,则默认为TCP。

EXPOSE指令实际上没有发布端口。它作为构建映像的人和运行容器的人之间的一种文档,用于发布要发布的端口。要在运行容器时实际发布端口,请使用-p标志on docker run 来发布和映射一个或多个端口,或使用-P标志发布所有公开的端口并将它们映射到高阶端口。

默认情况下,EXPOSE假定为TCP。您还可以指定UDP:

1
EXPOSE 80/udp

要在TCP和UDP上公开,请包含两行:

1
2
EXPOSE 80/tcp
EXPOSE 80/udp

在这种情况下,如果使用docker run -P ,端口将为TCP暴露一次,对UDP则暴露一次。请记住,-P在主机上使用短暂的高阶主机端口,因此TCP和UDP的端口不同。

无论EXPOSE设置如何,您都可以使用-p标志在运行时覆盖它们。例如

1
docker run -p 80:80/tcp -p 80:80/udp ...

ENV

1
2
ENV <key> <value>
ENV <key>=<value> ...

ENV指令将环境变量<key>设置为该值 <value>。此值将在构建阶段中所有后续指令的环境中。

ENV指令有两种形式。第一种形式,ENV <key> <value>将一个变量设置为一个值。第一个空格后的整个字符串将被视为<value>- 包括空格字符。该值将针对其他环境变量进行解释,因此如果未对其进行转义,则将删除引号字符。

第二种形式ENV <key>=<value> ...允许一次设置多个变量。请注意,第二种形式在语法中使用等号(=),而第一种形式则不然。与命令行解析一样,引号和反斜杠可用于在值内包含空格。

例如:

1
2
ENV myName="John Doe" myDog=Rex\ The\ Dog \
myCat=fluffy

1
2
3
ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy

将在最终image中产生相同的结果。

ENV当从生成的image运行容器时,使用的环境变量将保持不变。您可以使用docker inspect,查看值,并使用它们进行更改docker run --env <key>=<value>

注意:环境持久性可能会导致意外的副作用。例如,设置ENV DEBIAN_FRONTEND noninteractive可能会使基于Debian的image上的apt-get用户感到困惑。要为单个命令设置值,请使用 RUN <key>=<value> <command>

ADD

ADD有两种形式:

  • ADD [--chown=<user>:<group>] <src>... <dest>
  • ADD [--chown=<user>:<group>] ["<src>",... "<dest>"] (包含空格的路径需要此表单)

注意:该--chown功能仅在用于构建Linux容器的Dockerfiles上受支持,并且不适用于Windows容器。由于用户和组所有权概念不能在Linux和Windows之间进行转换,因此使用/etc/passwd/etc/group将用户名和组名转换为ID会限制此功能仅适用于基于Linux OS的容器。

ADD指令从中复制新文件,目录或远程文件URL <src> ,并将它们添加到路径上image的文件系统中<dest>

<src>可以指定多个资源,但如果它们是文件或目录,则它们的路径将被解释为相对于构建上下文的源。

每个都<src>可能包含通配符,匹配将使用Go的 filepath.Match规则完成。例如:

1
2
ADD hom* /mydir/        # adds all files starting with "hom"
ADD hom?.txt /mydir/ # ? is replaced with any single character, e.g., "home.txt"

<dest>是一个绝对路径,或相对于一个路径WORKDIR,到其中的源将在目标容器内进行复制。

1
2
ADD test relativeDir/          # adds "test" to `WORKDIR`/relativeDir/
ADD test /absoluteDir/ # adds "test" to /absoluteDir/

添加包含特殊字符(例如[])的文件或目录时,需要按照Golang规则转义这些路径,以防止它们被视为匹配模式。例如,要添加名为的文件arr[0].txt,请使用以下命令;

1
ADD arr[[]0].txt /mydir/    # copy a file named "arr[0].txt" to /mydir/

除非可选--chown标志指定给定用户名,组名或UID / GID组合以请求所添加内容的特定所有权,否则将使用UID和GID为0创建所有新文件和目录。--chown标志的格式允许用户名和组名字符串或任意组合的直接整数UID和GID。提供没有组名的用户名或没有GID的UID将使用与GID相同的数字UID。如果提供了用户名或组名,则容器的根文件系统 /etc/passwd/etc/group文件将分别用于执行从名称到整数UID或GID的转换。以下示例显示了该--chown标志的有效定义:

1
2
3
4
ADD --chown=55:mygroup files* /somedir/
ADD --chown=bin files* /somedir/
ADD --chown=1 files* /somedir/
ADD --chown=10:11 files* /somedir/

如果容器根文件系统不包含任何文件/etc/passwd/etc/group文件,并且--chown 标志中使用了用户名或组名,则构建将在ADD操作上失败。使用数字ID不需要查找,也不依赖于容器根文件系统内容。

<src>远程文件URL 的情况下,目标将具有600的权限。如果正在检索的远程文件具有HTTPLast-Modified标头,则来自该标头的时间戳将用于设置mtime目标文件。但是,与在处理期间处理的任何其他文件一样ADDmtime将不包括在确定文件是否已更改且应更新缓存中。

注意:如果通过传递DockerfileSTDIN(docker build - < somefile)进行构建,则没有构建上下文,因此Dockerfile 只能包含基于URL的ADD指令。您还可以通过STDIN :( docker build - < archive.tar.gz)传递压缩存档Dockerfile,该存档位于存档的根目录,其余存档将用作构建的上下文。

注意:如果您的网址文件都使用认证保护,您将需要使用RUN wgetRUN curl或使用其它工具从容器内的ADD指令不支持验证。

注意ADD如果内容<src>已更改,则第一个遇到的指令将使来自Dockerfile的所有后续指令的高速缓存无效。这包括使缓存无效以获取RUN指令。

ADD 遵守以下规则:

  • <src>路径必须是内部语境的构建; 你不能ADD ../something /something,因为a的第一步 docker build是将上下文目录(和子目录)发送到docker守护程序。
  • 如果<src>是URL并且<dest>不以尾部斜杠结尾,则从URL下载文件并将其复制到<dest>
  • 如果<src>是URL并且<dest>以尾部斜杠结尾,则从URL推断文件名并将文件下载到<dest>/<filename>。例如,ADD http://example.com/foobar /将创建该文件/foobar。URL必须具有非常重要的路径,以便在这种情况下可以发现适当的文件名(http://example.com 不起作用)。
  • 如果<src>是目录,则复制目录的全部内容,包括文件系统元数据。

注意:不复制目录本身,只复制其内容。

  • 如果<src>是以可识别的压缩格式(identity,gzip,bzip2或xz)的本地 tar存档,则将其解压缩为目录。从资源远程网址解压。复制或解压缩目录时,它具有与之相同的行为tar -x,结果是:

    1. 无论在目的地路径上存在什么,
    2. 源树的内容,在逐个文件的基础上解决了有利于“2.”的冲突。

    注意:文件是否被识别为可识别的压缩格式仅基于文件的内容而不是文件的名称来完成。例如,如果一个空文件碰巧结束,.tar.gz这将不会被识别为压缩文件,并且不会生成任何类型的解压缩错误消息,而是将文件简单地复制到目标。

  • 如果<src>是任何其他类型的文件,则将其与元数据一起单独复制。在这种情况下,如果<dest>以尾部斜杠结尾/,则将其视为目录,<src>并将写入内容<dest>/base(<src>)

  • 如果<src>直接或由于使用通配符指定了多个资源,则<dest>必须是目录,并且必须以斜杠结尾/

  • 如果<dest>不以尾部斜杠结束,则将其视为常规文件,<src>并将写入其中的内容<dest>

  • 如果<dest>不存在,则会在其路径中创建所有缺少的目录。

COPY

COPY有两种形式:

  • COPY [--chown=<user>:<group>] <src>... <dest>
  • COPY [--chown=<user>:<group>] ["<src>",... "<dest>"] (包含空格的路径需要此表单)

注意:该--chown功能仅在用于构建Linux容器的Dockerfiles上受支持,并且不适用于Windows容器。由于用户和组所有权概念不能在Linux和Windows之间进行转换,因此使用/etc/passwd/etc/group将用户名和组名转换为ID会限制此功能仅适用于基于Linux OS的容器。

COPY指令从中复制新文件或目录<src> ,并将它们添加到路径中容器的文件系统中<dest>

<src>可以指定多个资源,但文件和目录的路径将被解释为相对于构建上下文的源。

每个都<src>可能包含通配符,匹配将使用Go的 filepath.Match规则完成。例如:

1
2
COPY hom* /mydir/        # adds all files starting with "hom"
COPY hom?.txt /mydir/ # ? is replaced with any single character, e.g., "home.txt"

<dest>是一个绝对路径,或相对于一个路径WORKDIR,到其中的源将在目标容器内进行复制。

1
2
COPY test relativeDir/   # adds "test" to `WORKDIR`/relativeDir/
COPY test /absoluteDir/ # adds "test" to /absoluteDir/

复制包含特殊字符(例如[])的文件或目录时,需要按照Golang规则转义这些路径,以防止它们被视为匹配模式。例如,要复制名为的文件arr[0].txt,请使用以下命令;

1
COPY arr[[]0].txt /mydir/    # copy a file named "arr[0].txt" to /mydir/

除非可选--chown标志指定给定用户名,组名或UID / GID组合以请求复制内容的特定所有权,否则将使用UID和GID为0创建所有新文件和目录。--chown标志的格式允许用户名和组名字符串或任意组合的直接整数UID和GID。提供没有组名的用户名或没有GID的UID将使用与GID相同的数字UID。如果提供了用户名或组名,则容器的根文件系统 /etc/passwd/etc/group文件将分别用于执行从名称到整数UID或GID的转换。以下示例显示了该--chown标志的有效定义:

1
2
3
4
COPY --chown=55:mygroup files* /somedir/
COPY --chown=bin files* /somedir/
COPY --chown=1 files* /somedir/
COPY --chown=10:11 files* /somedir/

如果容器根文件系统不包含任何文件/etc/passwd/etc/group文件,并且--chown 标志中使用了用户名或组名,则构建将在COPY操作上失败。使用数字ID不需要查找,也不依赖于容器根文件系统内容。

注意:如果使用STDIN(docker build - < somefile)构建,则没有构建上下文,因此COPY无法使用。

(可选)COPY接受一个标志--from=<name|index>,该标志可用于将源位置设置为FROM .. AS <name>将用于替代用户发送的构建上下文的先前构建阶段(使用其创建)。该标志还接受为FROM指令启动的所有先前构建阶段分配的数字索引 。如果找不到具有指定名称的构建阶段,则尝试使用具有相同名称的image。

COPY 遵守以下规则:

  • <src>路径必须是内部语境的构建; 你不能COPY ../something /something,因为a的第一步 docker build是将上下文目录(和子目录)发送到docker守护程序。
  • 如果<src>是目录,则复制目录的全部内容,包括文件系统元数据。

注意:不复制目录本身,只复制其内容。

  • 如果<src>是任何其他类型的文件,则将其与元数据一起单独复制。在这种情况下,如果<dest>以尾部斜杠结尾/,则将其视为目录,<src>并将写入内容<dest>/base(<src>)
  • 如果<src>直接或由于使用通配符指定了多个资源,则<dest>必须是目录,并且必须以斜杠结尾/
  • 如果<dest>不以尾部斜杠结束,则将其视为常规文件,<src>并将写入其中的内容<dest>
  • 如果<dest>不存在,则会在其路径中创建所有缺少的目录。

ENTRYPOINT

ENTRYPOINT有两种形式:

  • ENTRYPOINT ["executable", "param1", "param2"]exec形式,首选)
  • ENTRYPOINT command param1 param2shell形式)

ENTRYPOINT允许您配置将作为可执行文件运行的容器。

例如,以下将使用其默认内容启动nginx,侦听端口80:

1
docker run -i -t --rm -p 80:80 nginx

命令行参数docker run <image>将附加在exec表单中的所有元素之后ENTRYPOINT,并将覆盖使用的所有指定元素CMD。这允许将参数传递给入口点,docker run <image> -d 即将-d参数传递给入口点。您可以ENTRYPOINT使用docker run --entrypoint 标志覆盖指令。

所述shell形式防止任何CMDrun被使用命令行参数,但是具有你的缺点ENTRYPOINT将被开始作为一个子命令/bin/sh -c,其不通过信号。这意味着可执行文件将不是容器PID 1- 并且不会收到Unix信号 - 因此您的可执行文件将不会收到 SIGTERM来自docker stop <container>

只有意志中的最后一条ENTRYPOINT指令Dockerfile才有效。

exec ENTRYPOINT示例

您可以使用exec形式ENTRYPOINT设置相当稳定的默认命令和参数,然后使用任一形式CMD设置更可能更改的其他默认值。

1
2
3
FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]

运行容器时,您可以看到这top是唯一的进程:

1
2
3
4
5
6
7
8
9
$ docker run -it --rm --name test  top -H
top - 08:25:00 up 7:27, 0 users, load average: 0.00, 0.01, 0.05
Threads: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.1 us, 0.1 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem: 2056668 total, 1616832 used, 439836 free, 99352 buffers
KiB Swap: 1441840 total, 0 used, 1441840 free. 1324440 cached Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 19744 2336 2080 R 0.0 0.1 0:00.04 top

要进一步检查结果,您可以使用docker exec

1
2
3
4
$ docker exec -it test ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 2.6 0.1 19752 2352 ? Ss+ 08:24 0:00 top -b -H
root 7 0.0 0.1 15572 2164 ? R+ 08:25 0:00 ps aux

并且您可以优雅地请求docker stop test来关闭top

以下Dockerfile显示使用ENTRYPOINT在前台运行Apache(即as PID 1):

1
2
3
4
5
FROM debian:stable
RUN apt-get update && apt-get install -y --force-yes apache2
EXPOSE 80 443
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

如果需要为单个可执行文件编写启动脚本,可以使用execgosu 命令确保最终的可执行文件接收Unix信号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/env bash
set -e

if [ "$1" = 'postgres' ]; then
chown -R postgres "$PGDATA"

if [ -z "$(ls -A "$PGDATA")" ]; then
gosu postgres initdb
fi

exec gosu postgres "$@"
fi

exec "$@"

最后,如果您需要在关机时进行一些额外的清理(或与其他容器通信),或者协调多个可执行文件,您可能需要确保ENTRYPOINT脚本接收Unix信号,传递它们,然后执行一些更多的工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/sh
# Note: I've written this using sh so it works in the busybox container too

# USE the trap if you need to also do manual cleanup after the service is stopped,
# or need to start multiple services in the one container
trap "echo TRAPed signal" HUP INT QUIT TERM

# start service in background here
/usr/sbin/apachectl start

echo "[hit enter key to exit] or run 'docker stop <container>'"
read

# stop service and clean up here
echo "stopping apache"
/usr/sbin/apachectl stop

echo "exited $0"

如果运行此映像docker run -it --rm -p 80:80 --name test apache,则可以使用docker exec,或检查容器的进程docker top,然后请求脚本停止Apache:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ docker exec -it test ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.1 0.0 4448 692 ? Ss+ 00:42 0:00 /bin/sh /run.sh 123 cmd cmd2
root 19 0.0 0.2 71304 4440 ? Ss 00:42 0:00 /usr/sbin/apache2 -k start
www-data 20 0.2 0.2 360468 6004 ? Sl 00:42 0:00 /usr/sbin/apache2 -k start
www-data 21 0.2 0.2 360468 6000 ? Sl 00:42 0:00 /usr/sbin/apache2 -k start
root 81 0.0 0.1 15572 2140 ? R+ 00:44 0:00 ps aux
$ docker top test
PID USER COMMAND
10035 root {run.sh} /bin/sh /run.sh 123 cmd cmd2
10054 root /usr/sbin/apache2 -k start
10055 33 /usr/sbin/apache2 -k start
10056 33 /usr/sbin/apache2 -k start
$ /usr/bin/time docker stop test
test
real 0m 0.27s
user 0m 0.03s
sys 0m 0.03s

注意:您可以使用覆盖ENTRYPOINT设置--entrypoint,但这只能将二进制设置为exec(不会sh -c使用)。

注意exec表单被解析为JSON数组,这意味着您必须使用双引号(“)来围绕单词而不是单引号(’)。

注意:与shell表单不同,exec表单不会调用命令shell。这意味着不会发生正常的shell处理。例如, ENTRYPOINT [ "echo", "$HOME" ]不会对变量进行替换$HOME。如果你想要shell处理,那么要么使用shell表单,要么直接执行shell,例如:ENTRYPOINT [ "sh", "-c", "echo $HOME" ]。当使用exec表单并直接执行shell时,就像shell表单的情况一样,它是执行环境变量扩展的shell,而不是docker。

Shell ENTRYPOINT示例

您可以为ENTRYPOINT它指定一个纯字符串,它将在其中执行/bin/sh -c。此表单将使用shell处理来替换shell环境变量,并将忽略任何CMDdocker run命令行参数。为了确保能够正确地docker stop发出任何长时间运行的ENTRYPOINT可执行文件,您需要记住启动它exec

1
2
FROM ubuntu
ENTRYPOINT exec top -b

运行此image时,您将看到单个PID 1进程:

1
2
3
4
5
6
$ docker run -it --rm --name test top
Mem: 1704520K used, 352148K free, 0K shrd, 0K buff, 140368121167873K cached
CPU: 5% usr 0% sys 0% nic 94% idle 0% io 0% irq 0% sirq
Load average: 0.08 0.03 0.05 2/98 6
PID PPID USER STAT VSZ %VSZ %CPU COMMAND
1 0 root R 3164 0% 0% top -b

哪个将彻底退出docker stop

1
2
3
4
5
$ /usr/bin/time docker stop test
test
real 0m 0.20s
user 0m 0.02s
sys 0m 0.04s

如果您忘记添加exec到您的开头ENTRYPOINT

1
2
3
FROM ubuntu
ENTRYPOINT top -b
CMD --ignored-param1

然后,您可以运行它(为下一步命名):

1
2
3
4
5
6
7
$ docker run -it --name test top --ignored-param2
Mem: 1704184K used, 352484K free, 0K shrd, 0K buff, 140621524238337K cached
CPU: 9% usr 2% sys 0% nic 88% idle 0% io 0% irq 0% sirq
Load average: 0.01 0.02 0.05 2/101 7
PID PPID USER STAT VSZ %VSZ %CPU COMMAND
1 0 root S 3168 0% 0% /bin/sh -c top -b cmd cmd2
7 1 root R 3164 0% 0% top -b

您可以从输出中top看到指定ENTRYPOINT的不是PID 1

如果然后运行docker stop test,容器将不会干净地退出 - stop命令将被强制SIGKILL在超时后发送:

1
2
3
4
5
6
7
8
9
10
$ docker exec -it test ps aux
PID USER COMMAND
1 root /bin/sh -c top -b cmd cmd2
7 root top -b
8 root ps aux
$ /usr/bin/time docker stop test
test
real 0m 10.19s
user 0m 0.04s
sys 0m 0.03s

了解CMD和ENTRYPOINT如何相互作用

CMDENTRYPOINT指定运行容器时执行的命令。很少有规则描述他们的合作。

  1. Dockerfile应至少指定一个CMDENTRYPOINT命令。
  2. ENTRYPOINT 应该在将容器用作可执行文件时定义。
  3. CMD应该用作定义ENTRYPOINT命令的默认参数或在容器中执行ad-hoc命令的方法。
  4. CMD 在使用替代参数运行容器时将被覆盖。

下表显示了针对不同ENTRYPOINT/ CMD组合执行的命令:

No ENTRYPOINT ENTRYPOINT exec_entry p1_entry ENTRYPOINT [“exec_entry”,“p1_entry”]
No CMD error,not allowed / bin / sh -c exec_entry p1_entry exec_entry p1_entry
CMD [“exec_cmd”,“p1_cmd”] exec_cmd p1_cmd / bin / sh -c exec_entry p1_entry exec_entry p1_entry exec_cmd p1_cmd
CMD [“p1_cmd”,“p2_cmd”] p1_cmd p2_cmd / bin / sh -c exec_entry p1_entry exec_entry p1_entry p1_cmd p2_cmd
CMD exec_cmd p1_cmd / bin / sh -c exec_cmd p1_cmd / bin / sh -c exec_entry p1_entry exec_entry p1_entry / bin / sh -c exec_cmd p1_cmd

VOLUME

1
VOLUME ["/data"]

VOLUME指令创建具有指定名称的安装点,并将其标记为从本机主机或其他容器保存外部安装的卷。该值可以是JSON数组,VOLUME ["/var/log/"]或具有多个参数的普通字符串,例如VOLUME /var/logVOLUME /var/log /var/db

docker run命令使用基础映像中指定位置存在的任何数据初始化新创建的卷。例如,请考虑以下Dockerfile片段:

1
2
3
4
FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol

此Dockerfile会docker run生成一个image,该image将导致创建新的挂载点/myvol并将greeting文件复制到新创建的卷中。

有关指定卷的说明

关于卷中的卷,请记住以下事项Dockerfile

  • 基于Windows的容器上的卷:使用基于Windows的容器时,容器中卷的目标必须是以下之一:
    • 不存在或空目录
    • 除了之外的驱动器 C:
  • 从Dockerfile中更改卷:如果任何构建步骤在声明后更改卷内的数据,那么这些更改将被丢弃。
  • JSON格式:列表被解析为JSON数组。您必须用双引号(")而不是单引号(')括起单词。
  • 主机目录在容器运行时声明:主机目录(mountpoint)本质上是依赖于主机的。这是为了保持image的可移植性,因为不能保证给定的主机目录在所有主机上都可用。因此,您无法从Dockerfile中安装主机目录。该VOLUME指令不支持指定host-dir 参数。您必须在创建或运行容器时指定安装点。

USER

1
2
USER <user>[:<group>] or
USER <UID>[:<GID>]

USER运行的image和用于当任何指令设置的用户名(或UID)和任选的所述用户组(或GID)使用RUNCMDENTRYPOINT它后面的指令Dockerfile

警告:当用户没有主要组时,image(或下一个说明)将与该root组一起运行。

在Windows上,如果用户不是内置帐户,则必须先创建用户。这可以通过net user作为Dockerfile的一部分调用的命令来完成。

1
2
3
4
5
FROM microsoft/windowsservercore
# Create Windows user in the container
RUN net user /add patrick
# Set it for subsequent commands
USER patrick

WORKDIR

1
WORKDIR /path/to/workdir

WORKDIR指令集的工作目录对任何RUNCMDENTRYPOINTCOPYADD它后面的说明Dockerfile。如果WORKDIR不存在,即使它未在任何后续Dockerfile指令中使用,也将创建它。

WORKDIR指令可以在a中多次使用Dockerfile。如果提供了相对路径,则它将相对于前一条WORKDIR指令的路径 。例如:

1
2
3
4
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

最终pwd命令的输出Dockerfile将是 /a/b/c

WORKDIR指令可以解析先前使用的环境变量 ENV。您只能使用显式设置的环境变量Dockerfile。例如:

1
2
3
ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd

最终pwd命令的输出Dockerfile将是 /path/$DIRNAME

ARG

1
ARG <name>[=<default value>]

ARG指令定义了一个变量,用户可以docker build使用该--build-arg <varname>=<value> 标志在构建时将该变量传递给构建器。如果用户指定了未在Dockerfile中定义的构建参数,则构建会输出警告。

1
[Warning] One or more build-args [foo] were not consumed.

Dockerfile可以包括一个或多个ARG指令。例如,以下是有效的Dockerfile:

1
2
3
4
FROM busybox
ARG user1
ARG buildno
...

警告:建议不要使用构建时变量来传递密码,例如github密钥,用户凭据等docker history。使用该命令,构建时变量值对于映像的任何用户都是可见的。

默认值

ARG指令可以可选地包括一个默认值:

1
2
3
4
FROM busybox
ARG user1=someuser
ARG buildno=1
...

如果ARG指令具有默认值,并且在构建时没有传递值,则构建器将使用默认值。

范围

一个ARG变量定义进入从在其上在限定的线效果Dockerfile不从参数对命令行或其他地方使用。例如,考虑这个Dockerfile:

1
2
3
4
5
1 FROM busybox
2 USER ${user:-some_user}
3 ARG user
4 USER $user
...

用户通过调用以下内容构建此文件:

1
$ docker build --build-arg user=what_user .

第2行计算USER``some_useruser变量在后续第3行定义。第4行USER计算结果what_useruser定义,并what_user在命令行上传递值。在通过ARG指令定义之前 ,对变量的任何使用都会导致空字符串。

一个ARG指令超出范围在它被定义的构建阶段结束。要在多个阶段中使用arg,每个阶段都必须包含该ARG指令。

1
2
3
4
5
6
7
FROM busybox
ARG SETTINGS
RUN ./run/setup $SETTINGS

FROM busybox
ARG SETTINGS
RUN ./run/other $SETTINGS

使用ARG变量

您可以使用ARGENV指令指定指令可用的变量RUN。使用该ENV指令定义的环境变量 始终覆盖ARG同名指令。考虑这个Dockerfile和一个ENVARG指令。

1
2
3
4
1 FROM ubuntu
2 ARG CONT_IMG_VER
3 ENV CONT_IMG_VER v1.0.0
4 RUN echo $CONT_IMG_VER

然后,假设使用此命令构建此映像:

1
$ docker build --build-arg CONT_IMG_VER=v2.0.1 .

在这种情况下,RUN指令使用v1.0.0而不是ARG用户传递的设置:v2.0.1此行为类似于shell脚本,其中本地范围的变量覆盖作为参数传递的变量或从其定义的环境继承的变量。

使用上面的示例但不同的ENV规范,您可以在指令ARGENV指令之间创建更有用的交互:

1
2
3
4
1 FROM ubuntu
2 ARG CONT_IMG_VER
3 ENV CONT_IMG_VER ${CONT_IMG_VER:-v1.0.0}
4 RUN echo $CONT_IMG_VER

ARG指令不同,ENV值始终保留在构建的image中。考虑没有--build-arg标志的docker构建:

1
$ docker build .

使用此Dockerfile示例CONT_IMG_VER仍然保留在image中,但其值将是指令v1.0.0中第3行的默认值ENV

预定义的ARG

Docker有一组预定义ARG变量,您可以ARG在Dockerfile中使用相应的指令。

  • HTTP_PROXY
  • http_proxy
  • HTTPS_PROXY
  • https_proxy
  • FTP_PROXY
  • ftp_proxy
  • NO_PROXY
  • no_proxy

要使用它们,只需使用标志在命令行上传递它们:

1
--build-arg <varname>=<value>

默认情况下,这些预定义变量将从输出中排除 docker history。排除它们可以降低在HTTP_PROXY变量中意外泄露敏感验证信息的风险。

例如,考虑使用构建以下Dockerfile--build-arg HTTP_PROXY=http://user:pass@proxy.lon.example.com

1
2
FROM ubuntu
RUN echo "Hello World"

在这种情况下,HTTP_PROXY变量的值在docker history和中不可用, 并且不会被缓存。如果您要更改位置,并且您的代理服务器已更改为http://user:pass@proxy.sfo.example.com,则后续构建不会导致缓存未命中。

如果需要覆盖此行为,则可以通过ARG 在Dockerfile中添加语句来执行此操作,如下所示:

1
2
3
FROM ubuntu
ARG HTTP_PROXY
RUN echo "Hello World"

构建此Dockerfile时,HTTP_PROXY会保留在其中 docker history,并且更改其值会使构建缓存无效。

对构建缓存的影响

ARG变量不会像ENV变量那样持久保存在构建的image中。但是,ARG变量确实以类似的方式影响构建缓存。如果Dockerfile定义了一个ARG值与前一个版本不同的变量,则在第一次使用时会发生“缓存未命中”,而不是其定义。特别是,RUN指令后面的所有指令都 隐式ARG使用ARG变量(作为环境变量),因此可能导致高速缓存未命中。ARG除非在中包含匹配的ARG语句,否则所有预定义变量都将免于缓存Dockerfile

例如,考虑这两个Dockerfile:

1
2
3
4
5
6
1 FROM ubuntu
2 ARG CONT_IMG_VER
3 RUN echo $CONT_IMG_VER
1 FROM ubuntu
2 ARG CONT_IMG_VER
3 RUN echo hello

如果--build-arg CONT_IMG_VER=<value>在命令行中指定,则在两种情况下,第2行上的规范都不会导致高速缓存未命中; 第3行确实导致缓存未命中。ARG CONT_IMG_VER导致RUN行被识别为与运行CONT_IMG_VER=<value>echo hello 相同,因此如果<value> 更改,我们将获得缓存未命中。

考虑同一命令行下的另一个示例:

1
2
3
4
1 FROM ubuntu
2 ARG CONT_IMG_VER
3 ENV CONT_IMG_VER $CONT_IMG_VER
4 RUN echo $CONT_IMG_VER

在此示例中,高速缓存未命中发生在第3行。发生未命中是因为ENV引用ARG变量的变量值和通过命令行更改了该变量。在此示例中,该ENV 命令使image包含该值。

如果一条ENV指令覆盖了一个ARG同名的指令,比如这个Dockerfile:

1
2
3
4
1 FROM ubuntu
2 ARG CONT_IMG_VER
3 ENV CONT_IMG_VER hello
4 RUN echo $CONT_IMG_VER

第3行不会导致缓存未命中,因为值为CONT_IMG_VER常量(hello)。因此,RUN(第4行)上使用的环境变量和值在构建之间不会发生变化。

ONBUILD

1
ONBUILD [INSTRUCTION]

当image用作另一个构建的基础时,该ONBUILD指令向image添加将在稍后执行的触发指令。触发器将在下游构建的上下文中执行,就好像它已经FROM在下游指令之后立即插入一样Dockerfile

任何构建指令都可以注册为触发器。

如果要构建将用作构建其他映像的基础的映像(例如,可以使用特定于用户的配置自定义的应用程序构建环境或守护程序),这将非常有用。

例如,如果您的映像是可重用的Python应用程序构建器,则需要将应用程序源代码添加到特定目录中,并且可能需要之后调用构建脚本。你不能只是打电话ADDRUN现在,因为你还没有访问应用程序的源代码,这将是为每个应用程序生成不同的。您可以简单地为应用程序开发人员提供Dockerfile复制粘贴到他们的应用程序中的样板,但这样做效率低,容易出错且难以更新,因为它与特定于应用程序的代码混合在一起。

解决方案是用于ONBUILD在下一个构建阶段注册预先指令以便稍后运行。

以下是它的工作原理:

  1. 当遇到ONBUILD指令时,构建器会向正在构建的image的元数据添加触发器。该指令不会影响当前构建。
  2. 在构建结束时,所有触发器的列表存储在键下的image清单中OnBuild。可以使用该docker inspect命令检查它们。
  3. 稍后,可以使用该FROM指令将image用作新构建的基础 。作为处理FROM指令的一部分,下游构建器查找ONBUILD触发器,并按照它们注册的顺序执行它们。如果任何触发器失败,FROM则中止指令,这反过来导致构建失败。如果所有触发器都成功,则FROM指令完成并且构建继续照常进行。
  4. 执行后,触发器将从最终image中清除。换句话说,它们不是由“大孩子”构建继承的。

例如,您可以添加以下内容:

1
2
3
4
[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]

警告:不允许ONBUILD使用链接指令ONBUILD ONBUILD

警告ONBUILD指令可能不会触发FROMMAINTAINER指令。

STOPSIGNAL

1
STOPSIGNAL signal

STOPSIGNAL指令设置将发送到容器的系统调用信号以退出。此信号可以是与内核的系统调用表中的位置匹配的有效无符号数,例如9,或SIGNAME格式的信号名,例如SIGKILL。

HEALTHCHECK

HEALTHCHECK指令有两种形式:

  • HEALTHCHECK [OPTIONS] CMD command (通过在容器内运行命令来检查容器运行状况)
  • HEALTHCHECK NONE (禁用从基础映像继承的任何运行状况检查)

HEALTHCHECK指令告诉Docker如何测试容器以检查它是否仍在工作。即使服务器进程仍在运行,这也可以检测到陷入无限循环且无法处理新连接的Web服务器等情况。

当容器指定了运行状况检查时,除了正常状态外,它还具有运行状况。这个状态最初是starting。每当健康检查通过时,它就会变成healthy(以前的状态)。经过一定数量的连续失败后,它就变成了unhealthy

之前可以出现的选项CMD是:

  • --interval=DURATION(默认值:30s
  • --timeout=DURATION(默认值:30s
  • --start-period=DURATION(默认值:0s
  • --retries=N(默认值:3

运行状况检查将首先在容器启动后的间隔秒运行,然后在每次上一次检查完成后再间隔秒。

如果单次运行的检查花费的时间超过超时秒数,那么检查将被视为失败。

它需要重试连续的健康检查失败才能考虑容器unhealthy

start period为需要时间引导的容器提供初始化时间。在此期间探测失败将不计入最大重试次数。但是,如果在启动期间运行状况检查成功,则会将容器视为已启动,并且所有连续失败将计入最大重试次数。

HEALTHCHECKDockerfile中只能有一条指令。如果列出多个,则只有最后一个HEALTHCHECK生效。

CMD关键字后面的命令可以是shell命令(例如HEALTHCHECK CMD /bin/check-running)或exec数组(与其他Dockerfile命令一样; ENTRYPOINT有关详细信息,请参阅参考资料)。

命令的退出状态指示容器的运行状况。可能的值是:

  • 0:成功 - 容器健康且随时可用
  • 1:不健康 - 容器无法正常工作
  • 2:保留 - 不要使用此退出代码

例如,要检查每五分钟左右网络服务器能够在三秒钟内为网站的主页面提供服务:

1
2
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1

为了帮助调试失败的探测器,命令在stdout或stderr上写入的任何输出文本(UTF-8编码)将存储在运行状况中并可以使用查询 docker inspect。此类输出应保持较短(目前仅存储前4096个字节)。

当容器的运行状况更改时,将health_status生成具有新状态的事件。

HEALTHCHECK功能已添加到Docker 1.12中。

SHELL

1
SHELL ["executable", "parameters"]

SHELL指令允许覆盖用于shell形式的命令的默认shell 。Linux上的默认shell是["/bin/sh", "-c"],而在Windows上["cmd", "/S", "/C"]。该SHELL指令必须以JSON格式写入Dockerfile。

SHELL:其中有两个常用的和完全不同的原生贝壳指令是在Windows上特别有用cmdpowershell,以及提供包括候补炮弹sh

SHELL指令可以多次出现。每条SHELL指令都会覆盖所有先前的SHELL指令,并影响所有后续指令。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM microsoft/windowsservercore

# Executed as cmd /S /C echo default
RUN echo default

# Executed as cmd /S /C powershell -command Write-Host default
RUN powershell -command Write-Host default

# Executed as powershell -command Write-Host hello
SHELL ["powershell", "-command"]
RUN Write-Host hello

# Executed as cmd /S /C echo hello
SHELL ["cmd", "/S", "/C"]
RUN echo hello

以下说明可以通过影响SHELL指令时, 他们的形式在一个Dockerfile使用:RUNCMDENTRYPOINT

以下示例是在Windows上找到的常见模式,可以使用以下SHELL指令简化:

1
2
3
...
RUN powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"
...

docker调用的命令将是:

1
cmd /S /C powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"

由于两个原因,这是低效的。首先,调用一个不必要的cmd.exe命令处理器(也就是shell)。其次,shell 形式的每条RUN指令都需要额外的命令前缀。powershell -command

为了提高效率,可以采用两种机制中的一种。一种是使用RUN命令的JSON形式,例如:

1
2
3
...
RUN ["powershell", "-command", "Execute-MyCmdlet", "-param1 \"c:\\foo.txt\""]
...

虽然JSON表单是明确的,并且不使用不必要的cmd.exe,但它确实需要通过双引号和转义更加详细。替代机制是使用SHELL指令和shell表单,为Windows用户创建更自然的语法,尤其是与escape解析器指令结合使用时:

1
2
3
4
5
6
7
# escape=`

FROM microsoft/nanoserver
SHELL ["powershell","-command"]
RUN New-Item -ItemType Directory C:\Example
ADD Execute-MyCmdlet.ps1 c:\example\
RUN c:\example\Execute-MyCmdlet -sample 'hello world'

导致:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
PS E:\docker\build\shell> docker build -t shell .
Sending build context to Docker daemon 4.096 kB
Step 1/5 : FROM microsoft/nanoserver
---> 22738ff49c6d
Step 2/5 : SHELL powershell -command
---> Running in 6fcdb6855ae2
---> 6331462d4300
Removing intermediate container 6fcdb6855ae2
Step 3/5 : RUN New-Item -ItemType Directory C:\Example
---> Running in d0eef8386e97


Directory: C:\


Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 10/28/2016 11:26 AM Example


---> 3f2fbf1395d9
Removing intermediate container d0eef8386e97
Step 4/5 : ADD Execute-MyCmdlet.ps1 c:\example\
---> a955b2621c31
Removing intermediate container b825593d39fc
Step 5/5 : RUN c:\example\Execute-MyCmdlet 'hello world'
---> Running in be6d8e63fe75
hello world
---> 8e559e9bf424
Removing intermediate container be6d8e63fe75
Successfully built 8e559e9bf424
PS E:\docker\build\shell>

SHELL指令还可用于修改shell的运行方式。例如,SHELL cmd /S /C /V:ON|OFF在Windows上使用,可以修改延迟的环境变量扩展语义。

SHELL,也可以在Linux上使用的指令应当替代壳需要如zshcshtcsh和其他。

SHELL功能已添加到Docker 1.12中。

Dockerfile示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# Nginx
#
# VERSION 0.0.1

FROM ubuntu
LABEL Description="This image is used to start the foobar executable" Vendor="ACME Products" Version="1.0"
RUN apt-get update && apt-get install -y inotify-tools nginx apache2 openssh-server
# Firefox over VNC
#
# VERSION 0.3

FROM ubuntu

# Install vnc, xvfb in order to create a 'fake' display and firefox
RUN apt-get update && apt-get install -y x11vnc xvfb firefox
RUN mkdir ~/.vnc
# Setup a password
RUN x11vnc -storepasswd 1234 ~/.vnc/passwd
# Autostart firefox (might not be the best way, but it does the trick)
RUN bash -c 'echo "firefox" >> /.bashrc'

EXPOSE 5900
CMD ["x11vnc", "-forever", "-usepw", "-create"]
# Multiple images example
#
# VERSION 0.1

FROM ubuntu
RUN echo foo > bar
# Will output something like ===> 907ad6c2736f

FROM ubuntu
RUN echo moo > oink
# Will output something like ===> 695d7793cbe4

# You'll now have two images, 907ad6c2736f with /bar, and 695d7793cbe4 with
# /oink.