Docker 之六 Dockerfile 详解

Dockerfile 介绍

Dockerfile 是用来构建 Docker 镜像的文件,实质是一系列命令和参数构成的脚本文件。当 Dockerfile 文件编写完之后,可以通过 “docker build” 与”docker run” 命令构建并运行新的 Docker 镜像。其中 Dockerfile 定义了进程需要的一切东西,涉及的内容包括执行代码或者文件、环境变量、依赖包、运行时环境、动态链接库、操作系统的发行版本、服务进程和内核进程(当应用进程需要和系统服务、内核进程打交道的时候,需要考虑如何设计 namespace 的权限控制)等等。

Dockerfile、Docker 镜像、Docker 容器三者的关系

Dockerfile 面向开发,Docker 镜像是交付标准,Docker 容器则涉及部署与运维,三者缺一不可,合力充当 Docker 体系的基石。从应用软件的角度来看,Dockerfile、Docker 镜像与 Docker 容器分别代表软件的三个不同阶段,其中 Dockerfile 是软件的原材料,Docker 镜像是软件的交付品,Docker 容器则可以认为是软件的运行状态,示意图如下:

docker-image-container

Dockerfile 编写示例

1
2
3
4
5
6
7
8
9
10
11
12
# Centos7官方的Dockerfile

FROM scratch
ADD centos-7-docker.tar.xz /

LABEL org.label-schema.schema-version="1.0" \
org.label-schema.name="CentOS Base Image" \
org.label-schema.vendor="CentOS" \
org.label-schema.license="GPLv2" \
org.label-schema.build-date="20181205"

CMD ["/bin/bash"]

Dockerfile 基础知识

  1. #表示注释
  2. 指令按照从上到下的顺序执行
  3. 每条指令都会创建一个镜像层,并对镜像进行提交
  4. 每条保留字指令都必须为大写字母格式,且后面至少要跟随一个参数

Docker 执行 Dockerfile 的大致流程

  1. Docker 从基础镜像运行一个容器
  2. 执行一条指令并对容器作出修改
  3. 执行类似”docker commit” 的操作来提交一个新的镜像层
  4. Docker 再基于刚提交的新镜像运行一个新容器
  5. 执行 Dockerfile 中的下一条指令,重复上面的执行流程,直到所有指令都执行完成

Dockerfile 指令 - FROM

1
2
3
4
5
6
7
功能:指定基础镜像,并且必须是第一条指令;如果不以任何第三方镜像为基础,那么写法为:FROM scratch

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

Dockerfile 指令 - MAINTAINER

1
2
3
功能:指定镜像维护者,可以是维护者的姓名、邮箱地址、网页地址等

语法:MAINTAINER <name>

Dockerfile 指令 - RUN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
功能:指定镜像构建时需要运行的命令,一般用于更新系统、安装应用软件等

语法:
RUN <command>
RUN ["executable", "param1", "param2"]
第一种写法后边直接跟shell命令,在linux操作系统上默认是"/bin/sh -c",在windows操作系统上默认是"cmd /S /C"
第二种写法是类似于函数调用,可将executable理解成为可执行文件,后面就是两个参数
RUN指令使用\作为换行符

示例:
RUN /bin/bash -c "source $HOME/.bashrc; echo $HOME"
RUN ["/bin/bash", "-c", "echo hello"]

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

Dockerfile 指令 - ENV

1
2
3
4
5
6
功能:设置环境变量

语法:
ENV <key> <value>
ENV <key>=<value> <key>=<value> ...
两者的区别就是第一种是一次设置一个,第二种是一次设置多个

Dockerfile 指令 - WORKDIR

1
2
3
4
5
6
7
8
9
10
功能:设置工作目录(即容器创建并启动后,通过终端登录进来所处的目录);对RUN、CMD、ENTRYPOINT、COPY、ADD指令生效,如果目录不存在则会自动创建,可以设置多次

语法:
WORKDIR /path/to/workdir

注意:
WORKDIR可以解析环境变量,例如:
ENV DIRPATH /path
WORKDIR $DIRPATH
RUN pwd

Dockerfile 指令 - ADD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
功能:将宿主机目录下的文件拷贝进镜像中,且支持url解析与自动解压tar压缩包

语法:
ADD <src>... <dest>
ADD ["<src>",... "<dest>"]
<src>可以是一个本地文件或者是一个本地压缩文件,还可以是一个url(此时类似于wget命令)
<dest>路径的填写可以是容器内的绝对路径,也可以是相对于工作目录的相对路径

示例:
ADD test relativeDir/
ADD test /relativeDir
ADD http://example.com/foobar /

注意:
任何压缩文件通过url的方式进行拷贝,都不会自动解压;同时尽量不要把<scr>写成一个文件夹,如果<src>是一个文件夹,则复制整个目录的内容,包括文件系统元数据

Dockerfile 指令 - COPY

1
2
3
4
5
6
7
8
功能:将宿主机目录下的文件拷贝进镜像中

语法:
COPY <src>... <dest>
COPY ["<src>",... "<dest>"]

注意:
COPY指令只能拷贝本地文件,不支持url解析,不会自动解压tar压缩包,除此之外其他用法与ADD指令一致

