Dockerfile是一个文本格式的配置文件,用户可以使用Dockerfile来快速创建自定义的镜像。

8.1 基本结构

Dockerfile由一行行命令语句组成,并且支持以 # 开头的注释行。
一般而言,Dockerfile从上到下分为四部分:

  • 基础镜像信息
  • 维护者信息(Maintainer)
  • 镜像操作指令(典型的是RUN指令。RUN指令将对镜像执行紧随其后的命令。没运行完一条RUN指令,镜像就添加新的一层,并提交)
  • 容器启动时执行的指令(不一定有)

例:

  1. FROM debian:jessie
  2. MAINTAINER NGINX Docker Maintainers "docker-maint@nginx.com"
  3. ENV NGINX_VERSION 1.10.1-1~jessie
  4. RUN apt-key adv .......
  5. RUN ln -sf .......
  6. EXPOSE 80 443
  7. CMD ["nginx","-g","daemon off;"]

8.2 指令说明

官方文档:https://docs.docker.com/engine/reference/builder/

指令 作用
FROM 指定所创建镜像的基础镜像
MAINTAINER 指定维护者信息
RUN 指定构建镜像时需执行的命令
CMD 指定启动容器时默认执行的命令
LABEL 指定生成镜像的元数据标签信息
EXPOSE 声明镜像内服务所监听的端口
ENV 指定环境变量
ADD 复制指定的路径下的内容到镜像下的路径下,可以为URL;如果为tar文件,会自动解压到路径下。
COPY 复制本地主机的路径下的内容到镜像下的路径下。通常我们都是拷贝本机文件,所以推荐使用COPY,因为更简单、更明确。
ENTRYPOINT 指定容器的默认入口
VOLUME 创建数据卷挂载点
USER 指定运行容器时的用户名或UID
WORKDIR 配置工作目录
ARG 指定镜像内使用的参数(例如版本号信息等)
ONBUILD 配置当所创建的镜像作为其他镜像的基础镜像时,所执行的创建操作指令。
STOPSIGNAL 指定容器退出的信号值
HEALTHCHECK 指定如何进行健康检查
SHELL 指定使用shell时的默认shell类型

1. FROM

指定所创建镜像的基础镜像,如果本地本存在,则默认回去Docker Hub下载指定镜像。一个合法的Dockerfile一定是以FROM指令开头。如果要在同一个Dockerfile中创建多个镜像,可以使用多个FROM指令,每个镜像一个。
格式:

  1. FROM <image> [AS <name>]
  2. FROM <image>[:<tag>] [AS <name>]
  3. FROM <image>[@<digest>] [AS <name>]

2. MAINTAINER

指定维护者信息。该信息会写入生成镜像的Author属性域中。
格式:

MAINTAINER <name>

根据官方文档,该指令已过时。应该使用 LABEL 指令代替,因为它更灵活。

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

3. RUN

指定构建镜像时运行的指令。每条RUN指令都将在当前镜像基础上创建一个新层,执行指定命令,然后提交,从而产生一个新镜像。这个新镜像也将成为下一个RUN命令的的当前镜像。
有两种格式:

RUN <command> 
# shell形式。将在默认的shell终端中运行命令。在linux下是/bin/sh -c。
# 命令太长时可用\换行

RUN ["executable", "param1", "param2"] 
# exec形式。使用exec执行,不会启动shell。
# 注意这里数组将被解析为JSON数组,因此必须用双引号。

4. CMD

指定从生成的镜像启动容器时,默认执行的命令
有三种形式:

CMD ["executable","param1","param2"]
# exec形式。使用exec执行,不会启动shell。
# 是官方推荐的形式。

CMD command param1 param2
# shell形式。将在默认的shell终端中运行命令。在linux下是/bin/sh -c。

CMD ["param1","param2"]
# 这种形式不是指定默认命令,而是为ENTRYPOINT指定的命令提供参数。
# 所有数组中的值豆浆作为ENTRYPOINT指定的命令的参数。

每个Dockerfile只能有一条CMD命令。如果指定了多条,则只有最后一条会被执行。
如果用户在启动容器时手动指定了运行的命令(作为run或create的参数),则会覆盖掉CMD。

5. LABLE

指定生成的镜像的元数据信息。
格式:

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

可以指定多个LABLE。
父镜像的LABEL会被子镜像继承。如果子镜像指定了同名LABEL,则会覆盖。
可通过 docker inspect 指令来查看label。

6. EXPOSE

声明容器运行时将监听的端口。
格式:

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

# 例:
EXPOSE 80
EXPOSE 80/tcp
EXPOSE 80/udp

可指定监听TCP或UDP端口。如果不指定协议,则默认监听TCP端口。
使用EXPOSE指令并不会真的发布该端口(即监听宿主主机的相应端口)。为了真的发布该端口,需要在docke run时使用-p或-P参数指定发布端口。

