Dockerfile
是一个文本文件,包含一些Docker指令。执行docker build
,Docker就会执行Dockerfile
里面的指令,来自动建立镜像。python
Dockerfile里面的指令能够访问context这些文件。linux
context是递归的,PATH
包含全部子目录,URL
包含全部子模块。nginx
例子,把当前目录当作context,git
$ docker build . Sending build context to Docker daemon 6.51 MB ...
build是由Docker daemon(守护进程)来运行,而不是CLI。github
build会把整个context发给daemon。因此最好把context设置为空目录,把Dockerfile放进去。只添加须要的文件,为了提升build性能,还能够添加.dockerignore
来排除一些文件和目录。golang
Warning!不要用系统根目录/
做为PATH,否则会把根目录下全部东西都传给Docker daemon。web
通常会把Dockerfile放在context根目录下,也可使用-f
来指定其余路径,docker
$ docker build -f /path/to/a/Dockerfile .
指定镜像存放仓库可使用-t
,shell
$ docker build -t shykes/myapp .
支持多个,apache
$ docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .
Docker daemon在执行Dockfile的指令前,会作检查,若是有语法错误会报错,
$ docker build -t test/myapp . Sending build context to Docker daemon 2.048 kB Error response from daemon: Unknown instruction: RUNCMD
Docker daemon执行指令,是一个一个执行,一个一个提交的。执行结束会生成镜像ID。自动清理context。
RUN cd /tmp
是无效的,由于daemon是独立执行每条指令的,不会做用到后面的指令。
为了加速build过程,Docker会重复使用中间镜像(缓存),在console日志中能够看到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
cache来源于以前本地build过的镜像,或者使用docker load
加载的镜像。
若是想直接指定一个镜像做为cache,可使用--cache-from
。
# Comment INSTRUCTION arguments
#
开头是注释或者parser directive(提示解析器作特殊处理)。
指令是忽略大小写的,不过为了和参数区分,通常全大写。
Dockerfile从上往下顺序执行指令,第一条指令必须是FROM
,定义build的parent image(父镜像)。没有parent的镜像叫base image。
参数里面的#
就不是注释了,是参数的一部分,
# Comment RUN echo 'we are running some # of cool things'
注释在Dockerfile指令执行前,会被移除。如下是等价的,
RUN echo hello \ # comment world
RUN echo hello \ world
注意,注释不支持换行符\
。
注释和指令前面的空格会被忽略,如下是等价的,
# this is a comment-line RUN echo hello RUN echo world
# this is a comment-line RUN echo hello RUN echo world
可是参数里面的空格,是会被保留的,
RUN echo "\ hello\ world"
# directive=value
Parser directives是一种特殊的注释,用来提示解析器作特殊处理。
可是Parser directives并不会添加layers到build中,也不会被识别为build step。
若是注释、空行、或者指令被运行后,Docker就不会再识别Parser directives了,因此必须把Parser directives放在Dockerfile的最前面的最前面。
Parser directives是忽略大小写的,不过通常约定为全小写。同时约定随后跟一个空行。
Parser directives不支持换行符。
如下是一些无效示例,
无效--换行符
# direc \ tive=value
无效--出现了2次
# directive=value1 # directive=value2 FROM ImageName
无效--在指令以后就是普通的注释
FROM ImageName # directive=value
无效--在普通注释以后也变成了普通注释
# About my dockerfile # directive=value FROM ImageName
无效--未知命令会被视为普通注释,普通注释以后也是普通注释
# unknowndirective=value # knowndirective=value
Parser directives同一行的空格会被忽略,如下是等价的,
#directive=value # directive =value # directive= value # directive = value # dIrEcTiVe=value
目前支持2个Parser directives,
syntax
,依赖BuildKitescape
反斜杠(默认)
# escape=\
或者反引号
# escape=`
用来指定转义符。这个在Windows系统颇有用,由于\
在Windows是路径分隔符。
好比,
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>
使用escape能够替换\
为`
# 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>
环境变量(使用ENV
指令来定义环境变量)可以用在指令中做为变量,被Dockerfile
解释。还能够处理转义符,以便在语句中照字面值地包含variable-like语法。
使用$variable_name
或${variable_name}
来引用环境变量。
可使用双括弧和下划线来命名,如${foo}_bar
。同时支持bash
修饰符,
${variable:-word}
set variable
后就是set的值,没有set variable
值就是word
${variable:+word}
set variable
后值就是word
,没有set variable
就是空字符串word既能够是string,也能够是另一个环境变量。
能够在变量前加转义符,好比\$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
(结合以上指令使用)须要注意的是,变量替换是针对整条指令的,
ENV abc=hello ENV abc=bye def=$abc ENV ghi=$abc
def
的值是hello,而不是bye,由于上一条指令赋值的hello。
ghi
的值才会是bye。
.dockerignore
文件位于context根目录,会把匹配到的文件和目录排除在context以外。
这样就能够在使用ADD
和COPY
命令时,避免把一些大文件或者敏感信息文件和目录,发送到Docker daemon。
context是由PATH
和URL
定义的,因此.dockerignore
文件会匹配这2个路径。
/foo/bar
== foo/bar
示例,
# comment */temp* */*/temp* temp?
Rule | Behavior |
---|---|
# comment |
注释忽略 |
*/temp* |
排除root的子目录下,temp 开头的文件和目录。 如 /somedir/temporary.txt 和 /somedir/temp |
*/*/temp* |
排除root的二层目录下,temp 开头的文件和目录。如 /somedir/subdir/temporary.txt |
temp? |
排除root下, temp +1个字符的文件和目录。如 /tempa 和/tempb |
匹配遵循Go语言的filepath.Match规则。
Docker还支持**
,匹配任意数量的目录(包括0)。如**/*.go
排除.go
结尾的,包括context root下全部目录。
若是排除了一堆文件后,想只包含其中几个文件,可使用异常规则!
。
示例,排除.md
结尾的文件,包含README.md
,
*.md !README.md
README-secret.md
不会被排除,由于!README*.md
能匹配到README-secret.md
,又把README-secret.md
包含进来了。
.dockerignore
文件甚至能够排除Dockerfile
和.dockerignore
,然而并无什么卵用,这些文件仍是会被发送到Docker daemon,只是ADD
和COPY
命令不会把它们复制到镜像了。
FROM
指令初始化一个新的buid stage,为后面的指令设置Parent Image。
FROM [--platform=<platform>] <image> [AS <name>]
或
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
或
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
--platform
,用来定义image的平台,如linux/amd64
, linux/arm64
, 或者windows/amd64
,这样就能支持多平台镜像。
tag
digest
是可选的,都不填时,默认用最新的tag。若是找不到tag,builder就会报错。
AS name
能够给image取个别名,在后续FROM
和COPY --from=<name|index>
指令中可使用这个别名。
能够在一个Dockerfile文件中使用多个FROM
。每一个FROM
都会把上个指令建立的状态清除。因此在每一个新的FROM
指令以前,记录commit输出的最后一个image ID。
ARG
是惟一能在FROM
以前的指令。
好比--platform
,默认状况下,会使用build请求的默认平台。也可使用全局build参数,经过automatic platform ARGs
(依赖BuildKit)来强制把stage指定为本地build平台(--platform=$BUILDPLATFORM
),而后用它来在stage中cross-compile目标平台。
FROM
和ARG
怎么结合使用呢?
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
是在build stage以外的,因此它不能用在FROM
后的任何指令中。若是要用,可使用在build stage中的不带value的ARG
指令,
ARG VERSION=latest FROM busybox:$VERSION ARG VERSION RUN echo $VERSION > image_version
RUN <command>
(shell 格式,Linux /bin/sh -c
Windowscmd /S /C
)RUN ["executable", "param1", "param2"]
(exec 格式)RUN
指令会在当前镜像之上的新layer中执行命令,commit结果,commit后的镜像会在Dockerfile
的下一个step中使用。
RUN
指令的commits符合Docker理念,commit is cheap,containers能够从image历史中任何记录建立,就像source control。
可使用不一样的SHELL
,
shell格式
RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
exec格式
RUN ["/bin/bash", "-c", "echo hello"]
shell格式会调用command shell,而exec格式不会,因此exec中$HOME
是没用的,要用的话直接执行shell RUN [ "sh", "-c", "echo $HOME" ]
。
注意,exec格式被解析为JSON数组,因此只能用双引号。还需注意反斜杠,
错误
RUN ["c:\windows\system32\tasklist.exe"]
正确
RUN ["c:\\windows\\system32\\tasklist.exe"]
默认是会启动RUN
的缓存的,好比RUN apt-get dist-upgrade -y
会在下次build的时候复用。可使用docker build --no-cache
来禁用缓存。
使用ADD
和COPY
指令也能够禁用RUN
缓存。
CMD
和RUN
是不一样的。RUN
指令是在build过程当中执行command和commit结果。CMD
在build时不会执行任何command,而是为image定义command,在container(镜像建立的容器)启动的时候执行。
CMD ["executable","param1","param2"]
(exec 格式,首选)CMD ["param1","param2"]
(ENTRYPOINT默认参数)CMD command param1 param2
(shell 格式)一个Dockerfile
只能有一个CMD
指令,若是有多个,只有最后一个生效。
shell格式会调用command shell,而exec格式不会,因此exec中$HOME
是没用的,要用的话直接执行shell RUN [ "sh", "-c", "echo $HOME" ]
。
注意,exec格式被解析为JSON数组,因此只能用双引号。还需注意反斜杠。
若是想要container每次运行相同的可执行文件,须要结合 ENTRYPOINT
使用。
若是docker run
定义了参数,那么会覆盖CMD
定义。
LABEL <key>=<value> <key>=<value> <key>=<value> ...
LABEL
用来给image添加metadata,是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."
一个image能够有多个label,一个label能够有多个键值对,如下是等价的,
LABEL multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \ multi.label2="value2" \ other="value3"
label会随着image继承,从base image或parent image继承到当前image。
重复的label,会用最新的覆盖旧的。
可使用命令查看image的labels,
docker image inspect --format='' myimage
{ "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
已经弃用了,直接使用LABLE
,
LABEL maintainer="SvenDowideit@home.org.au"
EXPOSE <port> [<port>/<protocol>...]
EXPOSE
定义了container监听的网络端口,支持TCP和UDP,默认TCP。
EXPOSE
并不真正的发布端口,而只是一种预约义。
真正发布是在docker run
的时候,使用-p
或-P
来发布。
-p
发布一个或多个端口,-P
发布所有,并映射到高位端口。
示例,默认TCP,能够定义UDP,
EXPOSE 80/udp
也能够同时定义TCP和UDP,
EXPOSE 80/tcp EXPOSE 80/udp
若是这里docker run
使用了-P
,将会暴露一次TCP端口和一次UDP端口,因为会映射到高位端口,它们的端口会不同。
使用-p
指定端口,
docker run -p 80:80/tcp -p 80:80/udp ...
也可使用docker network
来建立网络在container之间通讯而不须要暴露任何端口。由于container可使用任何端口通讯。
ENV <key> <value> ENV <key>=<value> ...
ENV
用来设置环境变量。有2种形式,如下是等价的,
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>
来修改环境变量。
ENV
的做用域除了build,还包括container running。有时候会有反作用,好比ENV DEBIAN_FRONTEND noninteractive
,全部操做都是非交互式的,无需向用户请求输入,直接运行命令。可能会使apt-get用户误认为是一个Debian-based image。正确的作法是为command添加单独的环境变量,如RUN apt-get install -y python3
。
ADD [--chown=<user>:<group>] <src>... <dest> ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
ADD
有2种形式,第2种是为了支持路径包含空格,因此加了双引号。
--chown
只适用于Linux container,对Windows无效。
ADD
的做用是从<src>
复制新文件,目录或者远程文件URLs,而后添加到<desc>
所在的image文件系统。
src
若是是文件和目录,那么就是相对路径,相对于build的context。同时支持通配符,遵循Golang的filepath.Match规则。
示例,添加全部以"hom"开头的文件,
ADD hom* /mydir/
用?
匹配单个字符,
ADD hom?.txt /mydir/
<dest>
是绝对路径,或者WORKDIR
的相对路径。
示例,绝对路径,
ADD test.txt /absoluteDir/
相对路径,<WORKDIR>/relativeDir/
,
ADD test.txt relativeDir/
若是路径种包含特殊字符(如[
和]
),那么须要进行转义,
示例,添加一个文件arr[0].txt
,
ADD arr[[]0].txt /mydir/
针对Linux,可使用--chown
定义username、groupname或者UID/GID,默认新文件和目录会被设置为UID为0,GID为0。
若是只设置username不设置groupname,或只设置UID不设置GID,GID会使用和UID相同的数值。
username和groupname会被container's root filesystem /etc/passwd
and /etc/group
转换为UID/GID。若是container没有这2个文件,在设置了username/groupname后,就会报错。能够经过设置UID/GID来避免。
示例,
ADD --chown=55:mygroup files* /somedir/ ADD --chown=bin files* /somedir/ ADD --chown=1 files* /somedir/ ADD --chown=10:11 files* /somedir/
若是build使用STDIN (docker build - < somefile
),就没有build context,就只能用ADD
URL。也能够在使用STDIN时添加压缩包 (docker build - < archive.tar.gz
),压缩包根目录的Dockerfile
和其余压缩包会当作build context。
若是src
是一个远程文件URL,就会须要600权限(Linux)。若是远程文件有HTTP Last-Modified
header,header的timestamp会用来设置到dest文件的mtime
。可是mtime
不会反映文件是否修改和缓存是否应该更新。
若是URL文件须要受权,ADD
是不支持的,须要使用RUN wget
, RUN curl
,或者container里面的其余工具。
ADD
遵循如下规则:
<src>
必须在build的context 中;不能 ADD ../something /something
添加context父目录的东西。由于 docker build
的第一步是把context,目录及其子目录发送到docker daemon。<src>
是URL,<dest>
没有以斜杠结尾,那么文件从直接从URL下载后,而后直接复制到 <dest>
。<src>
是URL,<dest>
是以斜杠结尾的,那么会从URL解析出文件名,下载到<dest>/<filename>
。好比, ADD http://example.com/foobar dest/
会建立文件 dest/foobar
。URL必须是明确的路径,以保证能找到合适的文件名(http://example.com
是无效的)。<src>
是目录,那么整个目录都会被复制,包括文件系统的metadata。(目录自己不复制,只是内容)<src>
是本地压缩包(如gzip, bzip2 or xz),那么会被解压成目录。远程URL是不会解压的。解压至关于执行了 tar -x
,若是dest路径下有文件冲突,会被重命名为“2”。(压缩包不是根据文件名判断的,而是根据内容,好比一个空文件命名为.tar.gz
,是不会被解压复制的)<src>
是任何其余文件,就会随同它的metadata一块儿复制。此时 <dest>
以斜杠 /
结尾的话,就会被认为是一个目录,<src>
的内容会被写到<dest>/base(<src>)
。<src>
定义的是多个资源,不管是直接仍是通配符匹配到的, <dest>
必须是一个目录,且以斜杠/
结尾。<dest>
不以斜杠结尾,那么就会被认为是一个普通文件,那么<src>
会被写到<dest>
。<dest>
不存在,那么path中的全部未建立的目录都会自动建立。若是src
内容改变了,在第一次遇到ADD
指令后,会禁用后续全部指令的缓存,包括RUN
指令的缓存。
COPY
和ADD
的区别在于ADD
能够添加远程URLS,COPY
不能。
COPY [--chown=<user>:<group>] <src>... <dest> COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
COPY
有2种形式,第2种是为了支持路径包含空格,因此加了双引号。
--chown
只适用于Linux container,对Windows无效。
COPY
的做用是从<src>
复制新文件,目录,而后添加到<desc>
所在的image文件系统。
src
若是是文件和目录,那么就是相对路径,相对于build的context。同时支持通配符,遵循Golang的filepath.Match规则。
示例,添加全部以"hom"开头的文件,
COPY hom* /mydir/
用?
匹配单个字符,
COPY hom?.txt /mydir/
<dest>
是绝对路径,或者WORKDIR
的相对路径。
示例,绝对路径,
COPY test.txt /absoluteDir/
相对路径,<WORKDIR>/relativeDir/
,
COPY test.txt relativeDir/
若是路径种包含特殊字符(如[
和]
),那么须要进行转义,
示例,添加一个文件arr[0].txt
,
COPY arr[[]0].txt /mydir/
针对Linux,可使用--chown
定义username、groupname或者UID/GID,默认新文件和目录会被设置为UID为0,GID为0。
若是只设置username不设置groupname,或只设置UID不设置GID,GID会使用和UID相同的数值。
username和groupname会被container's root filesystem /etc/passwd
and /etc/group
转换为UID/GID。若是container没有这2个文件,在设置了username/groupname后,就会报错。能够经过设置UID/GID来避免。
示例,
COPY --chown=55:mygroup files* /somedir/ COPY --chown=bin files* /somedir/ COPY --chown=1 files* /somedir/ COPY --chown=10:11 files* /somedir/
若是build使用STDIN (docker build - < somefile
),就没有build context,就不能用COPY
。
COPY
支持--from=<name|index>
,用来指定src为以前buid的image(经过FROM .. AS <name>
建立的)来替换build context。既能够是name也能够是index数字(全部使用FROM
指令创建的build stages)。若是经过name找不到build stage,就会去找同名的image。
COPY
遵循如下规则:
<src>
必须在build的context 中;不能 COPY ../something /something
添加context父目录的东西。由于 docker build
的第一步是把context,目录及其子目录发送到docker daemon。<src>
是目录,那么整个目录都会被复制,包括文件系统的metadata。(目录自己不复制,只是内容)<src>
是任何其余文件,就会随同它的metadata一块儿复制。此时 <dest>
以斜杠 /
结尾的话,就会被认为是一个目录,<src>
的内容会被写到<dest>/base(<src>)
。<src>
定义的是多个资源,不管是直接仍是通配符匹配到的, <dest>
必须是一个目录,且以斜杠/
结尾。<dest>
不以斜杠结尾,那么就会被认为是一个普通文件,那么<src>
会被写到<dest>
。<dest>
不存在,那么path中的全部未建立的目录都会自动建立。若是src
内容改变了,在第一次遇到COPY
指令后,会禁用后续全部指令的缓存,包括RUN
指令的缓存。
exec 格式
ENTRYPOINT ["executable", "param1", "param2"]
shell 格式
ENTRYPOINT command param1 param2
ENTRYPOINT
用来配置container做为可执行文件来运行。
示例,使用默认内容启动nginx,监听80端口,
$ docker run -i -t --rm -p 80:80 nginx
docker run <image>
的命令行参数,会被添加到exec格式中的全部元素以后,并覆盖CMD
指令定义的元素。这样就能够把参数传递给entry point,也就是docker run <image> -d
会把-d
传递给entry point。可使用docker run --entrypoint
来覆盖ENTRYPOINT
指令(可是只能把binary设置为exec,不能用sh -c
)。
shell
格式会禁用掉CMD
或者run
命令行参数,可是有个缺点就是,ENTRYPOINT
就不是做为/bin/sh -c
的子命令来启动的了,也就是不能传递signals。也就意味着可执行文件,不是container的PID 1
,也不会接收Unix signals(一种软件中断)。这样可执行文件就不会接收来自docker stop <container>
的SIGTERM
。
只有Dockerfile
的最后一个ENTRYPOINT
才会生效。
FROM ubuntu ENTRYPOINT ["top", "-b"] CMD ["-c"]
当运行container,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
,
$ 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
top -b -H
,其中top -b
是ENTRYPOINT
设置的,-H
是docker命令行参数,添加到了ENTRYPOINT
后面,覆盖了CMD
的-c。
而后能够优雅地使用docker stop test
请求top
shut down。
示例,使用ENTRYPOINT
在前台运行Apache(也就是PID 1
),
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"]
若是想编写单个可执行文件的启动脚本,可使用exec
和gosu
命令,来确保可执行文件可以接收到Unix signals。
#!/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 "$@"
最后,若是在shutdown的时候须要作一些额外的清理(或者和其余containers交互),或者是多个协调而不是单个可执行文件,就可能须要确保ENTRYPOINT
脚本可以接收Unix signals,传递,而后作更多工做,
#!/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
来运行这个image,那么就可使用docker exec
或docker top
来验证container处理,而后使用脚本中止Apache,
$ 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
shell格式会调用command shell,而exec格式不会,因此exec中$HOME
是没用的,要用的话直接执行shell RUN [ "sh", "-c", "echo $HOME" ]
。
注意,exec格式被解析为JSON数组,因此只能用双引号。还需注意反斜杠。
ENTRYPOINT
定义一个简单的string,而后它就会在/bin/sh -c
中执行。shell格式使用shell processing来替代shell environment variables,而后会忽略任何CMD
或docker run
命令行参数。为了确保docker stop
能直接signal任何运行的ENTRYPOINT
可执行文件,记住使用exec
开始,
FROM ubuntu ENTRYPOINT exec top -b
运行这个image时,你会看到单个PID 1
进程,
$ 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
,也会干净的退出,
$ /usr/bin/time docker stop test test real 0m 0.20s user 0m 0.02s sys 0m 0.04s
若是忘了在ENTRYPOINT
前添加exec
,
FROM ubuntu ENTRYPOINT top -b CMD --ignored-param1
运行(为下一步设置一个name),
$ 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
你就会看到ENTRYPOINT
定义的top
不是PID 1
。
若是执行docker stop test
,container就不会干净地退出。stop
命令会在超时后被强制发送一个SIGKILL
,
$ 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
real 10.19s超时。
CMD
和ENTRYPOINT
指令都定义了运行container时,哪些命令会执行。他们的结合有一些规则,
CMD
或ENTRYPOINT
。ENTRYPOINT
。ENTRYPOINT
定义默认参数,或者在container中执行ad-hoc(临时)命令,应该使用CMD
。CMD
。下面这个表格展现了CMD
和ENTRYPOINT
指令的不一样组合
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 |
注意,若是CMD
是从base image定义的,那么设置ENTRYPOINT
会重置CMD
为空值。此时若是要使用CMD
,必须在当前image从新定义。
VOLUME ["/data"]
VOLUME
指令用来建立挂载点,把container挂载到native host(宿主机)或其余container。
value能够是JSON array,如VOLUME ["/var/log/"]
,也能够是string,如VOLUME /var/log
或VOLUME /var/log /var/db
。
docker run
命令会用base image中定义的location中存在的任何数据,来初始化新建立的volumn。
示例,
FROM ubuntu RUN mkdir /myvol RUN echo "hello world" > /myvol/greeting VOLUME /myvol
docker run
会在/myvol
建立一个挂载点,而后把greeting
复制到新建立的volumn。
遵循规则,
C:
之外的驱动VOLUME
指令也不支持host-dir
这样的参数。USER <user>[:<group>]
或
USER <UID>[:<GID>]
USER
指令用于RUN
, CMD
和ENTRYPOINT
指令执行时指定user name / group。USER
指令能够设置user name(或UID),可选用user group(或GID)。
若是定义了user group,那么这个user就只有这个group的membership,任何其余配置的group memberships都会被忽略。
若是user没有primary group,那么image(或者下一条指令)就会以root
group运行。
在Windows,若是不是内建帐号,必须先建立。能够在Dockerfile中调用net user
命令,
FROM microsoft/windowsservercore # Create Windows user in the container RUN net user /add patrick # Set it for subsequent commands USER patrick
WORKDIR /path/to/workdir
WORKDIR
为RUN
, CMD
, ENTRYPOINT
, COPY
and ADD
指令设置工做目录。
若是WORKDIR
不存在,即便后面的Dockerfile不会用到,它仍然会被建立。
WORKDIR
指令能够在Dockerfile中定义屡次。若是是相对路径,那么就是相对于上一条WORKDIR
指令的路径。
示例,
WORKDIR /a WORKDIR b WORKDIR c RUN pwd
pwd
的结果是/a/b/c
。
WORKDIR
能够引用ENV
定义的环境变量,示例,
ENV DIRPATH /path WORKDIR $DIRPATH/$DIRNAME RUN pwd
pwd
的结果是/path/$DIRNAME
。
ARG <name>[=<default value>]
ARG
指令定义变量,用户能够在使用docker build
命令带参数--build-arg <varname>=<value>
,在build-time传递这个变量给builder。若是用户指定了一个build参数而没有在Dockerfile中定义,build会报warning,
[Warning] One or more build-args [foo] were not consumed.
一个Dockerfile能够包含一个或多个ARG
指令。
示例,
FROM busybox ARG user1 ARG buildno # ...
警告!不建议使用build-time变量来传递私密数据,如github keys,用户认证信息等。由于image的任何用户均可以使用docker history
查看build-time变量。
ARG
指令能够设置默认值(可选),
FROM busybox ARG user1=someuser ARG buildno=1 # ...
若是ARG
指令有默认值,在build-time没有值传递,那么builder会用这个默认值。
ARG
指令是在它被定义那一行生效的,而不是命令行被使用的时候,或者其余地方。
示例,
FROM busybox USER ${user:-some_user} ARG user USER $user # ...
用户build这个文件,调用,
$ docker build --build-arg user=what_user .
第2行的USER
结果为some_user
由于user
变量是在第3行定义的。
第4行的USER
结果为what_user
,由于user
变量已经被定义了,在命令行传递了what_user
值。
在ARG
指令定义以前,任何变量使用结果都是空string。
在ARG
定义的build stage结束时,ARG
指令就超出范围了。为了在多个stages使用同一个arg,每一个stage都必须包括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
假设使用这条命令build image,
$ docker build --build-arg CONT_IMG_VER=v2.0.1 .
RUN
会使用v1.0.0
而不是ARG
传递的v2.0.1
。这个行为有点相似于shell脚本,一个局部变量会覆盖经过参数传递的变量,或者从环境定义继承的变量。
仍是上面的例子,定义不一样的ENV
会把ARG
和ENV
结合的更好用,
FROM ubuntu ARG CONT_IMG_VER ENV CONT_IMG_VER ${CONT_IMG_VER:-v1.0.0} RUN echo $CONT_IMG_VER
不像ARG
,ENV
的值会在build image中持久化。若是不用--build-arg
build,
$ docker build .
用这个Dockerfile,CONT_IMG_VER
仍然会持久化在这个image,它的值是v1.0.0
,由于在第3行用ENV
定义了默认值。
在这个示例中,经过ENV
指令,能够把命令行参数传递进来,而后持久化到最终的image,实现了变量扩展。变量扩展只支持Dockerfile指令的一部分指令。
ADD
COPY
ENV
EXPOSE
FROM
LABEL
STOPSIGNAL
USER
VOLUME
WORKDIR
ONBUILD
(结合以上指令使用)Docker有一些预约义的ARG
变量,你能够不使用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
变量中意外泄露敏感认证信息的风险。
示例,使用--build-arg HTTP_PROXY=http://user:pass@proxy.lon.example.com
来build Dockerfile,
FROM ubuntu RUN echo "Hello World"
HTTP_PROXY
变量不会输出到docker history
,也不会被缓存。若是代理服务器变成了http://user:pass@proxy.sfo.example.com
,后续的build不会致使cache miss。
可使用ARG
来覆盖这个默认行为,
FROM ubuntu ARG HTTP_PROXY RUN echo "Hello World"
当build这个Dockerfile的时候,HTTP_PROXY
会存到docker history
中,若是它的值改变了,会把build缓存禁用掉。
ARG
变量并不会像ENV
持久化到image,可是会以相似的方式,影响到build缓存。若是Dockerfile定义了一个ARG
变量,这个变量和前一个build不同,那么在第一次用这个变量的时候会发生"cache miss"(不是定义的时候)。尤为是,全部ARG
后面的RUN
指令通常都会使用ARG
变量,这样就会致使cache miss。可是全部预约义ARGs是没有影响cache的,除非是在Dockerfile中有一个同名的ARG
指令。
示例,2个Dockerfile
FROM ubuntu ARG CONT_IMG_VER RUN echo $CONT_IMG_VER
FROM ubuntu ARG CONT_IMG_VER RUN echo hello
若是在命令行指定--build-arg CONT_IMG_VER=<value>
,以上2个示例在第2行都不会cache miss,第3行会cache miss。ARG CONT_IMG_VER
会致使RUN那一行被认为是执行了CONT_IMG_VER=<value>
echo hello,因此若是<value>
改变了,就cache miss了。
另一个示例,
FROM ubuntu ARG CONT_IMG_VER ENV CONT_IMG_VER $CONT_IMG_VER RUN echo $CONT_IMG_VER
第3行会发生cache miss。由于ENV
引用的ARG
变量经过命令行改变了。另外,在这个示例中,ENV
会致使image包含这个value(ENV
会持久化到image中)。
若是ENV
和ARG
指令重复,
FROM ubuntu ARG CONT_IMG_VER ENV CONT_IMG_VER hello RUN echo $CONT_IMG_VER
第3行就不会发生cache miss,由于CONT_IMG_VER
的值是常量(hello
)。所以第4行RUN
指令用到的环境变量和值在build之间不会改变。
ONBUILD <INSTRUCTION>
ONBUILD
指令会在image中添加一个trigger,这个trigger会在image做为base的时候触发。trigger会在下游的 build context中执行,就像在下游的Dockerfile
中,在 FROM
指令以后,它就已经被当即嵌入了。
任何build指令均可以注册为trigger。
若是你build一个image,这个image会做为base来build其余images,这就颇有用。好比,一个应用build环境或者一个deamon自定义配置。
示例,若是一个image是可复用的Python应用builder(用来build新的应用image),那么它须要把应用源码添加到一个特定目录,而后调用build脚本。此时ADD
和RUN
指令是没法访问应用源码的,每一个应用build的源码也可能不同。你能够简单地,给应用开发者提供Dockerfile
样本文件来复制粘贴到他们的应用中,但这是低效、易出错和困难去作更新的,由于这个和“应用定义”代码混淆了。
可使用ONBUILD
指令来提早注册指令,在下个build stage再运行。
过程以下,
ONBUILD
指令,builder就会添加trigger到正在build的image的metadata。这条指令不会影响当前build。OnBuild
下面。能够用 docker inspect
命令查看。FROM
指令。 FROM
指令在处理时,下游builder会查找 ONBUILD
triggers,而后按它们注册的顺序执行。若是有trigger失败了,FROM
指令就会中断,build失败。若是triggers都成功了,那么FROM
会完成,build成功。好比你可能会添加这样的内容,
ONBUILD ADD . /app/src ONBUILD RUN /usr/local/bin/python-build --dir /app/src
注意,1.链式ONBUILD ONBUILD
是不容许的。2.ONBUILD
可能不会trigger FROM
或 MAINTAINER
指令。
STOPSIGNAL signal
STOPSIGNAL
指令设置system call signal,发送到container退出。signal能够是有效的unsigned number(匹配kernel’s syscall table里的position,好比9),也能够是SIGNAME(好比SIGKILL)。
2种格式,
HEALTHCHECK [OPTIONS] CMD command
(经过运行container里面的命令来检查container)HEALTHCHECK NONE
(禁用健康检查,从base image继承)HEALTHCHECK
指令用来告诉Docker怎样测试container是否还在工做。好比虽然server一直在运行,可是实际上已经死循环了,没法处理新链接了。
当container定义了健康检查,就会把健康状态添加到status中。status初始化是starting
。不管健康检查何时经过,它都会变为healthy
(不管以前是什么状态)。在必定数量的连续失败后,它会变为unhealthy
。
第一种格式的OPTION
能够是,
--interval=DURATION
(default: 30s
)--timeout=DURATION
(default: 30s
)--start-period=DURATION
(default: 0s
)--retries=N
(default: 3
)在container开始后的interval seconds ,会运行健康检查。每一个健康检查完成后,等待interval seconds再次运行。
若是健康检查运行的时候超过了timeout seconds,就认为失败。
失败的次数若是达到了retries的值,就认为unhealthy
。
start period指定了container须要启动的时间。在这期间探针失败(Probe failure)不会记做重试次数。可是,若是在这期间健康检查经过了,那么container就认为已经启动了,这以后的失败(all consecutive failures)就会记做重试次数。
一个Dockerfile只能有一个HEALTHCHECK
指令。若是有多个,那么只有最后一个HEALTHCHECK
生效。
第1种格式的command
既能够是shell命令(如,HEALTHCHECK CMD /bin/check-running
),也能够是exec
数组。
command的退出状态反应了container的健康状态,
示例,每5分钟检查1次,以确保web服务器能在3秒内为网站首页提供服务,
HEALTHCHECK --interval=5m --timeout=3s \ CMD curl -f http://localhost/ || exit 1
为了帮助debug失败探针(failing probes),任何写到stdout或stderr输出文本(UTF-8编码)都会被存储到健康状态,而且可使用docker inspect
查询。并且输出应该简短(目前只有最开始的4096 bytes会被存储)。
当container的健康状态改变了,会用新的状态生成一个health_status
事件。
SHELL ["executable", "parameters"]
SHELL
指令容许重写shell格式命令的默认shell。Linux的默认shell是["/bin/sh", "-c"]
,Windows的默认shell是["cmd", "/S", "/C"]
。SHELL
指令必须在Dockfile中写成JSON格式。
SHELL
指令在Windows特别有用,由于Windows有2个经常使用的不一样的原生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
当shell格式的RUN
, CMD
,ENTRYPOINT
出如今Dcokerfile中时,SHELL
指令能影响这些指令。
示例,Windows上常见的模式,能够经过使用SHELL指令进行简化,
RUN powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"
docker调用的命令,
cmd /S /C powershell -command Execute-MyCmdlet -param1 "c:\foo.txt"
这个有点低效,有2个缘由。首先,有一个没必要要的cmd.exe命令行处理器(aka shell)被调用了。其次,shell格式的RUN
指令须要额外的前缀命令powershell -command
。
为了更高效,有2种机制。其一是使用JSON格式,
RUN ["powershell", "-command", "Execute-MyCmdlet", "-param1 \"c:\\foo.txt\""]
JSON格式是清晰的,不会使用没必要要的cmd.exe。可是须要双引号和转义符,显得有点冗余。
。其二是用SHELL
指令和shell
格式,这样能够给Windows用户更天然的语法,特别是和escape
parser directive结合使用的时候,
# 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
指令也能被用来修改shell操做方式。好比在Windows用SHELL cmd /S /C /V:ON|OFF
,能够修改延迟环境变量扩展语义。
SHELL
指令也能够用在Linux上,可选的shell有zsh
, csh
, tcsh
等。
# 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.
如下内容可查看参考资料进一步阅读。
参考资料
https://docs.docker.com/engine/reference/builder/
下一篇《Dockerfile最佳实践》,欢迎持续关注哦。
版权申明:本文为博主原创文章,转载请保留原文连接及做者。
专一测试,坚持原创,只作精品。