Dockerfile 指令 - VOLUME

1
2
3
4
5
6
7
8
功能:数据卷,用于容器内数据的保存和持久化

语法:
VOLUME /dataVolume
VOLUME ["/dataVolume"]
VOLUME /dataVolume1 /dataVolume2
VOLUME ["/dataVolume1","/dataVolume2"]
参数可以是一个JsonArray ,也可以是单个或多个值,上面四种写法都是正确的

Dockerfile 指令 - CMD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
功能:指定容器启动时需要执行的命令

语法:
CMD ["executable","param1","param2"]
CMD command param1 param2
CMD ["param1","param2"]
第一种使用exec执行,推荐使用
第二种在/bin/sh中执行,提供给需要交互的应用
第三种指定提供给ENTRYPOINT指令的参数

示例:
CMD [ "sh", "-c", "echo $HOME"]
CMD /bin/bash -c "echo $HOME"
CMD [ "echo", "$HOME"]

注意:
每个Dockerfile文件只能有一条CMD命令,如果指定了多条CMD指令,只有最后一条CMD指令会被执行。同时如果用户启动容器时候指定了运行的命令,则会覆盖掉Dockerfile文件中CMD指令指定的命令;例如“docker run -it peter/centos:1.1 ls -al”中的“ls -al”会覆盖Dockerfile文件中的CMD指令。

Dockerfile 指令 - ENTRYPOINT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
功能:指定容器启动时需要执行的命令

语法:
ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2

示例:
ENTRYPOINT [ "sh", "-c", "echo $HOME"]
ENTRYPOINT /bin/bash -c "echo $HOME"

ENTRYPOINT与CMD指令的相同点:
1) 容器启动时才执行指令,运行时机相同
2) 每个Dockerfile文件只能有一条ENTRYPOINT/CMD命令,如果指定了多条ENTRYPOINT/CMD指令,只有最后一条ENTRYPOINT/CMD指令会被执行

ENTRYPOINT与CMD指令的不同点:
1)如果用户启动容器时指定了运行的命令,ENTRYPOINT指令不会被覆盖(会追加后续用户启动容器时指定的命令内容),而CMD指令则会被覆盖
2)如果在Dockerfile中同时写了ENTRYPOINT和CMD指令,并且CMD指令不是一个完整的可执行命令,那么CMD指令的内容将会作为ENTRYPOINT指令的参数
3)如果在Dockerfile中同时写了ENTRYPOINT和CMD指令,并且CMD指令是一个完整的指令,那么它们两个会互相覆盖,谁在最后谁生效

Dockerfile 指令 - ONBUILD

1
2
3
4
5
6
7
8
功能:该指令只对当前镜像的子镜像生效,即父镜像在被子镜像继承后,父镜像Dockerfile中的ONBUILD指令会被触发

语法:
ONBUILD [INSTRUCTION]

示例:
ONBUILD RUN [ "npm", "install" ]
ONBUILD COPY ./package.json /app

Dockerfile 指令 - EXPOSE

1
2
3
4
5
6
7
8
9
功能:暴露容器运行时监听的端口,使容器内的应用可以通过端口和外界通信

语法:
EXPOSE port1
EXPOSE port2
EXPOSE port3

注意:
EXPOSE指令并不会让容器监听的端口映射到宿主机的端口,如果想使容器监听的端口与宿主机的端口有映射关系,必须在容器启动的时候指定"-P"或者"-p"参数

Dockerfile 指令 - USER

1
2
3
4
5
6
7
8
功能:指定启动容器的用户,可以是用户名或UID

语法:
USER admin
USER UID

注意:
如果设置了容器以admin用户去运行,那么RUN、CMD、ENTRYPOINT指令都会以这个用户身份去运行

Dockerfile 指令 - STOPSIGNAL

1
2
3
4
功能:指定当容器退出时给系统发送指定的信号

语法:
STOPSIGNAL signal

Dockerfile 指令 - ARG

1
2
3
4
5
6
7
8
9
10
11
功能:定义变量

语法:
ARG <name>[=<default value>]

示例:
ARG user1
ARG buildno=1

注意:
当使用ARG指令定义了一个变量,在执行"docker build"命令构建镜像的时候,使用"--build-arg <varname>=<value>"来指定变量的值;如果用户在构建镜像时指定了一个没有定义在Dockerfile中的变量,那么Docker将会抛出一个Warning;如果ARG定义的变量拥有默认值,那么当构建镜像没有指定变量值的时候,将会使用这个默认值

Dockerfile 指令 - LABEL

1
2
3
4
5
6
7
8
9
10
11
12
13
功能:为镜像指定标签

语法:
LABEL <key>=<value> <key>=<value> <key>=<value> ...

示例:
LABEL "com.example.vendor"="ACME Incorporated"
LABEL multi.label1="value1" \
multi.label2="value2" \
other="value3"

注意:
Dockerfile中可以有多个LABEL指令,但是建议只使用一个LABEL指令并写成一行,如太长需要换行则可以使用\符号作为换行符。LABEL指令会继承基础镜像中的LABEL指令,如遇到key相同,则覆盖父镜像的值