之前已经了解到镜像的定制实际上就是定制每一层所添加的配置、文件。如果可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,这就是 Dockerfile

格式描述#

Dockerfile 整体由两类语句组成

  • Comment  注释信息
  • Instruction arguments  指令参数,一行一个指令

注意

  • Dockerfile 文件名首字母必须大写
  • Dockerfile 指令不区分大小写,但为方便和参数做区分,通常指令使用大写字母
  • Dockerfile 指令按顺序从上至下依次执行
  • Dockerfile 第一个非注释行必须是 FROM 指令,用来指定制作当前镜像依据的是哪个基础镜像
  • Dockerfile 需要调用的文件必须跟 Dockerfile 文件在同一目录下,或者在其子目录下。父目录或者其它路径无效

FROM#

  • 功能为指定基础镜像,并且必须是第一条指令
  • 如果不以任何镜像为基础写法为: FROM scratch ,意味着接下来所写的指令将作为镜像的第一层开始

注意:如果没有指定仓库,docker build 会先从本机查找是否有此基础镜像,如果没有默认去 Docker Hub Registry 上拉取,再找不到就会报错

FROM <image>
FROM <image>:<tag>
FROM <image>:<digest> 
# 三种写法,其中<tag>和<digest> 是可选项,如果没有选择,那么默认值为latest

MAINTAINER(新版本过时)#

  • Dockerfile 作者信息,一般格式是:姓名+邮箱地址
  • 并不限制 MAINTAINER 指令的位置,但建议放在 FROM 指令之后
  • 在较新的 docker 版本中,MAINTAINER 已经被 LABEL 替代
MAINTAINER "merle@example.com"

LABEL#

为镜像指定各种元数据(键值对格式): LABEL 会继承基础镜像中的 LABEL ,如遇 key 相同值将会被覆盖

LABEL <key>=<value> <key>=<value>

COPY#

复制宿主机上的文件到目标镜像中,格式:

COPY [--chown=:] <源路径>... <目标路径>
COPY [--chown=:] ["<源路径1>",... "<目标路径>"]

RUN  指令一样,COPY 也有两种格式,一种类似于命令行,一种类似于函数调用。COPY  指令将从构建上下文目录中 <源路径>  的文件/目录复制到新的一层的镜像内的 <目标路径>  位置。比如:

COPY package.json /usr/src/app/

<源路径>  可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 filepath.Match  规则,比如:

COPY hom* /mydir/
COPY hom?.txt /mydir/

<目标路径>  可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR  指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先创建缺失目录。

注意:使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git 进行管理的时候

在使用该指令的时候还可以加上 --chown=<user>:<group>    选项来改变文件的所属用户及所属组

COPY --chown=55:mygroup files* /mydir/
COPY --chown=bin files* /mydir/
COPY --chown=1 files* /mydir/
COPY --chown=10:11 files* /mydir/

ADD#

  • ADD 指令跟 COPY 类似,不过它支持使用 tar 文件和 URL 路径
  • 当拷贝的源文件是 tar 文件时,会自动展开为一个目录并拷贝进新的镜像中。通过 URL 获取到的 tar 文件不会自动展开
  • 主机可以联网的情况下, docker build 可以将网络上的某文件引用下载并打包到新的镜像中
ADD <src>... <dest>
ADD ["<src>",... "<dest>"]

注意:ADD 指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。在 Docker 官方的 Dockerfile 最佳实践文档 中要求,尽可能的使用 COPY,因为 COPY 的语义很明确,就是复制文件,而 ADD 则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD 的场合,就是所提及的需要自动解压缩的场合。因此在 COPYADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD

在使用该指令的时候也可以加上 --chown=<user>:<group> 选项来改变文件的所属用户及所属组

ADD --chown=55:mygroup files* /mydir/
ADD --chown=bin files* /mydir/
ADD --chown=1 files* /mydir/
ADD --chown=10:11 files* /mydir/

WORKDIR#

  • docker run -w
  • 指定工作目录,可以指定多个,每个 WORKDIR 只影响它下面的指令,直到遇见下一个 WORKDIR 为止
  • WORKDIR 可以调用由 ENV 指令定义的变量
  • WORKDIR 相对路径或者绝对路径:相对路径是相对于上一个 WORKDIR 指令的路径,如果上面没有 WORKDIR 指令,那就是当前 Dockerfile 文件的目录

VOLUME#

  • docker run -v
  • 用于在镜像中创建一个挂载点目录。之前提到 Volume 有两种类型:挂载主机目录和数据卷。在Dockerfile中只支持docker数据卷,也就是说只能指定容器内的路径,不能指定宿主机的路径
VOLUME <mountpoint>
VOLUME ["<mountpoint>"]

EXPOSE#

  • docker run -expose 指定容器中待暴露的端口。比如容器提供的是一个https服务且需要对外提供访问,那就需要指定待暴露443端口,然后在使用此镜像启动容器时搭配 -P  参数才能将待暴露的状态转换为真正暴露的状态,转换的同时443也会转换成一个随机端口,跟 -p:443一个意思
  • EXPOSE 指令可以一次指定多个端口,例如: EXPOSE 11111/udp 11112/tcp
EXPOSE <port>[/<protocol>] [<port>[/<protocol>] ...]
<protocol>用于指定协议类型,如果不指定,默认TCP协议

ENV#

  • docker run -e
  • 为镜像定义所需的环境变量,并可被 ENV 指令后面的其它指令所调用。调用格式为 $variable_name 或者 ${variable_name}
  • 使用 docker run 启动容器的时候加上 -e  的参数为 variable_name 赋值,可以覆盖 DockerfileENV 指令指定的环境变量值。但不会影响到 Dockerfile 中已经引用过此变量的文件
