本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。

Dockerfile是一个文本文件,包含一些 Docker 指令。执行docker build,Docker 就会执行Dockerfile里面的指令,来自动创建镜像。

用法

Dockerfile 里面的指令可以访问 context 这些文件。

context 是递归的,PATH包含所有子目录,URL包含所有子模块。

例子,把当前目录当做 context,

$ docker build .

Sending build context to Docker daemon  6.51 MB
...

build 是由 Docker daemon(守护进程)来运行,而不是 CLI。

build 会把整个 context 发给 daemon。所以最好把 context 设置为空目录,把 Dockerfile 放进去。只添加需要的文件,为了提高 build 性能,还可以添加.dockerignore来排除一些文件和目录。

Warning!不要用系统根目录/作为 PATH,不然会把根目录下所有东西都传给 Docker daemon。

一般会把 Dockerfile 放在 context 根目录下,也可以使用-f来指定其他路径,

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

指定镜像存放仓库可以使用-t,

$ docker build -t shykes/myapp .

支持多个,

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

Parser directives

## 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,依赖 BuildKit
  • escape

escape

反斜杠(默认)

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

.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

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

  • 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

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

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

MAINTAINER 已经弃用了,直接使用LABLE,

LABEL maintainer="SvenDowideit@home.org.au"

EXPOSE

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

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

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,就只能用ADDURL。也可以在使用 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

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指令的缓存。

ENTRYPOINT

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才会生效。

ENTRYPOINT Exec 示例

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 Shell 示例

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 如何结合使用

CMD和ENTRYPOINT指令都定义了运行 container 时,哪些命令会执行。他们的结合有一些规则,

  1. Dockerfile 应该定义至少一个CMD 或ENTRYPOINT 。
  2. 如果使用 container 作为可执行文件,应该定义ENTRYPOINT 。
  3. 如果需要给ENTRYPOINT 定义默认参数,或者在 container 中执行 ad-hoc(临时)命令,应该使用CMD。
  4. 以可选参数运行 container 时会覆盖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

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。

遵循规则,

  • **基于 Windows 的 containers **: volumn 的目标路径必须是以下之一:
    • 不存在的或者空的目录
    • 除 C:以外的驱动
  • 在 Dockerfile 里面修改 volumn: 在 volumn 已经被声明之后的任何 build steps 尝试修改 volumn 数据,都会被忽略。
  • JSON formatting: 要用双引号,不要用单引号.
  • 在 container run-time 才会声明主机目录(挂载点): 挂载点是依赖主机的。因为主机目录不能保证对所有主机都是有用的,为了保证 image 的可移植性,不能在 Dockerfile 中挂载主机目录,而是必须在创建或运行 container 的时候。VOLUME指令也不支持host-dir这样的参数。

USER

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

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

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 变量

可以使用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 (结合以上指令使用)

预定义 ARGs

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

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 再运行。

过程如下,

  1. 当碰到ONBUILD 指令,builder 就会添加 trigger 到正在 build 的 image 的 metadata。这条指令不会影响当前 build。
  2. 在 build 的最后,所有的 triggers 都会被存储到 image 的 manifest,在 key OnBuild下面。可以用 docker inspect 命令查看。
  3. 然后 image 可能会被用来作为新 build 的 base,使用 FROM 指令。 FROM 指令在处理时,下游 builder 会查找 ONBUILD triggers,然后按它们注册的顺序执行。如果有 trigger 失败了,FROM指令就会中断,build 失败。如果 triggers 都成功了,那么FROM会完成,build 成功。
  4. Triggers 会在执行后,从最后一个 image 中清除。也就是说,它们是不会随着“父子”build 继承的。

比如你可能会添加这样的内容,

ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src

注意,1.链式ONBUILD ONBUILD是不允许的。2.ONBUILD可能不会 trigger FROM 或 MAINTAINER指令。

STOPSIGNAL

STOPSIGNAL signal

STOPSIGNAL指令设置 system call signal,发送到 container 退出。signal 可以是有效的 unsigned number(匹配 kernel’s syscall table 里的 position,比如 9),也可以是 SIGNAME(比如 SIGKILL)。

HEALTHCHECK

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 的健康状态,

  • 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

示例,每 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

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等。

Dockerfile 示例

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

以下内容可查看参考资料进一步阅读。

  • BuildKit(第三方工具)
  • Parser directives 的命令 syntax(依赖 BuildKit)
  • RUN 已知 bug(Issue 783)
  • External implementation features(依赖 BuildKit)
  • Automatic platform ARGs in the global scope(依赖 BuildKit)

参考资料

https://docs.docker.com/engine/reference/builder/