官方文档翻译,原文地址:https://docs.docker.com/engine/reference/builder/

需要提前了解的知识

架构图

架构图

docker是C/S架构模式,平时用到的docker命令其实只是个客户端,本身不会进行任何操作,只是负责给server端发送请求,而真正的server端其实是本地启动的dockerd这个程序,也叫做docker-engine,细节就不讨论了

使用docker version可以看到具体的版本信息

版本

分层设计

docker镜像是分层的,dockerfile中的每一条命令都是独立的,并且会创建一层新的镜像,镜像层都是只读的,在使用镜像启动容器时,会添加一层可写层,这一层叫做”容器层”,它以下的都叫做”镜像层”

所有对容器的改动 - 无论添加删除还是修改文件,都会只发生在容器层,细节就不讨论了

名词

Image 镜像 Container 容器 Label 标签


Dockerfile 参考

Docker可以读取Dockerfile中的指令来构建镜像,一个Dockerfile就是包含了构建一个镜像所有命令的文本文件,使用docker build就会根据文件中的命令来构建镜像,本文介绍了Dockerfile中的常用命令

用法

docker build命令可以根据Dockefilecontext来构建一个镜像,构建的context(上下文)指的是指定位置的PATHURL的文件,PATH是你本地的文件目录,URL是git仓库的地址

context是递归处理的,所以PATH包含了所有子目录,URL包含了所有的git submodule

下面这个例子就是使用当前目录来构建镜像

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

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

想要在context中使用文件的话,需要用指定,例如COPY指令.为了提高构建的性能,可以通过添加.dockerignore的方式去掉不需要的文件和目录

默认情况下,Dockerfile名字应该叫做Dockerfile,而且它应该存放在根目录中,也可以使用-f来指定任何文件

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

如果构建成功,可以指定仓库和tag以保存新的镜像

$ docker build -t shykes/myapp .

要在构建后将镜像打多个tag,可以在运行build命令时添加多个-t参数

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

在Docker Daemon根据Dockerfile构建时,会先检测语法是否正确,如果不正确会返回错误

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

Docker Daemon会一行一行地执行Dockerfile中的指令,在最终输出你的新镜像ID前,如果过程中需要新的镜像,就会提交一次结果,Docker Daemon会自动清除你发送的上下文

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

如果有可能的话,Docker会复用中间镜像(缓存),来加速构建过程.在输出中可以看到使用了缓存

$ 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指定的镜像不需要具有父链,可以从其他register中提取.

格式

Dockerfile的格式是这样的:

# Comment 注释
INSTRUCTION arguments
指令 参数

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

Docker会按照Dockerfile中的指令一行一行执行,一个Dockerfile必须FROM指令开头,FROM指令,指定了构建时使用的基础镜像.

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

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

解析器指令

解析器指令是可选的,并且会影响处理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=\ (backslash)

或者

# escape=` (backtick)

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

转义字符既用于转义行中的字符,也用于转义换行符.这允许Dockerfile指令跨越多行.请注意,除非在行尾,否则无论转义解析器指令是否包含在Dockerfile中,都不会在RUN命令中执行转义

环境替换

环境变量(使用ENV指令声明)也可以在某些指令中用作Dockerfile要解释的变量.还会处理转义,以便将类似变量的语法包含在字面上