7. ENV

指定环境变量。指定的环境变量在后续的构建过程中生效,也会存在于启动后的容器中。
格式:

ENV <key> <value>
ENV <key>=<value> ...

指定的环境变量可在启动容器时覆盖:

docker run --env <key>=<value> image

8. ADD

该指令将指定的文件、目录或URL指定的远程文件拷贝到镜像中的指定目录下。
格式:

ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"] # 当路径中有空格时,使用该形式

其中可以是以下三种形式:

  • Dockerfile所在目录的一个相对路径(文件或目录)。注意只能访问该目录及其子目录。
  • 一个URL
  • 一个tar文件

可以是镜像内的绝对路径,或者相对于工作目录(WORKDIR指令指定)的相对路径。
路径支持正则表达式。

9. COPY

该指令将指定的文件、目录拷贝到镜像中的指定目录下。
格式:

COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

可以是Dockerfile所在目录的一个相对路径(文件或目录)。注意只能访问该目录及其子目录。。
可以是镜像内的绝对路径,或者相对于工作目录(WORKDIR指令指定)的相对路径。
路径支持正则表达式。

COPY和ADD对比

COPY和ADD的功能基本一致。ADD可视为增强版的COPY。
COPY指令只能复制当前构建上下文中的文件(即当前Dockerfile所在目录下的文件)。
ADD指令除了可复制当前构建上下文中的文件外,还支持其他两种数据源:通过URL访问远程文件和指定一个需要自动解压的tar文件。
大部分情况下,我们都只是复制本机文件到镜像,因此应该使用COPY。而对于拷贝远程文件、解压文件等情形,虽然ADD可以完成,但不如直接使用RUN CURL等命令直接和清晰。因此绝大部分情况下,都应该使用COPY,不要使用ADD。
参考:

10. ENTRYPOINT

指定镜像的默认入口命令。
格式:

ENTRYPOINT ["executable", "param1", "param2"]
# exec形式。使用exec执行,不会启动shell。
# 是官方推荐的形式。

ENTRYPOINT command param1 param2 
# shell形式。将在默认的shell终端中运行命令。在linux下是/bin/sh -c。

当指定了ENTRYPOINT时,CMD指令只能用于提供参数。
每个Dockerfile中只能有一个ENTRYPOINT,当指定多个时,只有最后一个生效。
在启动容器时,必须使用—entrypoint参数才能覆盖掉Dockerfile中的配置。镜像名后跟命令的形式不行。

CMD和ENTRYPOINT对比

两者的最终目的都是为了给基于该镜像启动的容器提供默认执行项。 如果直接启动一个不带CMD或ENTRYPOINT的镜像,将会报错:

$ docker run alpine
FATA[0000] Error response from daemon: No command specified

因此,通常情况下CMD和ENTRYPOINT至少应该指定一个。
另一方面,CMD相比于ENTRYPOINT来说,更加容器被覆盖(docker run最后的参数即会覆盖CMD,不需要专门指定参数)。如果你的镜像用途并不明确,没有预期用户会怎么使用,那么推荐使用CMD。这样你的镜像使用上更加灵活。
如果你的镜像明确是一个“可执行应用”,期望用户就是把它当成一个应用来用,那么就应该使用ENTRYPOINT。并且此时推荐ENTRYPOINT和CMD同时使用。ENTRYPOINT负责指定执行的命令,而CMD负责该命令的提供默认参数。当用户启动容器、不指定任何参数时,将使用CMD指定的参数。而当用户指定了参数时,则使用用户提供的参数。
参考资料:

  • https://www.ctl.io/developers/blog/post/dockerfile-entrypoint-vs-cmd/

    11. VOLUME

    创建一个挂载点。
    格式:
    VOLUME ["<路径1>", "<路径2>"...]
    VOLUME <路径>
    
    容器在运行时,由于采用的是联合文件系统,在其中进行写操作很慢,并且容器关闭后数据就丢失了。为了提高性能,需要绕过联合文件系统,直接访问宿主主机的文件系统。这便是VOLUME的作用。VOLUME既可以在容器启动时指定,也可以在Dockerfile中指定,从而镜像构建时便确定。对于数据库一类的应用,VOLUME很重要,需要将产生的数据放到一个VOLUME中。为了避免用户忘记声明VOLUME,于是需要在Dockerfile中指定。

    12. USER

    该指令指定一个用户名(或UID),也可指定一个用户组(或GID)。在镜像构建阶段,Dockerfile中位于USER指令之后的RUN、CMD、ENTRYPOINT指令的执行将使用USER指定的用户名和用户组。
    格式:
    USER <user>[:<group>] or
    USER <UID>[:<GID>]
    

    13. WORKDIR

    该指令为后续的RUN, CMD, ENTRYPOINT, COPYADD指令指定工作目录。
    格式:
    WORKDIR /path/to/workdir
    
    如果指定的工作目录不存在,将自动创建一个新目录。
    一个Dockerfile中可以使用多个WORKDIR指令,根据需要不断切换工作目录。如果使用的路径是相对路径,则会相对于前一个工作目录进行计算。
    WORKDIR指令可以解析它前面通过ENV设置的环境变量:
    ENV DIRPATH /path
    WORKDIR $DIRPATH/$DIRNAME
    RUN pwd # 这里pwd输出为/path/$DIRNAME
    

    14. ARG

    该指令用于定义一些构建参数,用户可以在执行 docker build 命令时传入这些参数值。典型的,比如版本号。

    注意,ARG用于定于构建参数,准确点说应该叫BUILD_ARG。ARG只会存在于构建时,不会存在于容器启动时。另外,CMD和ENTRYPOINT是在启动时进行解析的。因此,ARG不能用于CMD和ENTRYPOINT命令中。因为那个时候ARG已经不存在了。一个解决方案是,把ARG赋值给环境变量ENV,ENV是会保留到运行时的。

