docker build
docker build
可以基于Dockerfile和context打包出一个镜像,其中context是一系列在PATH
或URL
中指定的位置中的文件(context是递归的,包含子目录下的文件,build时会将context中的全部内容传递给docker daemon)。其中PATH
是指本地文件系统,URL
则是git仓库的地址。比如在当前目录下创建镜像:docker build .
,此时context就是当前目录.
。build的运行是由docker daemon操作的,并不是CLI(Command-Lin Interface)。通常build时强烈建议创建一个空目录,然后将Dockerfile文件放入,最后只将build需要用到的文件放到该目录下。注:千万不要用root或者/
作为PATH,docker daemon会将整个目录下的文件读取。为了尽可能提高Docker的build性能,和git一样可以在上述目录中添加.dockerignore
文件排除一些不要的文件,比如正常打包时都会排除掉Dockerfile:
./Dockerfile*
默认build时会基于context的根目录下Dockerfile文件打包(如果不存在会报错),当然可以通过-f DockerfilePath
的方式指定任意位置的Dockerfile位置,但后面的context必须在Dockerfile所在位置的目录或者父目录(指定为其他目录会报错,比如指定为~/k8s/
),比如:
# ~/docker/test1/Dockerfil指定Dockerfile位置,~/docker/指定context一定是Dockerfile所在目录或父目录
docker build -f ~/docker/test1/Dockerfile ~/docker/
此外还可以指定如果镜像构建成功存放的仓库和标签(即repository和tag),如构建时打上阿里云的仓库标签:docker build -t registry.cn-shanghai.aliyuncs.com/hhu/redis:4.0-alpine3.9 .
。注:这里可以不断追加-t 标签
的方式打上多个仓库仓库标签,比如:docker build -t registry.cn-shanghai.aliyuncs.com/hhu/redis:4.0-alpine3.9 -t test1/redis:4.0-alpine3.9 -t test2/redis:4.0-alpine3.9 .
,上述栗子一下就会出现3个标签的镜像,但它们的ID是相同的,仅仅是标签不同。
和大部分的应用类似,docker daemon会在运行Dockerfile中的指令时会先验证文件的可行性,比如语法错误就会返回error。Dockerfile中的每条指令都是独立运行的(比如RUN cd /tmp
并不会影响下一条指定的执行),并不会创建新的镜像。不论何时,docker都会尽可能重复应用缓存的中间镜像以便加快build的过程,所以有时在build时可能会在控制台看到Using cache
的字样。可以使用的缓存镜像只有在本地具备父链才能使用(即缓存的镜像先前被创建过或者整个镜像链都使用docker load
加载过)。如果在build时希望使用某个特定的镜像缓存可以使用--cache-from
选项,注:使用--cache-from
时不需要具备本地父链,可能会从其他的注册中心拉下来。
注:docker run
如果不显式的指定-it
进行交互的话,此时创建的容器会直接在后台运行,如果没有前台进行,docker会让容器自动停止并移除,所以很多时候为了让一些只有操作系统的镜像创建的容器不被docker自动移除,我们往往会在创建镜像dockerfile的时候加上一个CMD
:
# 创建 centOS 空系统工具经常加这个CMD
CMD /usr/sbin/init
# 创建 Alpine 空系统工具经常加这个CMD
CMD init
他们目的都是在系统中挂一个前台进程,这样docker就不会认为该容器处于空闲状态去移除它,目的都是一样的,当然我们也可以人为造出一些死循环来达到这种目的。
Dockerfile的语法为INSTRUCTION arguments
,不区分大小写,但为了区分指令和参数,约定指令全部大写、参数尽量小写。docker会按序执行Dockerfile中的指令,Dockerfile文件必须以FROM
指令开头(除指令解释器外),它指定了当前构建镜像的基础镜像。通常Dockerfile中也允许注释,注释行以#
开头,但指令解释器除外,比如``。
指令解释器是可选的,它会影响下面指令的处理方式,但不会在增加层layer,也不会作为构建的步骤展示出来。docker一旦处理了注释、空行或生成器指令,就不会再看是它否分析器指令了,就算下面还有指令解释器,docker也会将其视为注释(都是以#
开头),所以Dockerfile中只要有指令解释器,请务必尽可能的靠前声明(约定解释器为小写),应该Dockerfile的第一行。指令解释器不支持行继续的行为(Linux中表现为\
),同时指令解释器后应该空一行。总结一下,应该遵循如下的规则:
#
开头,指令解释器小写,如果有位于Dockerfile的第一行;\
;下面是一些反例:
# 反例1:使用了行继续 \
# direc \
tive=value
# 反例2:只会识别第一个,value2不应该出现会被当成注释
# directive=value1
# directive=value2
FROM ImageName
# 反例3:指令解释器非首行
FROM ImageName
# directive=value
注:指令解释器允许使用空格符,比如下面的几种方式都是一样有效的:
#directive=value
# directive =value
# directive= value
# directive = value
Dockerfile支持的指令解释器有:syntax
和escape
。其中syntax
仅支持下一代构建工具BuildKit,这里暂不进行捣鼓,直接看escape
,它定义的是Dockerfile中的转义字符,有2种定义方式:
# escape=\
# 或者
# escape=`
如果不指定默认转义字符为\
。转义字符既用于转义一行中的字符,也用于转义换行符。这使得Dockerfile指令可以跨越多行,无论转义分析器指令是否包含在Dockerfile中,转义都不会在RUN
命令中执行,除非在行尾执行。在windows上,\ 是目录之间的分隔符,将转义字符设置为 ` 是非常有用的,它和 PowerShell 一致,比如在windows的powerShell中:
FROM microsoft/nanoserver
COPY testfile.txt c:\\
RUN dir c:\
没有指定指令解释器,默认为\
,那此时第二行中COPY testfile.txt c:\\
就出现了转义字符(c:\\
中的第一个\
),实际意思就是COPY testfile.txt c:\
,那剩下的那个\
就变成了行继续的操作,所以第二行和第三行是一行实际为:COPY testfile.txt c:\RUN dir c:
第三行的转义字符在行末属于可执行范围。在powershell中,为了避免这个问题可以使用 ` 作为转义字符就不会出现上述的问题:
# escape=`
FROM microsoft/nanoserver
COPY testfile.txt c:\\
RUN dir c:\
上述Dockerfile拆解成了3个指令(除去指令解释器,之前是2条指令)。
docker中可以使用ENV
为特定的指令声明环境变量,转义字符也被处理为将类似变量的语法包含到字面上的语句中。环境变量在Dockerfile中使用$variable_name
或${variable_name}
标注,上述2标注方式是一样的,第2种{xx}
的方式通常用于解决变量名没有空格的问题(不是很理解),比如${foo}_bar
,此外这种大括号的方式还支持一些标准的bash
修饰符,比如(word
只是随意取的值):
${variable:-word}
表示如果variable
设置了,那它的值就是设置的值;如果variable
没有设置,那word
就是它的值(有点三目运算的意思)。${variable:+word}
表示如果variable
设置了,那word
就是它的值;如果variable
没有设置,那它的值就是空字符串。注:变量上也是可以使用转义字符的,比如使用的转义字符是默认的\
,那在变量中\$foo
或\${foo}
,那就表示它就是个普通的字符串$foo
或${foo}
,而不是对应foo的值。
环境变量支持如下的指令:ADD
、COPY
、ENV
、EXPOSE
、FROM
、LABEL
、STOPSIGNAL
、USER
、VOLUME
、WORKDIR
、ONBUILD
(1.4版本之后该指令只有在和上述其他指令结合使用时才能使用环境变量)。下面是一个示例(解析的指令在#
后已经标出):
FROM busybox
# 声明环境变量foo,表示的值为/bar
ENV foo /bar
# 等同于 WORKDIR /bar
WORKDIR ${foo}
# 等同于 ADD . /bar
ADD . $foo
# 等同于 COPY $foo /quux
COPY \$foo /quux
环境变量的替换将在整个指令中对每个变量使用相同的值,比如下面的示例:
ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc
上述的定义过程中,def
的值为hello
,而不是bye
;ghi
的值为bye
,因为它不是将abc
设置为bye
那条指令的一部分。可能有点绕人,但只要记住一点就行了,在使用变量时,变量的值永远都是前面最靠近使用该变量的指令的定义(但不包含本身),如第2条指令使用def=$abc
,向前寻找变量abc
,排除自身,发现最靠近的abc
定义在第一条指令中abc=hello
,所以def=hello
;同样第3条指令使用ghi=$abc
,向前寻找变量abc
,发现第2条指令最靠近它,虽然第1条指令也有abc
的声明,但没有第2条指令靠近,所以取第2条指令之中abc
的声明bye
,所以ghi=bye
。
.dockerignore
文件 和.gitignore
类似,在CLI(Command-Lin Interface)将context发送给docker daemon时,它会关注一下context根目录下的.dockerignore
文件,如果存在,CLI将修改context以排除与该文件中匹配的文件和目录。这样可以避免将一些不必要的大文件和敏感发送给docker daemon并通过ADD
或COPY
将这些文件加入到镜像中。同样的.dockerignore
文件中也是允许注释的,行首用#
标注即可,比如:
# comment
*/temp*
*/*/temp*
temp?
上述的# comment
是一个注释,CLI将不会管它;*/temp*
表示CLI将排除context根目录中的任何一级子目录中名称以temp
开头的文件或目录;*/*/temp*
表示CLI将从context根目录下任何两级子目录中排除以temp
开头的文件或目录(如/somedir/subdir/temporary.txt
);temp?
表示CLI将排除context根目录中名为temp
的单字符扩展名的文件和目录(比如/tempa
)。此外它还支持**
通配符(匹配0或多个目录,注意是用于匹配目录的!!),如**/*.go
就是匹配context目录(包括它所有的子孙目录)下所有以.go
结尾的文件。
如果行首是!
可以用于排除例外的情况,示例如下:
*.md
!README.md
上述就是排除所有以.md
结尾的文件,但README.md
除外不用排除。
!
的位置会影响.dockerignore
文件中与特定文件匹配的属性决定它是包含的还是排除的最后一行,下面是2个示例(有点懵懂):
# 示例1:除了README-secret.md之外,排除上下文中含任何其他以.md结尾的文件。
*.md
!README*.md
README-secret.md
# 示例2:所有README文件都包含在内,中间的 README-secret.md 没有效果,因为 !readme*.md 与 readme-secret.md 匹配却在最后
*.md
README-secret.md
!README*.md
在.dockerignore
中甚至可以将Dockerfile
和.dockerignore
排除掉,但它们仍然会被发送到daemon因为docker需要这些进行job,但ADD
和COPY
指令不会将它们复制到镜像中。
FROM
FROM
指令初始化一个新的构建阶段,它为后续的指令设置了一个基本镜像,因此它必须是在所有指令的最前面定义的(除指令解释器),这个基本镜像可以是任何一个合法的镜像。语法如下:
# 方式1
FROM [AS ]
# 方式2
FROM [:] [AS ]
# 方式3
FROM [@] [AS ]
FROM
可以在一个Dockerfile
文件中出现多次,创建多个镜像或使用一个生成阶段作为另一个的依赖。只需在每条新的FROM
指令之前记录提交所输出的最后一个镜像的ID,每个FROM
指令会清除之前其他指令创建的状态。name
是可选的,可以通过在FROM
指令中添加AS name
给一个新的构建阶段起一个名字,这个名字可以在后来的FROM
指令和COPY --from=
指令中引用对应的镜像。同样tag
和digest
也是可选的,如果不指定,默认为latest
。
ARG
ARG
是唯一一个可以在FROM
指令之前的,FROM
指令是支持在第一个FROM
之前由ARG
指令声明的变量,比如:
# 声明变量 CODE_VERSION,值为latest
ARG CODE_VERSION=latest
# 在FROM指令中使用由ARG声明的变量
FROM base:${CODE_VERSION}
CMD /code/run-app
# 在FROM指令中使用由ARG声明的变量
FROM extras:${CODE_VERSION}
CMD /code/run-extras
在FROM
指令之前ARG
变量的声明是属于构建阶段之外的,因此不能在FROM
指令之后的其他指令中使用。如果要使用第一个From
之前声明的ARG
值,需要在构建阶段内使用没有指定值的ARG
指令,比如:
ARG VERSION=latest
FROM busybox:$VERSION
# 声明一个没有默认值的变量 ARG 指令
ARG VERSION
RUN echo $VERSION > image_version
RUN
RUN
指令将会在当前镜像的顶部的一个新层layer中执行命令并提交结果,提交镜像的结果将用于Dockerfile文件中的下一步骤。分层RUN
并生成新的新的结果符合Docker的核心思想(即提交的成本很低且可以从镜像历史的任何一点创建容器,很像源代码管理)。语法如下:
# 形式1:shell的形式,命令运行在shell中,Linux中默认运行 /bin/sh -c,win中默认运行 cmd /S /C
RUN
# 形式2:exec的形式
RUN ["executable", "param1", "param2"]
exec形式可以避免对shell字符串进行munging,可以在不包含指定可执行shell的基本镜像中运行RUN
指令。shell形式默认的shell可以使用SHELL
命令更改,比如要使用除/bin/sh
之外的shell,就需要使用传入所需shell的exec形式:
RUN ["/bin/bash", "-c", "echo hello"]
在shell形式中可以使用\
以继续运下一行的 RUN 指令(即使用\
作为行继续操作符,可以跨行运行),比如:
RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'
上述使用的是行继续操作符,实际是一个指令,等同于:RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
。exec形式会被编译成JSON形式,所以需要使用双引号"
而不是单引号'
,此外在exec形式中应该避免\
,如RUN ["c:\windows\system32\tasklist.exe"]
会出错,正确的写法应该为RUN ["c:\\windows\\system32\\tasklist.exe"]
。
和shell形式不同,exec形式不会调用shell,所以平常在shell正常处理的在exec形式中可能无法处理,比如:RUN [ "echo", "$HOME" ]
不会替换变量$HOME
,如果要用shell处理,要么直接用shell形式RUN
,要么直接执行shell,如:RUN [ "sh", "-c", "echo $HOME" ]
。RUN
指令的缓存在下一个构建阶段不会失效,比如RUN apt-get dist-upgrade -y
将会在下一个阶段重复使用,当然也可以使用--no-cache
参数来让RUN
指令失效,比如:docker build --no-cache
,RUN
指令的缓存也可以通过ADD
指令失效。
CMD
一个Dockerfile文件中仅可以只有一个CMD
指令,如果有多个CMD
,仅有最有一个CMD
奏效,它的主要目的是为正在执行的容器提供默认值,这些默认值可以包括可执行文件,也可以省略可执行文件(但此时必须指定一个ENTRYPOINT
指令),主要语法如下:
# 形式1:exec形式,这是首选形式
CMD ["executable","param1","param2"]
# 作为ENTRYPOINT默认参数
CMD ["param1","param2"]
# shell形式
CMD command param1 param2
如果CMD
用于为ENTRYPOINT
指令提供默认参数(即上述第2种形式),那CMD
和ENTRYPOINT
指令都应该用JSON格式指定(双引号"
)。当在shell或exec形式,CMD
指令设置在运行镜像时要执行的命令,如果使用shell形式的CMD
,
应该是/bin/sh -c
:
FROM ubuntu
CMD echo "This is a test." | wc -
如果不想在shell中运行
,那就必须使用JSON数组的形式并使用可执行文件的全路径。数组形式是CMD
的首选形式,任何额外的参数必须在数组中做独立表达,如:
FROM ubuntu
CMD ["/usr/bin/wc","--help"]
如果想每次容器都执行相同的可执行文件,应该考虑使用ENTRYPOINT
和CMD
的配合使用。如果指定docker run
的参数,那么它们将覆盖在CMD
中指定的默认值。
注:RUN
和CMD
可能有些像,主要区别是:RUN
是运行一个命令然后提交结果,而CMD
在构建镜像的过程中不会干任何事,但指定镜像的预期命令。
LABEL
LABEL
指令用于向镜像添加元数据,它是一个键值对,如果key值或value值中包含空格,需要使用双引号和反斜杠,一个镜像可以有多个标签,下面是示例:
# 有空格用引号包裹
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 multi.label1="value1" multi.label2="value2" other="value3"
# 一个LABEL中设置多个标签
LABEL multi.label1="value1" \
multi.label2="value2" \
other="value3"
FROM
指令中指定的基本镜像中的标签会被当前构建的镜像继承过来,这些标签如果在当前构建的镜像指定相同的key值但不同的value值时是会被覆盖的,使用docker inspect xxx
可以查看镜像xxx的标签,下面是openjdk部分标签:
docker inspect 3675b9f543c5
# 结果
[
{
"Id": "sha256:3675b9f543c5db0d8d554f8e103a4cb98db26be5e5c88019cbe49dcd4fea4685",
"RepoTags": [
"openjdk:8-jdk-alpine"
],
"RepoDigests": [
"openjdk@sha256:2a52fedf1d4ab53323e16a032cadca89aac47024a8228dea7f862dbccf169e1e"
],
"Parent": "",
"Comment": "",
"Created": "2019-04-10T01:52:39.548813341Z",
"Container": "2d38510bb479e015e7990ac60a6af8855e1c9f7bf6b6a66bde2c8625355eccd9",
"ContainerConfig": {
...
},
...
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]
EXPOSE
EXPOSE
指令表示了docker容器运行时监听的特定网络端口,可以指定监听TCP(默认)或UDP,在实际运行容器docker run
时可以使用-p
标识去发布并映射到一个或多个端口。比如默认监听TCP,现在改成UDP:
EXPOSE 80/udp
也可以同时监听TCP和UDP的80端口:
EXPOSE 80/tcp
EXPOSE 80/udp
如果在docker run
时指定-p
,那端口将为TCP和UDP各暴露一次。-p
在主机上使用了一个短暂的高阶主机端口,因此TCP和UDP的端口将不相同(不懂)。当然就算在Dockerfile中配置过端口,在docker run
时可以通过-p
来覆盖这里定义的,如:
docker run -p 80:80/tcp -p 80:80/udp ...
docker network
可以在容器之间创建网络进行交流,不需要暴露或发布特定端口,因为连接到网络的容器可以通过任何端口相互通信。
ENV
ENV
指令用于设置环境变量,将环境变量设置
设置为
,这里设置的环境变量将会存在于所有后续指令的环境变量中,可以在命令行进行替换,可以使用docker inspect xxx
查看环境变量的值。ENV
有如下形式:
# 形式1:设置单个变量值,在第一个空格后的字符串就是value(可能包含空格)
ENV
# 形式1示例,定义了3个环境变量
ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy
# 形式2:允许同时设置多个环境变量,可以使用双引号 和行继续操作符 \
ENV = ...
# 形式2示例,定义了2个环境变量
ENV myName="John Doe" myDog=Rex\ The\ Dog \
myCat=fluffy
使用ENV
设置的环境变量将从生成镜像到镜像容器运行保持不变,使用docker inspect
可以看到这些环境变量,在运行容器时可以使用docker run --env
来改变环境变量。如果为单个指令设置值的话,尽量使用RUN
。
ADD
ADD
指令可以复制新的文件、目录或者远程URL中的
,将它们添加到镜像的文件系统中(具体是镜像的
目录),具体语法有2种:
# 形式1:将context中 src 复制到镜像中的 dest 目录
ADD [--chown=:] ...
# 形式2:和形式1一样,就多了双引号,主要用于src和dest路径中存在空格的情况
ADD [--chown=:] ["",... ""]
是相对于context的相对路径,可以有多个,而且它可以包含通配符,比如:
# 将所有以hom开头的为文件复制进去
ADD hom* /mydir/
# ? 匹配单个字符,如"home.txt"
ADD hom?.txt /mydir/
是镜像中的一个路径,可以是绝对路径,也可以是相对于WORKDIR
的一个相对目录(以/
开头就是绝对路径),如下:
# 将 test 复制到 `WORKDIR`/relativeDir/ 的相对路径
ADD test relativeDir/
# 将 test 复制到绝对路径 /absoluteDir/
ADD test /absoluteDir/
当复制包含特殊字符名字(比如[
和]
)的文件或目录时,需要做一些处理防止被误认为通配符,比如复制arr[0].txt
文件:
# 将 arr[0].txt文件复制到镜像中的 /mydir/ 目录
ADD arr[[]0].txt /mydir/
注:其中--chown
是仅支持建立再Linux容器上的Dockerfile,win上无效。所有新文件或目录都是使用UID和GID为0开头(Linux中UID和GID即表示UserId和GroupId,0是超级用户,500起则是普通用户,表示的一种权限)被创建,除非使用--chown
选项指定用户、用户组(或用UID和GID,2种方式可以混用),如果指定了用户却不指定用户组(或指定了UID却不指定GID),那将会使用和UID一样的GID。下面是示例:
ADD --chown=55:mygroup files* /somedir/
ADD --chown=bin files* /somedir/
ADD --chown=1 files* /somedir/
ADD --chown=10:11 files* /somedir/
ADD
遵循下面的规则:
必须在context目录中,不能出现context之外的目录,CLI只将context发送给daemon,docker是无法读取context之外目录的文件、目录的;
如果是一个URL:
不以斜杠/
结尾,文件将会从URL下载并被复制到
;
以斜杠/
结尾,那将从URL推断文件名并将文件下载到/
,如ADD http://example.com/foobar /
将会创建/foobar
文件;
是一个目录,那整个目录的内容(只是它内部的内容但不包含目录本身)将会被复制,包括文件系统的元信息;
是本地context目录下的压缩包,那它将会被解压成一个目录(前提是可识别的压缩格式:gzip, bzip2 or xz),但仅限于context是本地目录的情况,如果
是URL资源就不会自动解压了。
有多个,那
必须只能是一个目录(即必须/
结尾);
不以/
结尾,那将会被认为是一个文件(不是目录),那
的内容将会被写入到该文件中;
不存在,那将会创建(丢失的目录也会随之被创建);COPY
COPY
和上述的ADD
指令功能一样,但COPY
的
不能是URL,也不能对压缩包进行解压,语法类似:
COPY [--chown=:] ...
COPY [--chown=:] ["",... ""]
ENTRYPOINT
ENTRYPOINT
允许配置容器,语法为:
# 形式1:exec形式,优先
ENTRYPOINT ["executable", "param1", "param2"]
# 形式2:shell形式
ENTRYPOINT command param1 param2
比如以默认内容启动nginx,监听80端口:docker run -i -t --rm -p 80:80 nginx
。运行docker run xxx
时指定命令行参数将会追加到exec形式的ENTRYPOINT
指令中,且覆盖使用CMD
指令指定的所有元素,允许将这些参数传给entry point,-d
(即docker run
)即可将将这些参数传给entry point,甚至可以使用docker run --entrypoint
选项覆盖Dockerfile中的ENTRYPOINT
指令。shell形式禁止使用任何CMD
或run
命令行参数,缺点是ENTRYPOINT
将作为/bin/sh-c
的子命令启动,不会传递信号,即可执行文件经不会成为容器的PID 1
且不会收到 Unix 信号,因此可执行文件将收不到docker stop
发的SIGTERM
。Dockerfile中最后一条ENTRYPOINT
指令才有效。
【exec形式的ENTRYPOINT
】
可以使用exec形式的ENTRYPOINT
设置相当稳定的默认命令和参数,然后使用任意一种形式的CMD
设置可能改变的额外默认值,如:
FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]
在运行该镜像容器时,top
是唯一的进程。在命令行中可以使用--entrypoint
选项来覆盖Dockerfile中ENTRYPOINT
指令。
注:
""
(最终是被编译成JSON);$HOME
不会被替换成用户根目录,比如/root
),如有需要必须指定一个shell目录,如ENTRYPOINT [ "sh", "-c", "echo $HOME" ]
;【shell形式的ENTRYPOINT
】
shell形式可以直接指定字符串,默认就会在/bin/sh -c
中执行,理所当然的可以进行变量替换。
【总结】
CMD
和ENTRYPOINT
指令定义了在运行容器时什么命令会执行,规则如下:
CMD
或ENTRYPOINT
指令;ENTRYPOINT
;CMD
指令应当作为ENTRYPOINT
指令指定默认参数的一种方式,或者在容器中执行特殊命令;CMD
指令可以在运行容器时指定被重写;CMD
在基础镜像上定义了,那在当前镜像中如果设置ENTRYPOINT
将会重置上述的CMD
的值(变为空值),此时必须在当前镜像中定义CMD
的值;VOLUME
VOLUME
指令用于创建指定名字的挂载点,并将其标记为保存来自本地主机或其他容器的外部装入的卷。形式有JSON和纯字符串的:
# 形式1(JSON)
VOLUME ["/var/log/"]
# 形式2
VOLUME /var/log
# 可以指定多个
VOLUME /var/log /var/db
比如:
FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol
将会创建一个新的挂载点/myvol/greeting
并将greeting
文件复制到上述新建的存储卷。使用VOLUME
有如下的注意点:
VOLUME
指令不支持指定host-dir
参数,在创建或者运行容器时必须指定挂载点;USER
USER
指令用于在运行镜像中的RUN
、CMD
和ENTRYPOINT
指令时设置用户名(或UID
)以及用户组(可选,或者GID
),主要形式如下:
# 形式1
USER [:]
# 形式2
USER [:]
注:
root
用户组下;net user
来创建,下面是示例:FROM microsoft/windowsservercore
# 在容器中先创建win用户
RUN net user /add patrick
# Set it for subsequent commands
USER patrick
WORKDIR
WORKDIR
指令用于设置RUN
、CMD
、ENTRYPOINT
、COPY
、ADD
指令的工作目录,如果WORKDIR
指定的目录不存在,就会被创建(即使后续指令没有使用该目录),该目录可以在Dockerfile中使用多次,基本语法为:
WORKDIR /path/to/workdir
如果提供了相对路径,那它是先前WORKDIR
指定目录的相对路径,下面是示例:
# 示例
# 绝对路径
WORKDIR /a
# 相对路径
WORKDIR b
WORKDIR c
RUN pwd
最终的路径为/a/b/c
。此外WORKDIR
指令可以解析之前使用ENV
设置的环境变量,如:
ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
# 输出应为 /path/$DIRNAME
RUN pwd
ARG
ARG
指令用于用户在创建镜像时定义的一些变量,语法:
ARG [=]
其中默认值是可选的,如果在构建时不指定构建参数将会使用默认值。可以在docker build
时使用--build-arg
指定这些变量的具体值,如果用户指定了Dockerfile中未定义的构建参数,构建时将会出现下面的日志:
[Warning] One or more build-args [foo] were not consumed.
可以定义多个ARG
指令,比如:
# 注:默认值是可省的
FROM busybox
ARG user1
ARG buildno
注:不建议将密码这类的信息作为构建参数,因为任何用户都可以使用docker history
查看到构建参数。
【作用域】
ARG
变量定义从dockerfile中定义它的地方开始生效,而不是从参数在命令行或其他地方的使用开始生效,比如:
FROM busybox
USER ${user:-some_user}
ARG user
USER $user
构建时使用docker build --build-arg user=what_user .
,实际在Dockerfile中的第2行中定义了USER
,并将变量user
的值假设为some_user
,第4行的USER
指令定义了变量user
是什么(将会从命令行中传过来)。在用ARG
指令定义变量之前,任何变量的使用都是空字符串(那是,没定义就使用不报错就不错了)。ARG
指令的作用域只在定义ARG
的阶段生效,如果需要在多个阶段使用arg,每个阶段必须都包含ARG
指令,如:
FROM busybox
ARG SETTINGS
RUN ./run/setup $SETTINGS
FROM busybox
ARG SETTINGS
RUN ./run/other $SETTINGS
【使用】
可以使用ARG
和ENV
指令指定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 .
最终的CONT_IMG_VER
变量输出为v1.0.0
。
【预定义的ARG】
Docker中有一些预定义好的参数ARG
,这些参数可以直接在build时指定,而无需在Dockerfile中先进行定义,预定义的参数有:
HTTP_PROXY
http_proxy
HTTPS_PROXY
https_proxy
FTP_PROXY
ftp_proxy
NO_PROXY
no_proxy
使用时直接通过--build-arg
传值进去即可。默认预定义的参数是无法使用docker history
看到的,如果要覆盖默认行为(即预定义参数无法通过docker history
看到),可以在Dockerfile中定义一个同名参数覆盖即可,如:
FROM ubuntu
ARG HTTP_PROXY
RUN echo "Hello World"
【构建缓存的影响】
ARG
变量不会像ENV
变量一样将其持久化到镜像中,但它也以类似的方式影响着构建缓存。如果Dockerfile中定义了和之前构建定义的不同ARG
变量值,在第一阶段就会出现“cache miss”,尤其是在所有的RUN
指令都是用了之前ARG
定义的变量就会造成上述的缓存未命中。所有预定义的ARG
变量并不会被缓存除非在Dockerfile中覆盖过(换而言之,所有Dokcerfile中使用ARG
定义的变量都会被缓存)。下面是示例:
# Dockerfile1
FROM ubuntu
ARG CONT_IMG_VER
RUN echo $CONT_IMG_VER
# Dockerfile2
FROM ubuntu
ARG CONT_IMG_VER
RUN echo hello
在构建时指定--build-arg CONT_IMG_VER=
,在上述2个Dockerfile的构建过程中,第3行会出现 cache miss 的提示(但我操作过程中进行的很顺利啊,并没有出现这个提示啊( ̄ ‘i  ̄;),我是谁,我在哪……)。
ONBUILD
当一个镜像A被用于另一个镜像B的基本镜像时(即FROM
),ONBUILD
指令用于向一个镜像中添加一个 trigger,以便可以在一段时间后执行。触发器将在下游(即当前构建的镜像B)生成的context中执行,只要它是在下游Dockerfile中FROM
指令之后立即插入的。流程如下:
ONBUILD
时,构建器会向当前正在构建的镜像的 metadata 中添加 trigger,该指令不会影响当前构建;OnBuild
字段(一个数组对象)中,可以使用docker inspect
查看这个字段;FROM
指令的一部分,下游的构建器将会寻找ONBUILD
trigger并按它们注册的顺序执行。如果其中某一个trigger执行失败了,FROM
指令将被中止,从而导致构建失败;下面是示例:
[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]
注:
ONBUILD
指令禁止嵌套使用,即类似于ONBUILD ONBUILD
;ONBUILD
指令可能不会触发FROM
或MAINTAINER
指令(即尽量不要在ONBUILD
指令中注册FROM
或MAINTAINER
指令);STOPSIGNAL
STOPSIGNAL
指令用于设置系统发送到容器中、目的用于退出的命令,这个信号可以是内核的 syscall 表中有效的无符号数字(比如9
),或格式为SIGNAME
的信号名字(比如SIGKILL
)。基本语法如下:
STOPSIGNAL signal
HEALTHCHECK
HEALTHCHECK
指令用于告诉Docker如何检测容器是否处于健康(工作)状态,有点类似于服务检测,这个功能可以检测到一些情况,如Web服务器卡在无限循环中,无法处理新连接,即使服务器进程仍在运行。当一个容器指定了一个健康检测,那该容器除了它本身的正常状态外,还会额外有一个“健康状态”health status(字段为health_status
),健康状态初始值为starting
,只有当健康检查(可以称作容器体检)通过了,它才会变成healthy
,在连续次数(这个次数可以指定)的体检结果都不通过那 health status 将变成unhealthy
。HEALTHCHECK
的基本语法为:
# 形式1:在容器内运行指定命令进行容器体检
HEALTHCHECK [OPTIONS] CMD command
# 形式2:禁用从基映像继承的任何运行状况检查
HEALTHCHECK NONE
上述形式1中的OPTIONS
可用选项有:
--interval=DURATION
:表示容器体检的时间间隔,通常在容器首次进行体检(此时可以理解为delay)和体检失败后再次体检时用到,默认是30s;--timeout=DURATION
:表示容器体检的超时时间,一次体检时长超过此时间则认为本次体检失败,置为默认为30s;--start-period=DURATION
:表示容器的启动时长,在这个时间段内体检失败的结果不作数(即不算作retries
),但是如果在这段时间内,容器体检成功了,从此刻(即使此刻还在start-period
内)开始所有之后的检查就开始生效,retries
开始统计,就认为容器已经启动成功,默认是0s;--retries=N
:表示容器体检重试的最大次数,即第一次体检失败并不意味着容器不健康,只有失败时尝试指定次数都是失败,该容器此时的health status
才会置为unhealthy
,默认是3次; 形式1中CMD
后可以是shell命令,如HEALTHCHECK CMD /bin/check-running
,也可以是exec数组(和之前类似),这些命令的执行完成后的状态就可以推测出容器的health status,下面是多数命令的常用退出状态码:
0
:success - 容器体检健康,可以正常使用;1
:unhealthy - 容器体检不健康,不能进行正常工作;2
:reserved - 不要使用此退出代码;比如:
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1
从前至后,参数分别为:检测间隔5分钟,检测超时时间为3s,检测行为指令curl -f http://localhost/ || exit 1
。为了调式失败的探测failing probes
,使用stdout和stderr命令输出的内容(只能存储最近的4096byte)都将会存储到健康状态中,这些输出都可以使用docker inspect
命令查看到。
SHELL
SHELL
指令允许在shell形式中使用的默认shell可以被重写,Linux中默认的Shell为["/bin/sh", "-c"]
,win中默认的shell为["cmd", "/S", "/C"]
。Dockerfile中的SHELL
指令必须使用JSON格式。SHELL
指令在win上是非常有用的,因为win上有两种常用的shell:cmd
和powershell
,包含可选的shell:sh
。
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
当在Dockerfile中的RUN
、CMD
、ENTRYPOINT
指令中使用shell形式会被SHELL
指令影响。下面的示例是win上的通用模式,它可以使用SHELL
指令简化,如:
RUN powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"
docker最终调用的命令为:
cmd /S /C powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"
但这样效率较低,因为存在一个不必要的命令处理器正在被调用(即shell),而且RUN
指令以shell形式给出需要一个额外的powershell -command
前缀。为了提高效率,有2种方式:
RUN
指令的JSON形式:RUN ["powershell", "-command", "Execute-MyCmdlet", "-param1 \"c:\\foo.txt\""]
;SHELL
指令,配合使用shell形式,对于win用户是更加是更加自然的语法,特别是结合使用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'
SHELL
指令还可以用于修改shell的执行方式,如在win上使用SHELL cmd /S /C /V:ON|OFF
,可以修改延迟的环境变量扩展语义。
附:
下面是整章Dockerfile最终的综合示例:
# 示例1
# 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
# 示例2
# 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"]
# 示例3
# 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