Dockerfile详解

由于构建Docker镜像的源码,是一个包含一些指令的文本文档,

Dockerfile Format

Format

  • “#”: 注释
  • 指令:参数

指令大小写不明感,但是一般采用代写,

.dockeringore

在Docker开始构建镜像时,Docker客户端会先在上下文目录中寻找.dockerignore文件,根据.dockerignore文件排除上下文目录中的部分文件和目录,然后把剩下的文件和目录传递给Docker服务端开始构建。“.dockerignore”语法与“.gitignore”相同。

  • 使用场景
    对于大型项目在构建镜像时,一般不建议在项目的根目录放置Dockerfile文件,因为上下文内容太过庞杂,构建缓存会非常之大,解决办法有两种。
    一种是新建一个Docker相关的文件夹,改变构建上下文,构建时指定上下文,以减少不必要的目录进入构建缓存。另外一种办法就是使用“.dockerignore”文件,

环境变量替换

${variablename:-word}:如果变量没有被设置,则采用“word”给其赋默认值,如果有则仍采用“variablename”本身的值。

$ echo ${NAME:-tom}
tom
$ NAME=jerry
$ echo ${NAME:-tom}
jerry

v a r i a b e l n a m e : + w o r d {variabelname:+word}则与 variabelname:+word{variablename:-word}相反。

$ echo ${NAME:+tom}
tom
$ unset NAME

Dockerfile Instructions

FROM

  • FROM指令是重要的一个且必须为Dockerfile文件开篇的第一行非注释行,用于为镜像文件构建过程中指定基础镜像,后续的指令运行与基础镜像所提供的运行环境

  • 实践中,基础镜像可以是任何可用的镜像文件,默认情况下,docker build会在docker主机上查找指定的镜像文件,在其不存在时,则会从Docker Hub Registry上拉取所需的镜像文件

  • 如果找不到指定的镜像文件,docker build会返回一个错误信息。

  • Syntax

    • **FROM repository[:tag]**或

    • FROMrepository@digest

      采用digest更为安全,指明哈希码。

      • repository:指定作为base image的名称;
      • tag:base image的标签,为可选项,默认为“latest”

MAINTAINER (depreacted)

  • 注意此命令已废弃,但仍可使用,现在一般用“LABLE”。用于让Dockerfile制作者提供本人的详细信息,一般为邮箱等。

  • Dockerfile并不限制MAINTAINER指令可出现的位置,但一般推荐将其放置于FROM之后

  • Syntax

    • MAINTAINER <author’s detail>

      • <author’s detail>可是任何文本信息,但约定俗成用作者名称和邮箱地址

        例如:MAINTAINER "gzr"gezirong@126.com

LABLE

kv键值对,例如:

LABLE MAINTAINER=“gzr gezirong@126.com

COPY

  • 用于从Docker主机复制文件至创建的新镜像文件

  • Syntax

    • COPY src … dest
    • COPY [“src”, … “dest”]
      • src:要复制的源文件或目录,支持使用通配符
      • dest: 目标路径,即在创建的image的文件系统路径;建议为dest使用绝对路径,否则,COPY指定则以WORKDIR为起始路径;

    注意:在路径中有空白字符时,通常使用第二种格式

  • 文件复制准则

    • src必须是build上下文中的路径,不能是其父目录中的文件
    • 如果src是目录,则其内部文件或子目录会被递归复制,但src目录本身不会被复制
    • 如果指定了多个src,或在src中使用了通配符,则dest必须是一个目录,且必须以/结尾
    • 如果dest事先不存在,它将会被自动创建,则包括其父目录路径。

ADD(解压缩 )

  • ADD指令类似于COPY指令,ADD支持使用TAR文件和URL路径

  • Syntax

    • ADD src … dest
    • ADD [“src”, … “dest”]
      • src:要复制的源文件或目录,支持使用通配符
      • dest: 目标路径,即在创建的image的文件系统路径;建议为使用绝对路径,否则,COPY指定则以WORKDIR为起始路径;

    注意:在路径中有空白字符时,通常使用第二种格式

  • 操作准则

    • 同COPY指令

    • 如果src为URL,且dest不以/结尾,则src指定的文件将被下载并直接被创建为dest;

      如果dest以 / 结尾,则文件名URL指定的文件将被直接下载并保存为dest / filename

    • 如果src是一个本地系统上的压缩格式的tar文件,它将展开为一个目录,其行为类似于"tar -x"命令;然而,通过URL获取到的tar文件将不会自动展开;

    • 如果src有多个,或其间接或直接使用了通配符,则dest必须是一个以 / 结尾的目录路径;如果dest不以 / 结尾,则将其视作一个普通文件,src的内容将被直接写入到dest。