格式:

ARG <name>[=<default value>]

用户指定参数值:

docker build --build-arg <varname>=<value>

如果指定的参数不存在,则会参数如下警告信息:

[Warning] One or more build-args [foo] were not consumed.

可以多次使用ARG指令,指定多个参数。
注意:不要通过ARG指令传递敏感信息,因为传递的构建参数值是可以通过 docker history 命令看到的。

15. ONBUILD

该指令指定一些其他指令,这些指令将在当前镜像作为别的镜像的基础镜像时,在其构建阶段执行。
格式:

ONBUILD [INSTRUCTION]

用ONBUILD指定的指令,在本镜像的构建过程中不会执行,而是存储在镜像中。当创建另一个Dockerfile,并且用FROM指令指定本镜像作为基础镜像时,在子镜像的构建阶段,读取到FROM指令时,Docker会寻找基础镜像的ONBUILD指令,并按顺序执行其指定的指令。注意ONBUILD指令只会影响直接继承的子镜像的构建过程,对于孙子镜像没有影响。
该指令常用于制作“执行环境”类型的镜像,且镜像名通常会添加“onbuild”后缀。

16. STOPSIGNAL

指定所创建的镜像启动的容器所接受的退出信号值。
格式:

STOPSIGNAL signal # signal可以为无符号整数,如9,或信号名,如SIGKILL

17. HEALTHCHECK

该指令告诉Docker如何检查启动的容器是否处于正常工作状态。
有两种格式:

HEALTHCHECK [OPTIONS] CMD command (通过在容器内执行指定的命令来检查容器是否健康)
HEALTHCHECK NONE (禁用从基础镜像继承的健康检查)

选项:

  • --interval=DURATION (default: 30s)
  • --timeout=DURATION (default: 30s)
  • --start-period=DURATION (default: 0s)
  • --retries=N (default: 3)

一个Dockerfile中只能指定一个HEALTHCHECK指令。指定多个时只有最后一个生效。

18. SHELL

为后续的RUN、CMD、ENTRYPOINT的shell形式命令指定默认shell。
格式:

SHELL ["executable", "parameters"]

8.3 创建镜像

当创建了一个Dockerfile之后,即可通过docker build指令来创建镜像。

docker build [OPTIONS] PATH | URL | -

选项:
https://docs.docker.com/engine/reference/commandline/build/
常用选项:

  • —file , -f,指定dockerfile所在路径。
  • —tag , -t,指定生成镜像的标签信息。

docker build命令依靠上下文dockerfile文件来构建镜像。上下文是PATH或URL参数指定的目录及其子目录。Dockerfile默认也在上下文中找,但是可以通过-f参数指定上下文以外的其他文件。该命令将上下文和dockerfile发送给Docker服务端,由服务端来创建镜像。


8.4 使用.dockerignore文件

在使用docker build命令时,在docker CLI将上下文发送给docker守护进程之前,它会在上下文的根目录中寻找.dockerignore文件。如果该文件存在,CLI将使用该文件中指定的模式对context目录下的文件进行匹配,并将匹配成功的文件从上下文中移除,然后再发送。这样可以避免发送大文件和敏感文件。
.dockerignore文件中每一行代表一个模式。
例:

*/temp*
*/*/temp*
temp?
# 注释

8.5 最佳实践

  • 精简镜像用途。
  • 选用合适的基础镜像。
  • 提供足够清晰的命令注释和维护者信息。
  • 正确使用版本号。
  • 减少容器层数。需要尽量合并指令。
  • 及时删除临时文件和缓存文件。
  • 合理使用缓存,减少内容目录下文件,使用.dockerignore,以提高生产速度。
  • 合理指令顺序。在开启缓存的情况下,内容不变的指令尽量放在前面。
  • 减少外部源干扰。