使用$variable_name${variable_name}Dockerfile中标注环境变量.它们被等效地处理,并且大括号语法通常用于解决具有没有空格的变量名称的问题,例如${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

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

ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc

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

.dockerignore 文件

在docker CLI将上下文发送到Docker daemon之前,它会在上下文的根目录中查找名为.dockerignore的文件,如果此文件存在,CLI将修改上下文以排除与其中的模式匹配的文件和目录.这有助于避免将不必要的大型或敏感文件和目录发送到docker daemon,但是也可以使用ADDCOPY将它们添加到镜像

上下文会作为根目录和工作目录,例如,/foo/barfoo/bar都会在PATH的foo子目录中或位于URL的git存储库的根目录中排除名为bar的文件或目录.两者都不包括任何其他内容

如果.dockerignore文件中的行以#开头,则此行被视为注释

举个.dockerignore的例子:

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

这个文件最终的结果:

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

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

行开头!(感叹号)可用于排除例外情况.以下是使用此机制的示例.dockerignore文件:

    *.md
    !README.md

它的意思是除README.md之外的所有markdown文件都将从上下文中排除

你甚至可以使用.dockerignore文件来排除Dockerfile.dockerignore文件.不过这些文件仍然发送到docker daemon,因为它需要它们来完成它的工作.但ADDCOPY指令不会将它们复制到镜像中.

最后,你可能希望指定要包含在上下文中的文件,而不是要排除的文件.要实现此目的,请将*指定为第一个模式,然后指定一个或多个!来将文件保留

FROM 指令

FROM <image> [AS <name>]

或者

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

或者

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

FROM指令用来初始化新的构建阶段并为后续指令设置基本镜像.因此,有效的Dockerfile必须以FROM指令开头.镜像可以是任何有效的镜像,使用公共仓库是最简单的方式

  • ARGDockerfile中唯一可以在FROM之前的指令

  • FROM可以在单个Dockerfile中多次出现以创建多个镜像,或者使用一个构建阶段作为另一个构建阶段的依赖项.只需在每个新的FROM指令之前记下提交输出的最后一个镜像ID.每个FROM指令清除先前指令创建的任何状态

  • 通过将AS name添加到FROM指令,可以将名称赋予新的构建阶段.该名称可以在后续的FROMCOPY --from = <name | index>指令中使用,以引用此阶段构建的图像

  • tagdigest是可选的.如果省略其中任何一个,则构建器默认采用latest这个tag.如果找不到指定的tag,构建器将返回错误.

了解ARGFROM如何运作

FROM指令支持在第一个FROM之前通过ARG指令声明变量

ARG  CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD  /code/run-app

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

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

ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version

RUN 指令

RUN指令有两种形式:

  • RUN <command> (shell的形式,命令会在shell中运行,Linux下是/bin/sh -c,Windows是 cmd /S /C)

  • RUN ["executable","param1","param2"]

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

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

exec形式可以避免shell字符串重写,并使用不包含指定shell可执行文件的基本映像来运行RUN命令

可以使用SHELL命令更改shell形式的默认shell

在shell形式中,可以使用\(反斜杠)将单个RUN指令延续到下一行.例如,参考以下两行:

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

它们一起相当于这一行:

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

RUN指令的缓存在下一次​​构建期间不会自动失效,像RUN apt-get dist-upgrade -y这样的指令的缓存将在下一次构建期间重用,可以使用--no-cache标志使RUN指令的缓存无效,例如docker build --no-cache

CMD 指令

CMD指令有三种形式:

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

CMD的主要目的是为执行容器提供默认值,这些默认值可以包含可执行文件,也可以省略可执行文件,而如果省略了,则必须指定ENTRYPOINT指令.

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

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

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

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

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

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

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

LABEL 指令

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

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

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."

镜像可以有多个label(标签),您可以在一行中指定多个label.在Docker1.10之前,这减小了最终镜像的大小,但现在不再是这种情况了.您仍然可以选择在单个指令中指定多个标签,方法有以下两种:

LABEL multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \
      multi.label2="value2" \
      other="value3"

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

要查看镜像的标签,请使用docker inspect命令

"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"
},

MAINTAINER (deprecated) 弃用

MAINTAINER <name>

MAINTAINER指令设置生成镜像的Author字段.LABEL指令是一个更灵活的版本,您应该使用它,因为它可以设置您需要的任何元数据,并且可以轻松查看,例如使用docker inspect

EXPOSE

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

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

EXPOSE指令实际上不发布端口,它可以作为构建镜像的人和运行容器的人之间的一种文档,关于哪些端口要发布,要在运行容器时实际发布端口,请在docker run上使用-p标志发布和映射一个或多个端口,或使用-P标志发布所有公开的端口并将它们映射到宿主机端口.

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

EXPOSE 80/udp

如果tcp和udp都要,那就写两行

EXPOSE 80/tcp
EXPOSE 80/udp

在这种情况下,如果将-Pdocker run一起使用,则端口将针对TCP公开一次,针对UDP公开一次.-P在主机上使用短暂的宿主机端口,因此TCP和UDP的端口不同.

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

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

ENV 指令

ENV <key> <value>
ENV <key>=<value> ...

ENV指令将环境变量<key>设置为值<value>,此值将在构建阶段中所有后续指令的环境中,并且也可以被替换

