添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

记录Dockerfile的编写规则和用法

Dockerfile就是用于构建image的一系列命令和参数构成的脚本,通过 docker build -t <image_name:tag> -f </path/to/Dockerfile> . 来构建。

docker build 命令从名为 Dockerfile 的文件和 context 来构建image,context是 PATH (本地目录)或者 URL (Git repository位置)处的文件。context会以递归方式处理,所以PATH的子目录和git的submodules都会处理,同样这里要小心用于作为PATH的目录最好不要有与镜像无关的文件,通常会新建一个空文件夹做为context的PATH。

PATH下的 .dockerignore 可以用于排除文件和目录。

构建工作由Docker守护进程运行,而不是docker的CLI,其中 -t 参数用于指定镜像的repository和tag,可以有多个 -t -f 指定 Dockerfile 的路径,最后的 . 表示上下文件环境为当前目录。

1
docker build -t blueyi/python-3.6:dev -f ~/docker/Dockerfile .

.dockerignore

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

.dockerignore 文件中以 # 开头的行将被视为注释, ! 开头用于排除例外(即不排除)

另外有一个特殊的通配符 ** ,它匹配任何数量的目录(包含零),例如 **/*.go 将排除所有目录中找到的以 .go 结尾的所有文件,包含构建上下文的根。

1
2
3
4
# comment
*/temp*
*/*/temp*
temp?
  • # comment 忽略
  • /temp* 在根的任何直接子目录中排除其名称以temp开头的文件和目录。 例如,普通文件 /somedir/temporary.txt 被排除,目录 /somedir/temp 也被排除。
  • */*/temp* 从根目录下两级的任何子目录中排除以temp开头的文件和目录。 例如,排除了 /somedir/subdir/temporary.txt
  • temp? 排除根目录中名称为temp的单字符扩展名的文件和目录。 例如, /tempa /tempb 被排除。
  • Dockerfile编写规范

    Dockerfile文件内容格式如下:

    1
    2
    # Comment
    INSTRUCTION arguments

    其中 INSTRUCTION 不区分大小写,但建议大写,第一条指令必须是 FROM ,用于指定构建镜像的基础镜像, # 后面跟注释,但解析器指令除外(Parser directives)

    每一条指令都会独立运行,相互之间没有没有上下文关系

    在构建过程中每次生成一层新的镜像的时候这个镜像就会被缓存。即使是后面的某个步骤导致构建失败,再次构建的时候就会从失败的那层镜像的前一条指令继续往下执行。
    如果不想使用这种缓存功能,可以在构建的时候加上 --no-cache 选项:

    1
    docker build --no-cache -t="blueyi/centos" .

    解析器指令(Parser directives)

    解析器指令为可选的,会影响后续处理Dockerfile行,解析器指令形如:

    1
    # directive = value

    解析器指令必须放在Dockerfile的最顶端,使用行延续( \ )、出现2次、写在任何构建指令后等都将导致解析器指令无效。
    暂时只有一个解析器指令 escape ,用于指定Dockerfile中的转义字符,如果未定义,则默认为 \ ,用法如下:

    1
    # escape=\
    1
    # escape=`

    为了避免windows上的路径中会有 \ (例如: c:\\ ),建议windows上将转义符设置为

    环境变量(Environment replacement)

    环境变量由 ENV 语句声明( ENV 其实也是一个指令),可以用于其他一些指令中被解释出来,声明之后可以通过类似于shell中变量引用的方式引用: $variable_name 或者 ${variable_name} 。同样支持类似bash的修饰符:

  • ${variable:-word} 表示如果设置了variable,则结果将是该值。如果variable未设置,那么word将是结果。
  • ${variable:+word} 表示如果设置了variable,那么word将是结果,否则结果是空字符串。
  • ENV指令用法如下:

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

    第一种单行的形式value部分的任何内容包含空格,都会成为key的值,而第二种形式可以设置多个对,引号和反斜杠可用于值内包含空格。如:

    1
    2
    ENV myName="John Doe" myDog=Rex\ The\ Dog \
    myCat=fluffy
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    ENV myName John Doe
    ENV myDog Rex The Dog
    ENV myCat fluffy
    ````

    当前前一咱更好,构建的镜像更高效。

    可以通过`\`来进行对`$`转义。用例:

    ```Dockerfile
    FROM busybox
    ENV foo /bar
    WORKDIR ${foo} # WORKDIR /bar
    ADD . $foo # ADD . /bar
    COPY \$foo /quux # COPY $foo /quux

    由于每一条ENV对应一个镜像层,所以环境变量替换具有延迟性,即环境变量的声明将只会在其之后的指令中才有效
    例如:

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

    def值为hello,不是bye,因为该句中设置的变量只在后面的指令中有效。而ghi的值为bye,因为abc在上一条指令中被修改为bye。

    环境变量支持以下命令:

  • EXPOSE
  • LABEL
  • WORKDIR
  • VOLUME
  • STOPSIGNAL
  • 以及 ONBUILD 与上面指令的组合(1.4之前的版本不支持)。

  • ENV 定义的环境变量不能被CMD引用
  • 通过 ENV 定义的环境变量会永久保存到通过该镜像创建的任何容器中,可以通过 env 来查看
  • 可以通过 docker run 命令中通过 -e <key>=<value> (或 --env )参数来传递环境变量,以便在运行容器后命令或修改该变量,如: docker run -it -e "TEST=hello" <image>
  • 定义环境变量的同时可以引用已经定义的环境变量,ENV指令中可以直接引用的环境变量有: HOME HOSTNAME PATH TERM (默认是xterm)。

    如下Dockerfile是一个非常不合理的处理方式,因为多条的ENV可以合并为一条。

    1
    2
    3
    4
    5
    6
    7
    RUN set -ex && apt-get update && apt-get install -y iputils-ping
    ENV PATH /usr/local/bin:$PATH
    ENV LANG C.UTF-8
    ENV TERM xterm
    ENV PYTHON_VERSION 3.5.3
    ENV name1=ping name2=on_ip
    CMD $name1 $name2

    可以使用 docker inspect 来查看镜像的 ENV

    Dockerfile指令

    FROM

    FROM 用于为后续的指令指定其运行所需要的基础镜像(Base Image),所以Dockerfile中 FROM 必须是第一个指令(ARG除外),如果指定的该镜像不在本地,则Docker会自动从远程仓库获取。

    新的docker版本中引入了一个新的指令 ARG ,该指令是唯一可以出现在FROM之前的指令,可以配合FROM指令来指定一些FROM指令中想要引用的变量,例如版本信息。

    FROM指令用法如下:

    1
    2
    3
    4
    5
    FROM <image> [AS <name>]
    # 或者
    FROM <image>[:<tag>] [AS <name>]
    # 或者
    FROM <image>[@<digest>] [AS <name>]
  • ARG 是唯一可以出现在FROM之前的指令
  • FROM 可以在单个Dockerfile中多次出现,以创建多个镜像
  • AS <name> 选项为可选,如果有的话,该name将可以用于后面的FROM或 COPY --from=<name|index> 指令中的name,用以引用该image。
  • tag digest 也为可选的,如果省略,则默认为latest
  • ARG FROM 一起使用的一个用法:

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

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

    RUN

    RUN 用于运行命令。
    RUN有2种形式:

  • RUN <command> shell 形式,命令在shell中运行,Linux上为 /bin/sh -c ,Windows上为 cmd /S/C
  • RUN ["executable","param1","param2"] (exec 形式)
  • 每一条RUN指令都会提交一个新层到当前镜像,提交结果将会用于后续Dockerfile中的指令, 镜像历史类似于版本控制系统,可以从任何历史点进行镜像的创建,可以通过命令 docker history <image> 查看镜像历史层

    shell形式的RUN命令支持续行,例如:

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

    如果要使用不同的shell,例如 bash ,则需要使用 exec 形式,如:

    1
    RUN ["/bin/bash", "-c", "echo hello"]

    由于exec形式作为JSON数组解析,所以单词必须使用双引号(”)而不是单引号(’)括住。

    每一次运行RUN指令时,由于会重用缓存,并且不会对缓存进行有效性检查,例如 RUN apt-get dist-upgrade -y 生成的缓存也会用于下一次build,可以通过 docker build --no-cache 来禁止使用缓存。

    CMD

    CMD 指令用于指定容器启动时需要运行的程序。例如我们通常运行容器时为了能够执行其中的 /bin/bash 来进入到容器中交互,会如下运行:

    1
    docker run -it <image> /bin/bash

    后面的 /bin/bash 就相当于告诉容器运行之后运行 /bin/bash ,等效于我们在Dockerfile中添加一条:

    1
    CMD ['/bin/bash']

    如果Dockerfile已经有了CMD指令,我们传递的运行命令将会覆盖其中的CMD设置。

    一个Dockerfile中只能有一个CMD指令

    CMD 指令三种形式:

  • CMD ["executable","param1","param2"] (exec形式, 首选形式)
  • CMD ["param1","param2"] (做为ENTRYPOINT中命令的默认参数)
  • CMD command param1 param2 (shell形式,类似RUN, <cmd> 将在 /bin/sh -c 中执行)
  • 当CMD指令做为ENTRYPOINT指令时的命令参数时,同样是以JSON格式解析,所以也要使用双引号来包围参数而不是单引号。

    尽量各命令参数都使用双引号而不是单引号

    注意:CMD命令与RUN命令完全不同,RUN运行一个命令将提交一个镜像的层(layer),而CMD在构建时并不执行任何操作,只是用于指定容器的运行时要执行的命令

    LABEL

    LABEL 指令用于向镜像中添加元数据,可以通过 docker inspect 命令查看,如

    1
    docker inspect --format='{{.Config.Labels}}' <image>

    每一个 LABEL 指令都会产生一个镜像层,所以尽量将多条 LABEL 放到一条中。

    LABEL 用法:

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

    MAINTAINER (deprecated)

    该指令已经新废弃,用于指定该镜像的维护者,现在可以使用 LABEL 更好的实现该功能。用法如下:

    1
    MAINTAINER <name>
    1
    LABEL maintainer="name"

    EXPOSE

    用于指定容器运行后容器中监听的端口(也称为私有端口),当运行容器时使用 -P 参数时,容器将自动为监听端口分配宿主主机上相应的映射端口(也称为公共端口)。

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

    协议可选为 TCP UDP ,如果不指定协议,则默认为 TCP

    虽然支持 EXPOSE 时指定端口映射( EXPOSE 80:8080 私有 80 到公共 8080 ),而由于镜像在构建时并不能确定当容器运行时该端口是否被其他程序占用,所以不建议在 Dockerfile 中进行端口映射。

    1
    2
    3
    4
    5
    6
    # PORT
    EXPOSE 8080
    EXPOSE 22
    EXPOSE 8009
    EXPOSE 8005
    EXPOSE 8443

    ENV

    同上面的环境变量部分

    ADD

    用于从指定目录或者URL拷贝文件到镜像中。
    用法如下:

    1
    2
    ADD <src>... <dest>
    ADD ["<src>",... "<dest>"] # 用于路径中带有空格的情况
  • src 不是URL时,必须是相对于本地上下文件环境路径的相对路径,因为Docker会有构建之前将上下文件内容全部发送给Docker Daemon。
  • dest 结尾带有 / 时,docker会自动推测文件名,将目标文件拷贝到镜像中。(与linux中的cp命令其实一样)
  • src 为目录时,将复制目录里面的所有内容,目录本身不会被复制
  • src 为可以识别的压缩格式(tar.gz、gzip、bzip2、xz)等时,docker将会自动将其解压,解压功能类似于 tar -x ,但src为url时不可以
  • dest 必须是绝对路径,或者相对于 WORKDIR 的相对路径。
  • 用法举例:

    不好的用法

    1
    2
    3
    4
    ADD http://foo.com/package.tar.bz2 /tmp/
    RUN tar -xjf /tmp/package.tar.bz2 \
    && make -C /tmp/package \
    && rm /tmp/package.tar.bz2

    实际上最后的删除并不会生效,因为rm命令将位于另一个独立的镜像层。

    可以这样:

    1
    2
    3
    RUN curl http://foo.com/package.tar.bz2 \
    | tar -xjC /tmp/package \
    && make -C /tmp/package

    COPY

    用法与 ADD 几乎完全一样,但更纯粹,不支持URL,不支持自动解压。

    所以现在Docker团队推荐使用 COPY 进行文件拷贝,而不是 ADD

    ENTRYPOINT

    ENTRYPOINT 指令与CMD非常相似,都是指定容器运行后需要运行的命令,不同的是当与 docker run 配合使用时, docker run 后跟的执行内容将做为 ENTRYPOINT 指令指定的运行命令的参数,例如:

    1
    2
    3
    4
    5
    6
    FROM centos
    MAINTAINER allocator
    RUN yum install -y nginx
    RUN echo 'hello world' > /usr/share/nginx/html/index.html
    EXPOSE 80
    ENTRYPOINT ["/usr/sbin/nginx"]
    1
    docker run --name test -p 1080:80 -it test_nginx -g "daemon off"

    后面两个参数 -g "daemon off" 将传递给 /usr/sbin/nginx 作为参数运行。

    当CMD与ENTRYPOINT同时出现时,如果 CMD 的内容不是一个完整的指令,则不带参数运行容器时,CMD的内容将做为 ENTRYPOINT 指令的参数。而如果此时 CMD 是一个完整的命令,它将覆盖掉 ENTRYPOINT 中的内容。如果此时运行容器时再带参数,则此参数会覆盖掉 CMD 指令的内容。

    所以,可以这样使用:

    1
    2
    ENTRYPOINT ["/usr/sbin/nginx"]
    CMD ["-g daemon off"]

    VOLUME

    VOLUME 指令为容器创建挂载卷,类似于 docker run 时使用的 -v 命令进行目录映射,只是 -v 可以指定本地目录到容器指定目录的挂载,而Dockerfile中只能指定从该镜像创建容器时容器中的挂载点,具体对应的本地目录将由docker自动分配,可以通过 docker inspect <container> 查看。

    1
    VOLUME ["/data1", "/data2", ...]

    USER

    USER 指令用于设置其后的 RUN CMD ENTRYPOINT 命令的运行用户,因为默认docker将以root身份运行这些命令。

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

    WORKDIR

    WORKDIR 用于设定Dockerfile中其命令之后的 RUN CMD ENTRYPOIHNT COPY ADD 命令的执行路径。如果指定的 WORKDIR 不存在,则会自动被创建。该命令可以出现多次,如果使用的是相对目录,则该相对目录将会相对于前一个 WORKDIR 设置的目录。

    1
    WORKDIR /path/to/workdir
    1
    2
    3
    4
    WORKDIR /a
    WORKDIR b
    WORKDIR c
    RUN pwd

    最终pwd输出为 /a/b/c

    WORKDIR 中可以使用前面 ENV 设置的环境变量,如:

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

    pwd输出为 /path/$DIRNAME

    ARG

    ARG 指令在Docker 1.9版本才引入,该指令用于定义变量,其定义的变量只在build镜像过程中有效,镜像创建完成后消失,并可以通过build命令的 --build-arg <varname>=<value> 来指定其构建过程中varname的值。

    可以有多个 ARG 指令。

    ARG 定义的变量只在其后的指定中才有效。

    通过命令 docker history 可以看到在构建镜像时设置的 ARG

    如果build过程中指定了在 Dockefile 中未经ARG定义的变量,构建将会给出警告:

    1
    [Warning] One or more build-args [foo] were not consumed.
    1
    ARG <name>[=<default value>]

    ARG 设置的变量只会影响到构建过程,而 ENV 相当于改变了整个相应容器的环境变量。
    如果在 ARG 指令的后面使用 ENV 进行了同名的定义,则即使在build时指定的新值, ENV 的定义依然会覆盖 ARG 定义。

    ARG ENV 配合使用:

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

    可以这样构建:

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

    Docker中预定义了一些ARG变量:

  • HTTP_PROXY
  • http_proxy
  • HTTPS_PROXY
  • https_proxy
  • FTP_PROXY
  • ftp_proxy
  • NO_PROXY
  • no_proxy
  • 这些变量可以在构建时直接进行设置,如:

    1
    --build-arg HTTP_PROXY=http://user:pass@proxy.lon.example.com

    ONBUILD

    ONBUILD 指令用于为镜像添加一个触发器,其参数是任意一个 Dockerfile 指令,该指令不会在当前的build过程中生效,而是会在当 FROM 这个镜像创建新镜像时首先触发执行。

    1
    ONBUILD [INSTRUCTION]

    以下内容引用于网络:

    当我们在一个Dockerfile文件中加上ONBUILD指令,该指令对利用该Dockerfile构建镜像(比如为A镜像)不会产生实质性影响。

    但是当我们编写一个新的Dockerfile文件来基于A镜像构建一个镜像(比如为B镜像)时,这时构造A镜像的Dockerfile文件中的ONBUILD指令就生效了,在构建B镜像的过程中,首先会执行ONBUILD指令指定的指令,然后才会执行其它指令。

    需要注意的是,如果是再利用B镜像构造新的镜像时,那个ONBUILD指令就无效了,也就是说只能再构建子镜像中执行,对孙子镜像构建无效。其实想想是合理的,因为在构建子镜像中已经执行了,如果孙子镜像构建还要执行,相当于重复执行,这就有问题了。

    利用ONBUILD指令,实际上就是相当于创建一个模板镜像,后续可以根据该模板镜像创建特定的子镜像,需要在子镜像构建过程中执行的一些通用操作就可以在模板镜像对应的dockerfile文件中用ONBUILD指令指定。 从而减少dockerfile文件的重复内容编写。

    STOPSIGNAL

    STOPSIGNAL 指令用于设置容器退出时所要发送给容器的退出信号,必须是内核系统调用表中的合法值,如 9 SIGKILL

    1
    STOPSIGNAL signal

    HEALTHCHECK

    HEALTHCHECK 指令用于告诉docker如何去检测容器的健康状态。1.12的版本中加入,用法如下:

    1
    2
    HEALTHCHECK [OPTIONS] CMD command # 通过运行一个CMD指令指定的命令来检查容器健康状态
    HEALTHCHECK NONE # 禁用从基础镜像(base image)继承来的任何健康检查

    CMD 命令可以带有参数有:

  • --interval=DURATION (default: 30s) 表示检查的时间间隔
  • --timeout=DURATION (default: 30s) 表示检查命令多久
  • --start-period=DURATION (default: 0s) 表示启动预留给容器启动的时间
  • --retries=N (default: 3) 表示命令重试次数
  • 每一个Dockerfile中只能有一个 HEALTHECK 指令,如果有多个,则只有最后一个有效。

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

    上述示例表示每隔5分钟,运行一次curl获取网页,如果3s未返回,则认为命令运行失败,容器报告不健康。可以看到CMD执行的curl如果成功,则会正常返回,即报告健康,但如果curl失败,则会执行 exit 1 ,即返回不健康。(这里假设重试次数为1)
    其中, CMD 执行的命令退出状态码可以为以下3个中的一个:

  • 0 : success - 健康
  • 1 : unhealthy - 不健康
  • 2 : reserved - 系统保留,不建议用户使用
  • SHELL

    SHELL 指令用于覆盖其他指令以及容器中默认运行命令的shell程序,默认情况下Linux的shell为 ["/bin/sh", "-c"] ,Windows为 ["cmd", "/S", "/C"]

    SHELL指令同样必须以JSON格式写在Dockerfile中

    指定的shell将会影响 RUN CMD ENTRYPOINT 的运行。

    该指令在1.12中引入。

    1
    SHELL ["executable", "parameters"]
    1
    SHELL ["/bin/bash", "-c"]

    则将Linux容器的默认执行命令指定为 /bin/bash

    下面一下Win下的例子:

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

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

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

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

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

    Dockerfile最佳实践

    官方给出的建议