ENV <key> <value>

ENV <key>=<value> ...

# 第一种格式一次只能定义一个变量,<key>之后所有内容都会被视为<value>的组成部分
# 第二种格式一次可以定义多个变量,每个变量为一个"="的键值对,如果<value>中包含空格,可以用反斜线 \ 进行转义,也可以为<value>加引号,另外参数过长时可用反斜线做续行。
# 定义多个变量时,建议使用第二种方式,因为Dockerfile中每一行都是一个镜像层,构建起来比较吃资源

RUN#

  • 用于指定 docker build 过程中运行的程序,可以是任何命令
  • RUN 指令后所执行的命令必须在 FROM 指令后的基础镜像中存在才行    
RUN <command>

RUN ["executable", "param1", "param2"]

第一种后边直接跟 shell 命令

  • linux 上默认 /bin/sh -c
  • windows 上默认 cmd /S /C

第二种类似于函数调用,可将 executable 理解成为可执行文件,后面就是两个参数。

  • RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME
  • RUN ["/bin/bash", "-c", "echo hello"]

注意:多行命令不要写多个RUN,原因是Dockerfile中每一个指令都会建立一层,多少个RUN就构建了多少层镜像,会造成镜像的臃肿、多层,不仅仅增加了构件部署的时间,还容易出错。RUN书写时的换行符是 \

CMD#

  • 指定启动容器的默认要运行的程序,也就是 PID 为1的进程命令,且其运行结束后容器也会终止。如果不指定,默认是 bash
  • CMD 指令指定的默认程序会被 docker run 命令行指定的参数所覆盖
  • Dockerfile 中可以存在多个 CMD 指令,但仅最后一个生效。因为一个 docker 容器只能运行一个 PID 为1的进程。
  • CMD 类似于 RUN 指令,也可以运行任意命令或程序,但是两者的运行时间点不同( RUN 指令运行在 docker build 的过程中,而 CMD 指令运行在基于新镜像启动容器也就是 docker run 时)
CMD command param1 param2
CMD ["executable","param1","param2"]
CMD ["param1","param2"]

前两种语法格式同 RUN 指令。第一种用法对于CMD指令基本没有意义,因为它运行的程序PID不为1。 第三种则需要结合 ENTRYPOINT 指令使用,CMD指令后面的命令作为 ENTRYPOINT 指令的默认参数。如果 docker run 命令行结尾有参数指定,那 CMD 后面的参数不生效。

ENTRYPOINT#

  • 类似 CMD 指令的功能,用于为容器指定默认运行程序
  • Dockerfile 中可以存在多个 ENTRYPOINT 指令,但仅最后一个生效
  • 与CMD区别在于:由 ENTRYPOINT 启动的程序不会被 docker run 命令行指定的参数所覆盖,而且这些命令行参数会被当做参数传递给 ENTRYPOINT 指令指定的程序
  • docker run--entrypoint 选项的参数可覆盖 ENTRYPOINT 指定的默认程序
ENTRYPOINT command param1 param2
ENTRYPOINT ["executable", "param1", "param2"]

USER#

  • 用于指定 docker build 过程中任何 RUNCMD 等指令的用户名或者 UID
  • 默认情况下容器的运行用户为 root
USER <user>[:<group>]

USER <UID>[:<GID>]

实践中UID需要是 /etc/passwd 中某用户的有效UID,否则docker run命令将运行失败

HEALTHCHECK#

顾名思义,健康检查。此指令的就是告诉 docker 检查容器是否正常工作

HEALTHCHECK [OPTIONS] CMD command
HEALTHCHECK NONE

HEALTHCHECK 指令定义一个 CMD ,在CMD后面编写一条命令去判断服务运行是否正常。检查肯定不是一次性的,所以 OPTIONS 就是指定检查的频率等

  • --interval=DURATION (默认值:30s):每隔多久检查一次,默认30s
  • --timeout=DURATION (默认值:30s):超时时长,默认30s
  • --start-period=DURATION (默认值:0s):启动健康检查的等待时间。因为容器启动成功时,进程不一定立马就启动成功,过早开始检查就会返回不健康
  • --retries=N (默认值:3):如果检查一次失败就返回不健康未免太武断,所以默认三次机会

CMD 健康检测命令发出时,返回值有三种情况:

  • 0:成功
  • 1:不健康
  • 2:保留,无实际意义。

HEALTHCHECK NONE 就是不做健康检查

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

SHELL#

用来指定运行程序默认要使用的 shell 类型,因为 windows 环境默认是 powershell 。此指令一般不会使用。

SHELL ["executable", "parameters"]

STOPSIGNAL#

指定发送使容器退出的系统调用信号。 docker stop 之所以能停止容器,就是发送了15的信号给容器内PID为1的进程。此指令一般不会使用。

STOPSIGNAL signal

ARG#

ARG 命令同 EVN 类似,也是指定一个变量,但不同的是, ENV 指令配合 -e 参数可以在 docker run 过程中传参,而使用 ARG 指令配合 --build-arg 参数可以在 docker build 过程中传参,方便为不同场景构建不同镜像。

ARG <name>[=<default value>]

ONBUILD#

  • 用于在 Dockerfile 中定义一个触发器
  • ONBUILD 后面指定的指令在 docker build 时是不会执行,构建完的镜像在被另一个 Dockerfile 文件中 FROM 指令所引用的时才会触发执行
ONBUILD [INSTRUCTION]
  • 几乎任何指令都可以成为触发器指令,但 ONBUILD 不能自我嵌套,且不会触发 FROMMAINTAINER 指令,多数情况是使用 RUN 或者 ADD
  • 在使用 COPY 指令时,应该注意后续引用该镜像的 Dockerfile 的同级目录下是否有被拷贝的文件