ENV指令有两种形式.第一种形式ENV <key> <value>,将单个变量设置为一个值,第一个空格后面的整个字符串都会被视为<value>,包括空格

第二种形式ENV <key> = <value> ...允许一次设置多个变量,注意在第二种形式中时候用等号,而第一种形式没有.与命令行解析一样,引号和反斜杠可用于在值内包含空格.

例子:

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

这两种写法最终产生的镜像是相同的

当使用这个镜像运行容器时,环境变量会保留,可以使用docker inspect来查看,并且可以使用docker run --env <key>=<value>来修改

ADD指令

ADD有两种格式

  • ADD [--chown=<user>:<group>] <src>... <dest>
  • ADD [--chown=<user>:<group>] ["<src>",... "<dest>"] 这种格式下路径中可以包含空格

只有用于构建Linux镜像时,–chown才有用,windows的是没用的

ADD指令从<src>复制新文件,目录或远程文件URL,并将它们添加到镜像的<dest>中.

可以指定多个<src>,如果是文件或者目录,则是相对于context的相对路径

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

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的相对路径,<src>将被复制到镜像的该路径下

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

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

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

所有新文件和目录都是使用UID和GID为0创建的,除非可选的–chown标志指定给定用户名

ADD指令遵循以下几种规则

  • <src>路径必须位于构建的上下文中;你不能添加../something / something,因为docker构建的第一步是将上下文目录(和子目录)发送到docker守护进程

  • 如果<src>是一个URL并且<dest>没有以斜杠结束,则从URL下载文件并将其复制到<dest>

  • 如果<src>是一个URL并且<dest>以尾部斜杠结尾,则从URL推断文件名,并将文件下载到<dest>/<filename>,举个例子,ADD http://example.com/foobar /将创建文件/foobar

  • 如果<src>是目录,则复制目录的全部内容,包括文件系统元数据 > 不复制目录本身,只复制其内容.

  • 如果<src>是可识别的压缩格式(identity,gzip,bzip2或xz)的本地tar,则将其解压缩为目录,远程URL中的资源不会解压缩.

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

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

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

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

COPY指令

有两种格式

  • COPY [--chown=<user>:<group>] <src>... <dest>
  • COPY [--chown=<user>:<group>] ["<src>",... "<dest>"] (如果目录包含有空格的话,需要使用这种格式)

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

多个目录都是根据context的相对目录

每个也都是使用Go的文件匹配例如:

COPY hom* /mydir/        # "hom"开头的所有文件
COPY hom?.txt /mydir/    # ? 匹配单个任意字符, e.g., "home.txt"

<dest>可以是绝对路径,也可以是对于WORKDIR的相对路径

COPY test relativeDir/   # 将 "test" 添加到 `WORKDIR`/relativeDir/
COPY test /absoluteDir/  # 添加 "test" 添加到 /absoluteDir/

如果使用标准输入来构建,那么是没有上下文的,不可以使用COPY指令

COPY遵循以下几个规则:

  • 必须在构建上下文中,COPY ../something /something这种就是错误的

  • 如果是目录,那么会复制所有的数据

不复制目录本身,只复制内容

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

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

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

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

ENTRYPOINT 指令

ENTRYPOINT有两种格式

  • ENTRYPOINT ["executable", "param1", "param2"] (“exec”形式,一般都是这个)
  • ENTRYPOINT command param1 param2 (shell形式)

ENTRYPOINT用来配置将作为可执行文件运行的容器

例如,下面这个命令会启动nginx,监听端口80:

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

在命令docker run <image>后的参数会附加在exec形式ENTRYPOINT的所有元素之后,并将覆盖使用CMD指定的命令,可以传递参数给entry point(入口点),例如docker run <image> -d会将-d参数传递给入口点,可以使用docker run --entrypoint标志覆盖ENTRYPOINT指令.

shell形式的不允许CMD或者任何命令行参数,缺点是ENTRYPOINT会作为/bin/sh -c的子命令,不会传递信号,这意味着可执行文件不是容器的PID 1 - 并且不会接收Unix信号 - 因此可执行文件将不会从docker stop <container>接收SIGTERM.

例子

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

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

当启动容器的时候,你可以看到top是唯一的进程

$ 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 -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 run命令时可以使用--entrypoint来覆盖Dockerfile中的ENTRYPOINT指令

理解CMDENTRYPOINT如何相互作用