# Dockerfile test
FROM busybox:latest
MAINTAINER gzr <gezr17@lzu.edu.cn>
COPY test.html /data/web/html/
COPY yum.repos.d /etc/yum.repos.d/
ADD http://nginx.org/download/nginx-1.16.0.tar.gz /usr/local/src/
# 创建镜像
$ docker build -t tinyhttpd:v0.1-3 ./
$ docker run --name tinyweb1 --rm tinyhttpd:v0.1-3 ls /usr/local/src
nginx-1.16.0.tar.gz
# 如上,说明压缩包并没有打开。
# 如果把需要使用的压缩包事先下载到本地,则会解压目录,在Dockerfile中增加如下指令
ADD nginx-1.16.0.tar.gz /usr/local/src
# 然后重新创建镜像
$ docker run --name tinyweb1 --rm tinyhttpd:v0.1-4 ls /usr/local/src
nginx-1.16.0
$ docker run --name tinyweb1 --rm tinyhttpd:v0.1-4 ls /usr/local/src/nginx-1.16.0
CHANGES
CHANGES.ru
LICENSE
README
auto
conf
configure
contrib
html
man
src
# 如上,可以发现nginx的tar包已经自动解压了。

WORKDIR

  • 用于为Dockerfile中所有的RUN、CMD、ENTRYPOINT、COPY和ADD指定设定工作目录
  • Syntax
    • WORKDIR dirpath

      • 在Dockerfile文件中,WORKDIR指令可出现多次,其路径也可以为相对路径,不过,其是相对此前一个WORKDIR指令指定的路径
      • 另外,WORKDIR也可调用有ENV指定定义的变量
    • 例如

      WORKDIR /var/log

      WORKDIR $STATEPATH

VOLUME

  • 用于在image中创建一个挂载点目录,以挂载Docker host上的卷或者其它容器上的卷
  • Syntax
    • VOLUME mountpoint
    • VOLUME[“mountpoint”]
  • 如果挂载点目录路径下此前在文件存在,docker run 命令在卷挂载完成后将此前的所有文件复制到新挂载的卷中。
# 在Dockerfile中加入如下行
VOLUME /data/mysql/
$ docker build -t tinyhttpd:v0.1-5 ./
$ docker run --name tinyweb1 --rm tinyhttpd:v0.1-5 sleep 60
$ docker inspect tinyweb1| grep Destination
 "Destination": "/data/mysql"

EXPOSE

  • 用于为容器打开指定要监听的端口以实现与外部通信
  • Syntax
    • EXPOSE port [/protocol] [/port[/protocol]…]
      • protocol用于指定传输层协议,可为tcp或udp二者之一,默认为TCP协议
  • EXPOSE指令可一次指定多个端口,例如
    • EXPOSE 11211/udp 11211/tcp
# 即使Dockerfile中指定了暴露端口,仍然需要暴露在命令行加入-P端口暴露给宿主机
$ docker run --name tinyweb1 --rm -P tinyhttpd:v0.1-6 /bin/httpd -f -h /data/web/html

ENV

  • 用于为镜像定义所需的环境变量,并可被Dockerfile文件中位于其后的其他指令(如 ENV、ADD、COPY等)所调用。
  • 调用格式为$variable_name 或 ${variable_name}
  • Syntax
    • ENV key value
    • ENV key=value
  • 第一种格式中,key之后的所有内容均被视作其value的组成部分,因此,一次只能设置一个变量;
  • 第二种格式可用一次设置多个变量,每个变量为一个“key=value”的键值对,如果value中包含空格,可以以反斜杠()进行转义,也可通过对value加引号进行标识;另外,反斜线也可用于续行;
  • 定义多个变量时,建议使用第二种方式,以便于在同一层中完成所有功能。
# 在Dockerfile中增加如下内容:
ENV DOC_ROOT /data/web/html/
COPY test.html ${DOC_ROOT:-/data/web/html/}

# 也可以在将镜像初始化为容器,在命令行中注入环境变量采用-e或者,--env
$ docker run --name tinyweb1 --rm -P -e WEB_SERVER_PACKAGE="nginx-1.15.2" tinyhttpd:v0.1-7 printenv
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=6508bf3524f6
WEB_SERVER_PACKAGE=nginx-1.15.2
DOC_ROOT=/data/web/html/
HOME=/root

