熟悉 Dockerfile 的指令是编写 Dockerfile 的前提,这里我们先来介绍几个最常见的 Dockerfile 指令,它们基本上囊括了所有 Dockerfile 中 90% 以上的工作。
FROM
通常来说,我们不会从零开始搭建一个镜像,而是会选择一个已经存在的镜像作为我们新镜像的基础,这种方式能够大幅减少我们的时间。
在 Dockerfile 里,我们可以通过 FROM 指令指定一个基础镜像,接下来所有的指令都是基于这个镜像所展开的。在镜像构建的过程中,Docker 也会先获取到这个给出的基础镜像,再从这个镜像上进行构建操作。
FROM 指令支持三种形式,不管是哪种形式,其核心逻辑就是指出能够被 Docker 识别的那个镜像,好让 Docker 从那个镜像之上开始构建工作。
FROM <image> [AS <name>]FROM <image>[:<tag>] [AS <name>]FROM <image>[@<digest>] [AS <name>]
既然选择一个基础镜像是构建新镜像的根本,那么 Dockerfile 中的第一条指令必须是 FROM 指令,因为没有了基础镜像,一切构建过程都无法开展。
当然,一个 Dockerfile 要以 FROM 指令作为开始并不意味着 FROM 只能是 Dockerfile 中的第一条指令。在 Dockerfile 中可以多次出现 FROM 指令,当 FROM 第二次或者之后出现时,表示在此刻构建时,要将当前指出镜像的内容合并到此刻构建镜像的内容里。这对于我们直接合并两个镜像的功能很有帮助。
RUN
镜像的构建虽然是按照指令执行的,但指令只是引导,最终大部分内容还是控制台中对程序发出的命令,而 RUN 指令就是用于向控制台发送命令的指令。
在 RUN 指令之后,我们直接拼接上需要执行的命令,在构建时,Docker 就会执行这些命令,并将它们对文件系统的修改记录下来,形成镜像的变化。
RUN <command>RUN ["executable", "param1", "param2"]
RUN 指令是支持 \ 换行的,如果单行的长度过长,建议对内容进行切割,方便阅读。而事实上,我们会经常看到 \ 分割的命令,例如在上面我们贴出的 Redis 镜像的 Dockerfile 里。
ENTRYPOINT 和 CMD
基于镜像启动的容器,在容器启动时会根据镜像所定义的一条命令来启动容器中进程号为 1 的进程。而这个命令的定义,就是通过 Dockerfile 中的 ENTRYPOINT 和 CMD 实现的。
ENTRYPOINT ["executable", "param1", "param2"]ENTRYPOINT command param1 param2CMD ["executable","param1","param2"]CMD ["param1","param2"]CMD command param1 param2
ENTRYPOINT 指令和 CMD 指令的用法近似,都是给出需要执行的命令,并且它们都可以为空,或者说是不在 Dockerfile 里指出。
当 ENTRYPOINT 与 CMD 同时给出时,CMD 中的内容会作为 ENTRYPOINT 定义命令的参数,最终执行容器启动的还是 ENTRYPOINT 中给出的命令。
关于 ENTRYPOINT 和 CMD 的更详细对比,在后一节里我们会提到。
EXPOSE
在第 9 节:为容器配置网络中,在未做特殊定义的前提下,我们直接连接容器网络,只能访问容器明确暴露的端口。而我们之前介绍的是在容器创建时通过选项来暴露这些端口。
由于我们构建镜像时更了解镜像中应用程序的逻辑,也更加清楚它需要接收和处理来自哪些端口的请求,所以在镜像中定义端口暴露显然是更合理的做法。
通过 EXPOSE 指令就可以为镜像指定要暴露的端口。
EXPOSE <port> [<port>/<protocol>...]
当我们通过 EXPOSE 指令配置了镜像的端口暴露定义,那么基于这个镜像所创建的容器,在被其他容器通过 —link 选项连接时,就能够直接允许来自其他容器对这些端口的访问了。
VOLUME
在一些程序里,我们需要持久化一些数据,比如数据库中存储数据的文件夹就需要单独处理。在之前的小节里,我们提到可以通过数据卷来处理这些问题。
但使用数据卷需要我们在创建容器时通过 -v 选项来定义,而有时候由于镜像的使用者对镜像了解程度不高,会漏掉数据卷的创建,从而引起不必要的麻烦。
还是那句话,制作镜像的人是最清楚镜像中程序工作的各项流程的,所以它来定义数据卷也是最合适的。所以在 Dockerfile 里,提供了 VOLUME 指令来定义基于此镜像的容器所自动建立的数据卷。
VOLUME ["/data"]
在 VOLUME 指令中定义的目录,在基于新镜像创建容器时,会自动建立为数据卷,不需要我们再单独使用 -v 选项来配置了。
COPY 和 ADD
在制作新的镜像的时候,我们可能需要将一些软件配置、程序代码、执行脚本等直接导入到镜像内的文件系统里,使用 COPY 或 ADD 指令能够帮助我们直接从宿主机的文件系统里拷贝内容到镜像里的文件系统中。
COPY [--chown=<user>:<group>] <src>... <dest>ADD [--chown=<user>:<group>] <src>... <dest>COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
COPY 与 ADD 指令的定义方式完全一样,需要注意的仅是当我们的目录中存在空格时,可以使用后两种格式避免空格产生歧义。
对比 COPY 与 ADD,两者的区别主要在于 ADD 能够支持使用网络端的 URL 地址作为 src 源,并且在源文件被识别为压缩包时,自动进行解压,而 COPY 没有这两个能力。
虽然看上去 COPY 能力稍弱,但对于那些不希望源文件被解压或没有网络请求的场景,COPY 指令是个不错的选择。