CMDENTRYPOINT都指定了容器运行时的命令,下面是描述它们如何生效的规则:

  • Dockerfile应该至少指定一个ENTRYPOINT或者CMD指令
  • 使用容器作为可执行文件时,应该定义ENTRYPOINT
  • CMD应该作为ENTRYPOINT的默认参数
  • 使用其他参数启动容器时,应该覆盖掉CMD

当从一个基础镜像继承而来,重新定义ENTRYPOINT时,CMD会被重置,这种情况下,需要在当前镜像定义CMD才能获取

VOLUME 指令

VOLUME ["/data"]

VOLUME指令创建具有指定名称的挂载点,并将其标记为从本机主机或其他容器保留外部挂载的卷.该值可以是JSON数组VOLUME [“/var/log/”]或具有多个参数的纯字符串,例如VOLUME /var/logVOLUME /var/log /var/db.有关通过Docker客户端的更多信息/示例和安装说明,请参阅文档

docker run命令使用存在于基本镜像中指定位置的任何数据初始化新创建的volume.例如,以下Dockerfile片段:

FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol

这个Dockerfiledocker run的镜像,在/myvol上创建一个新的挂载点,并将greeting文件复制到新创建的卷中.

有关指定volume

Dockerfile中的volumes需要注意:

  • 如果任何构建步骤在声明后更改卷中的数据,那么这些更改将被丢弃.
  • 该列表解析为JSON数组,这意味着您必须在单词之外使用双引号(“)而不是单引号(‘).

USER 指令

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

USER指令设置用户名(或UID)以及运行镜像时要使用的用户组(或GID)以及Dockerfile中它之后的任何RUN,CMDENTRYPOINT指令.

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

WORKDIR 指令

WORKDIR /path/to/workdir

WORKDIR指令为Dockerfile中的任何RUN,CMD,ENTRYPOINT,COPYADD指令设置工作目录.如果WORKDIR目录不存在,那么将被创建,即使它没有在任何后续的Dockerfile指令中使用

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

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

最终pwd的输出,应该是/a/b/c

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

ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd

Dockerfile中最后一个pwd命令的输出将是/path/$DIRNAME

ARG 指令

ARG <name>[=<default value>]

ARG指令定义一个变量,用户使用docker build命令时使用--build-arg <varname> = <value>,在构建时将其传递给构建器.如果用户指定了一个未在Dockerfile中定义的构建参数,构建将输出错误.

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

Dockerfile的作者可以通过指定ARG一个或多个变量,通过多次指定ARG来定义变量.例如,一个有效的Dockerfile:

FROM busybox
ARG user1
ARG buildno
...

警告:建议不要使用构建时变量来传递密码,例如github密钥,用户凭证等.使用docker history命令,任何镜像的用户都可以看到构建时变量值.

Default Value 默认值

ARG指令可以选择包含默认值

FROM busybox
ARG user1=someuser
ARG buildno=1
...

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

Scope 作用域

ARG变量定义从Dockerfile中定义的行开始生效,而不是从命令行或其他地方的参数使用.例如:

FROM busybox
USER ${user:-some_user}
ARG user
USER $user
...

用户通过调用以下命令构建此文件:

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

第2行的USER将变成some_user,因为用户变量在后续行3上定义.第4行的USER在定义用户时为what_user,在命令行中传递what_user值.在通过ARG指令定义之前,变量的任何使用都将导致空字符串.

ARG指令在构建阶段结束时超出范围.要在多个阶段使用arg,每个阶段必须包含ARG指令

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

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

Using ARG variables 使用ARG变量

可以使用ARGENV指令来指定RUN指令可用的变量.使用ENV指令定义的环境变量总是覆盖同名的ARG指令.例如:

FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER v1.0.0
RUN echo $CONT_IMG_VER

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

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

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

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

FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER ${CONT_IMG_VER:-v1.0.0}
RUN echo $CONT_IMG_VER

ARG指令不同,ENV值始终保留在image中例如:

$ docker build .

使用此Dockerfile示例,CONT_IMG_VER仍然保留在图像中,但其值为v1.0.0,因为它是ENV指令在第3行中的默认设置

此示例中的变量扩展技术允许您从命令行传递参数,并通过利用ENV指令将它们保存在最终图像中.

Predefined ARGs 预定义的ARG

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

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

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

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

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

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

FROM ubuntu
RUN echo "Hello World"