RUN

  • 用于指定docker build过程中运行的程序,其可以是任何命令
  • Syntax
    • RUN command
    • RUN[“executable”,“param1”,“param2”]
  • 第一种格式中,通常是一个shell命令,且以“/bin/sh -c”来运行它,这意味着此进程在容器中的PID不为1,不能接收Unix信号,因此,当使用docker stop container命令停止容器时,此进程接收不到SIGTERM信号;
  • 第二种语法格式中的参数是一个JSON格式的数组,其中executable为要运行的命令,后面的paramN为传递给命令的选项或参数;然后,此种格式指定的命令不会以“/bin/sh -c”来发起,因此常见的shell操作如变量替换以及通配符(?, *等)替换将不会进行;不过,如果要运行的命令依赖于此shell特性的话,可以将其替换为类似下面的格式
    • RUN ["/bin/sh", “-c”, “executable”, “param1”]
  • 一个容器的真正成为容器的过程有俩:docker build成镜像,docker run使其成为容器。RUN命令是在docker build阶段执行的命令
  • 可以多次使用,采用&& 和 \来使指令简洁,并且镜像层更少。

CMD

  • 类似于RUN指令,CMD指令也可用于运行任何命令或应用程序,不过,二者的运行时间点不同
    • RUN指令运行于镜像文件构建过程中(docker build),而CMD指令运行于基于Dockerfile构建出的新镜像文件启动一个容器时(docker run)
    • CMD指令的首要目的在于为启动的容器指定默认要运行的程序,且其运行结束后,容器也将终止;不过,CMD指定的命令其可以被docker run的命令行选项所覆盖
    • 在Dockerfile中可以存在多个CMD指令,但仅最后一个生效
  • Syntax
    • CMD command
    • **CMD[" executable ",“param1”,“param2”]**或
    • CMD[“param1”,’'param2"]
  • 前两种语法格式的意义同RUN
  • 第三种则用于为ENTRYPOINT指令提供默认参数
# 在Dockerfile中添加以下内容:
FROM busybox
LABEL maintainer="gzr <gezirong@126.com>" app="httpd"

ENV WEB_DOC_ROOT="/data/web/html"

RUN mkdir -p $WEB_DOC_ROOT && \
    echo '<h1>Busybox httpd server.</h1>' > ${WEB_DOC_ROOT}/index.html

` CMD /bin/httpd -f -h ${WEB_DOC_ROOT}`

$ docker build -t tinyhttpd:v0.2-1 .
$ docker image inspect tinyhttpd:v0.2-1
"Cmd": [
                "/bin/sh",
                "-c",
                "/bin/httpd -f -h ${WEB_DOC_ROOT}"
            ]
# 上述输出内容表示,调用了shell
$ docker exec -it tineweb2 /bin/sh
/ # ps
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/httpd -f -h /data/web/html
    7 root      0:00 /bin/sh
   13 root      0:00 ps
   
# 然而在Dockerfile增加此行,则会报错
CMD ["/bin/httpd","-f","-h ${WEB_DOC_ROOT}"]
$ docker run -it --name tinyweb2 --rm -P tinyhttpd:v0.2-2
httpd: can't change directory to ' ${WEB_DOC_ROOT}': No such file or directory
failed to resize tty, using default size
# 这是因为它默认不会启动为shell的子进程。

是使镜像启动为容器时,默认调用的命令。CMD是只能有一个有效。

ENTRYPOINT

  • 类似于CMD指令的功能,用于为容器指定默认运行程序,从而使得容器像是一个单独的可执行程序
  • 与CMD不同的是,由ENTRYPOINT启动的程序不会被docker run 命令行指定的参数所覆盖,而且,这些命令行参数会被当做参数传递给ENTRYPOINT指定的程序。
    • 不过,docker run命令的–entrypoint选项的参数可覆盖ENTRYPOINT指定的程序
  • Syntax
    • ENTRYPOINT command
    • ENTRYPOINT[“executable”,“param1”,“param2”]
  • docker run命令传入的命令参数会覆盖CMD指令的内容并且附加到ENTRYPOINT命令最后作为其参数使用
  • Dockerfile文件中也可以存在多个ENTRYPOINT指令,但仅有最后一个会生效。
# 在Dockerfile中增加如下行内容
ENTRYPOINT /bin/httpd -f -h ${WEB_DOC_ROOT}
$ docker build -t tinyhttpd:v0.2-5 .
$ docker run -it --name tinyweb2 --rm -P tinyhttpd:v0.2-5
# 然而执行如下命令,相当于在ENTRYPOINT末尾增加ls /data/web/html/命令。有可能执行不了,需要采用--entrypoint参数来指定。
$ docker run -it --name tinyweb2 --rm -P tinyhttpd:v0.2-5 ls /data/web/html/
# 类似于下述的描述,加入--entrypoint 此时的ls是覆盖了CMD
$ docker run -it --name tinyweb2 --rm -P --entrypoint "ls /data/web/html/" tinyhttpd:v0.2-5

如果CMD和ENTRYPOINT同时定义了,CMD命令结果会被作为传递给ENTRYPOINT。

注意:JSON数组中,要使用双引号。

USER

  • 用于指定运行image时的或运行Dockfile中任何RUN、CMD或ENTRYPOINT指令指定的程序时的用户名或UID
  • 默认情况下,container的运行身份为root用户
  • Syntax
    • USER UID|UserName
    • 需要注意的是,UID可以为任意数字,但实践中其必须为/etc/passwd中某用户的有效UID,否则,docker run命令将运行失败。

HEALTHCHECK

  • 这是一个健康检查命令,用来检查容器启动运行时是否正常,若正常则返回healthy,否则返回unhealthy,例如有时候服务器被卡在无限循环中,并且无法处理新连接的情况,即使服务器进程仍在运行,但实际上问题已经产生了却不会报错,因为从docker看来这个容器还在运行,添加这个心跳检查命令,可以隔一段时间检查容器是否正常运行。

  • Syntax

    通过在容器中运行命令来检查容器运行状况。

    HEALTHCHECK [OPTIONS] CMD command

    禁用从基本映像继承的任何运行状况检查

    HEALTHCHECK NONE

  • 参数有以下4个:

    • 设置在容器启动多长时间后开始检查容器状态,–start-period =DURATION(默认为0s,一般需要修改)
    • 设置每次间隔探测时间 --interval=DURATION(默认为30s)
    • 设置超时时间,超过则不返回信息表示容器异常:–timeout=DURATION(默认为30s)
    • 设置重试次数,–retries(默认为3)
  • 结果有3种:

    • 0:success-容器健康且可以使用
    • 1:unhealthy-容器不正常
    • 2:reserved-带错误码退出

SHELL

  • 在docker构建过程中,会默认使用/bin/sh作为shell环境,Windows下构建默认使用cmd作为shell环境,但有时候我们需要其他shell环境来执行RUN的内容,这时需要用SHELL命令提醒Docker更换shell环境。

  • Syntax

    默认在Linux下是:["/bin/sh","-c"],在Windows下是[“cmd”,"/S","/C"]

    例如在Windows下降powershell更换为默认shell环境:

    SHELL [“powershell”, “-command”]

STOPSIGNAL

  • STOPSIGNAL允许用户定制化允许docker stop时的信号。

  • Syntax

    STOPSIGNAL signal

    例如:STOPSIGNAL SIGKILL,这样构建的镜像其启动的容器在停止时会发送SIGKILL信号,这个命令适用于一些不能接受正常退出信号的容器。

ARG

ARG命令定义一个变量,用户可以在构建时使用,效果和docker build --build-arg =一样,可以在构建时设定参数,这个参数只会在构建时存在。

  • Syntax

    ARG name[=default value]

    ARG与ENV类似,不同的是ENV会在镜像构建结束之后仍然存在镜像中,而ARG在镜像构建之后消失,例如在构建过程中,如果希望整个构建过程是无交互的,那么可以设置如下ARG命令(仅限Debian)

    ARG DEBIAN_FRONTEND=noninteractive
    # 或者在构建过程中修改作者信息
    ARG author= "gzr <gezirong@126.com>"
    LABEL maintainer=${author}
    $ docker build --build-arg author="gezr17 <gezr17@lzu.edu.cn>" -t myweb:v0.3-9 .
    

ONBUILD

  • 用于在Dockerfile中定义一个触发器
  • Dockerfile用于build镜像文件,此镜像文件也可作为base image被另一个Dockerfile用作FROM 指令的参数,并以之构建新的镜像文件
  • 在后面的这个Dockerfile中的FROM指令在build过程中被执行时,将会触发创建其base image的Dockerfile文件中的ONBUILD指令的触发器
  • Syntax
    • ONBUILD INSTRUCTION
  • 尽管指令都可注册成为触发器指令,但ONBUILD不能自我嵌套,且不会触发FROM和MAINTAINER指令
  • 使用包含ONBUILD指令的Dockerfile构建的镜像应该使用特殊的标签,例如ruby:2.0-onbuild
  • 在ONBUILD指令中使用ADD或COPY指令应该格外小心,因为新构建过程的上下文在缺少指令的源文件会失败。