在这种情况下,HTTP_PROXY变量的值在docker历史记录中不可用,并且不会被缓存,如果要更改位置,并且代理服务器已更改为http://user:pass@proxy.sfo.example.com 后续构建不会导致缓存未命中. 如果需要覆盖此行为,则可以通过在Dockerfile中添加ARG语句来执行此操作,如下所示

FROM ubuntu
ARG HTTP_PROXY
RUN echo "Hello World"

构建此Dockerfile时,HTTP_PROXY将保留在docker历史记录中,并且更改其值会使构建缓存无效

全局作用域的

以下ARG变量自动设置:

  • TARGETPLATFORM
  • TARGETOS
  • TARGETARCH
  • TARGETVARIANT
  • BUILDPLATFORM
  • BUILDOS
  • BUILDARCH
  • BUILDVARIANT

这些参数在全局范围内定义,因此在构建阶段或RUN命令中不会自动提供.要在构建阶段中公开其中一个参数,请在没有值的情况下重新定义它,举个例子:

FROM alpine
ARG TARGETPLATFORM
RUN echo "I'm building for $TARGETPLATFORM"

STOPSIGNAL 指令

STOPSIGNAL signal

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

HEALTHCHECK 指令

两种形式: - HEALTHCHECK [OPTIONS] CMD command (通过在容器中运行命令来检查容器运行状况) - HEALTHCHECK NONE (禁用从基本镜像继承的任何运行状况检查)

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

当容器指定了healthcheck时,除了其正常状态外,它还具有健康状态.此状态最初开始. 每当健康检查通过,它会变得健康(无论之前的状态).在一定数量的连续故障后,它变得不健康.

在CMD之前可以出现的选项有: - –interval=DURATION (default: 30s)
- –timeout=DURATION (default: 30s) - –start-period=DURATION (default: 0s) - –retries=N (default: 3)

运行状况检查将首先在容器启动后运行interval秒,然后在每次上次检查完成后再次运行interval秒.

如果检查的单次运行所花费的时间超过timeout秒数,则该检查被认为已失败.

如果连续retries次数的健康检查不通过,则容器被认为是不健康的.

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

CMD关键字之后的命令可以是shell命令(例如HEALTHCHECK CMD /bin/check-running)或exec形式(如同其他Dockerfile命令一样;详情参见ENTRYPOINT).

命令的退出状态表示容器的运行状况.可能的值为: - 0: success - the container is healthy and ready for use - 1: unhealthy - the container is not working correctly - 2: reserved - do not use this exit code

例如,要每五分钟检查一次Web服务器能够在三秒钟内为网站的主页提供服务

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 指令

SHELL ["executable", "parameters"]

SHELL指令允许用于命令的shell形式的默认shell被覆盖. Linux上的默认shell是[“/bin/sh”,“-c”],在Windows上是[“cmd”,“/S”,“/C”].SHELL指令必须以JSON格式写在Dockerfile中.

SHELL指令在Windows上特别有用,其中有两个常用的和完全不同的本机shell:cmd和powershell,以及包括sh的备用Shell.

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

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时,以下指令可能受SHELL指令的影响:RUN,CMDENTRYPOINT.

以下示例是Windows上的常见模式,可以使用SHELL指令进行简化 docker调用的命令将是:

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

这是低效的,有两个原因.首先,有一个不必要的cmd.exe命令处理器(也称为shell)被调用.其次,shell中的每个RUN指令都需要一个额外的powershell -command.

为了更有效率,可以采用两种机制之一. 一种是使用JSON形式的RUN命令,如:

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

虽然JSON形式是明确的,并且不使用不必要的cmd.exe,但它需要通过双引号和转义更详细. 备用机制是使用SHELL指令和shell形式,为Windows用户提供更自然的语法,特别是与escape 解析指令结合使用时:

# 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'

结果是

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指令还可以用于修改外壳操作的方式.例如,在Windows上使用SHELL cmd /S /C /V:ON|OFF,可以修改延迟的环境变量扩展语义.

SHELL指令也可以在Linux上使用,如果需要一个替代shell,如zsh,csh,tcsh和其他.

SHELL功能在Docker 1.12中添加.

Dockerfile Example

下面您可以看到Dockerfile语法的一些示例.如果您对更实际的东西感兴趣,请查看Docker化示例列表.

# 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.