Dockerfile 是用于构建 Docker 镜像的脚本文件,由一系列指令构成。通过 docker build命令构建镜像时,Dockerfile 中的指令会由上到下依次执行,每条指令都将会构建出一个镜像。这就是镜像的分层。因此,指令越多,层次就越多,创建的镜像就越多,效率就越低。所以在定义 Dockerfile 时,能在一个指令完成的动作就不要分为两条。
FROM
【语法】FROM
【解析】用于指定基础镜像,且必须是第一条指令;若省略了 tag,则默认为 latest。
MAINTAINER
【语法】MAINTAINER
【解析】MAINTAINER 指令的参数填写的一般是维护者姓名和信箱。不过,该指令官方已不建议使用,而是使用 LABEL 指令代替。
LABEL
【语法】LABEL
【解析】LABEL 指令中可以以键值对的方式包含任意镜像的元数据信息,用于替代MAINTAINER 指令。通过 docker inspect 可查看到 LABEL 与 MAINTAINER 的内容。
ENV
【语法 1】ENV
【解析】用于指定环境变量,这些环境变量,后续可以被 RUN 指令使用,容器运行起来之后,也可以在容器中获取这些环境变量。
【语法 2】ENV
【解析】可以设置多个变量,每个变量为一对
WORKDIR
【语法】WORKDIR path
【解析】容器打开后默认进入的目录,一般在后续的 RUN、CMD、ENTRYPOINT、ADD 等指令中会引用该目录。可以设置多个 WORKDIR 指令。后续 WORKDIR 指令若用的是相对路径,则会基于之前 WORKDIR 指令指定的路径。在使用 docker run 运行容器时,可以通过-w 参数覆盖构建时所设置的工作目录。
RUN
【语法 1】RUN
【解析】这里的
【语法 2】RUN [ " EXECUTABLE " , " PARAM1 " , " PARAM2 " , …]
【解析】在 docker build 执行过程中,会调用第一个参数"EXECUTABLE"指定的应用程序运行,并使用后面第二、三等参数作为应用程序的运行参数。
CMD
【语法 1】CMD [ " EXECUTABLE " , " PARAM1 " , " PARAM2 " , …]
【解析】在容器启动后,即在执行完 docker run 后会立即调用执行"EXECUTABLE"指定的可执行文件,并使用后面第二、三等参数作为应用程序的运行参数。
【语法 2】CMD command param1 param2, …
【解析】这里的 command 就是 shell 命令。在容器启动后会立即运行指定的 shell 命令。
【语法 3】CMD [" PARAM1 " , " PARAM2 " , …]
【解析】当CMD和ENTRYPOINT指令一起使用时,CMD可以提供参数给ENTRYPOINT。
ENTRYPOINT
【语法 1】ENTRYPOINT [ " EXECUTABLE " , " PARAM1 " , " PARAM2 " , …]
【解析】在容器启动过程中,即在执行 docker run 时,会调用执行"EXECUTABLE"指定的应用程序,并使用后面第二、三等参数作为应用程序的运行参数。
【语法 2】ENTRYPOINT command param1 param2, …
【解析】这里的 command 就是 shell 命令。在容器启动过程中,即在执行 docker run 时,会运行指定的 shell 命令。
EXPOSE
【语法】EXPOSE
【解析】指定容器准备对外暴露的端口号,但该端口号并不会真正的对外暴露。若要真正暴露,则需要在执行 docker run 命令时使用-p(小 p)来指定说要真正暴露出的端口号。
ARG
【语法】ARG < varname >[=
【解析】定义一个变量,该变量将会使用于镜像构建运行时。若要定义多个变量,则需要定义多个 ARG 指令。
ADD
【语法 1】ADD
【语法 2】ADD ["
【解析】该指令将复制当前宿主机中指定文件 src 到容器中的指定目录 dest 中。src 可以是宿主机中的绝对路径,也可以时相对路径。但相对路径是相对于 docker build 命令所指定的路径的。src 指定的文件可以是一个压缩文件,压缩文件复制到容器后会自动解压为目录;src 也可以是一个 URL,此时的 ADD 指令相当于 wget 命令;src 最好不要是目录,其会将该目录中所有内容复制到容器的指定目录中。dest 是一个绝对路径,其最后面的路径必须要加上斜杠,否则系统会将最后的目录名称当做是文件名的。
COPY
【说明】功能与 ADD 指令相同,只不过 src 不能是 URL。若 src 为压缩文件,复制到容器后不会自动解压。
ONBUILD
【语法】ONBUILD [INSTRUCTION]
【解析】该指令用于指定当前镜像的子镜像进行构建时要执行的指令。
VOLUME
【语法】VOLUME [" dir1 " , " dir2 ", …]
【解析】在容器创建可以挂载的数据卷。
在构建自己的镜像之前,首先要了解一个特殊的镜像 scratch。
scratch 镜像是一个空镜像,是所有镜像的 Base Image(相当于面向对象编程中的 Object类)。scratch 镜像只能在 Dockerfile 中被继承,不能通过 pull 命令拉取,不能 run,也没有 tag。并且它也不会生成镜像中的文件系统层。在 Docker 中,scratch 是一个保留字,用户不能作为自己的镜像名称使用。
在宿主机任意目录创建一个名称为 hello.c 的文件。这里在/root 下 mkdir 一个目录 hello_world,然后将 hello.c 文件创建在这里。文件内容如下:
#include
int main()
{
printf("hello docker world!\n");
return 0;
}
使用 gcc 编译 hello.c 文件。
在 hello_world 目录中新建一个 Dockerfile 文件,切记文件名必须是Dockerfile。内容如下:
# 表示基础镜像是scratch,也就是一个空白的镜像。
FROM scratch
# 将当前目录下的hello文件添加到镜像的根目录(/)下。
ADD hello /
# 设置了容器的默认命令,也就是当运行一个基于这个镜像的容器时,如果不指定运行的命令,那么默认会运行/hello这个命令。
CMD ["/hello"]
-t 用于指定要生成的镜像的
最后的点(.)是一个宿主机的 URL 路径
,构建镜像时会从该路径中查找 Dockerfile 文件
。同时,在 Dockerfile 中 ADD、COPY 指令中有使用的是相对路径,那个相对路径就相对的是这个路径。不过需要注意,即使 ADD、COPY 指令中使用绝对路径来指定源文件,该源文件所在路径也必须要在这个 URL 指定目录或子目录内,否则将无法找到该文件。
# 新建一个Dockerfile文件
vim Dockerfile
在文件中写入如下内容
FROM centos:7
MAINTAINER bing [email protected]
LABEL auth="bing" version="1.0" description="This is a Centos image I wrote myself"
ENV WORKPATH /usr/local
WORKDIR $WORKPATH
RUN yum -y install vim net-tools wget
CMD /bin/bash
# 写完之后构建镜像
docker build -t my-centos:1.0 .
# 运行新镜像
docker run --name my-centos -it my-centos:1.0
运行结果如下:
通过这两个例子,大部分的Dockerfile指令都使用过了。
下面就用go语言写一个简单的web服务应用,然后将该应用构建成镜像并运行。
创建一个go项目,创建一个main.go文件,写入如下内容:
package main
import (
"github.com/gin-gonic/gin"
"log"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/image_demo", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "This is a go web service",
})
})
err := r.Run("0.0.0.0:8080")
if err != nil {
log.Panicln(err)
}
}
使用go build main.go编译go代码,这样就得到了一个main.exe的可执行文件。
首先新建一个web目录,用于存放任意一个地方新建一个image_build的目录,在该目录下编写Dockerfile文件,并把刚刚得到的main文件移动到该目录下,并记得赋予main文件有 x 的权限(chmod +x main)。Dockerfile文件的内容如下:
# 使用官方的 Golang 镜像作为基础镜像
FROM golang:alpine
LABEL auth="bing" version="1.0" description="This is a go web service"
WORKDIR /web
COPY ./main /web
ENTRYPOINT ["/web/main"]
# 准备要暴露端口,也就是应用的默认端口
EXPOSE 8080
访问浏览器的结果如下:
可以看到,虽然Dockerfile中准备要对外映射的端口是8080,但是我实际运行的时候使用了8888端口,没有使用8080端口。也就是说EXPOSE指令指定的端口只是告诉使用者该应用的默认端口是8080,仅此而已。
需要注意的地方
:
以下面的Dockerfile文件为例,我简单讲一下镜像的构建过程和对build cache的理解。
FROM centos:7
LABEL auth="Tom"
COPY hello.log /var/log/
RUN yum -y install vim
CMD /bin/bash
Dockerfile文件写完之后,使用docker build -t test:1.0构建test:1.0镜像。在第一次构建镜像的过程中:
FROM centos:7
LABEL auth="Tom"
COPY hello.log /var/log/
RUN yum -y install vim
CMD /bin/bash
# 就是在最后添加一条指令
EXPOSE 9000
此时再构建新的镜像 test:2.0,会发现没有下载安装 vim 的过程了,并且发现了很多的 Using cache。说明这是使用了 build cache。然后使用docker history test:1.0和docker history test:2.0这两条命令查看它们每层的镜像ID,会发现test1.0和test:2.0的前五个镜像层的ID一模一样,这也说明了test:2.0的镜像在构建过程中复用了test:1.0的镜像层。
Docker Daemnon 通过 Dockerfile 构建镜像时,当发现即将新构建出的镜像(层)与本地已存在的某镜像(层)重复时,默认会复用已存在镜像(层)而不是重新构建新的镜像(层),这种机制称为 docker build cache 机制。该机制不仅加快了镜像的构建过程,同时也大量节省了Docker 宿主机的空间。
Docker 构建缓存并不是存储在内存中,而是存储在 Docker 的本地存储中。这个本地存储通常位于 Docker 守护进程的数据目录下,例如在 Linux 系统上,这个目录通常是 /var/lib/docker。所以,无论是关闭 Docker 引擎,还是重启 Docker 宿主机,只要该镜像(层)存在于本地,那么就会复用。
Dockerfile 文件发生变化
当 Dockerfile 文件中某个指令内容发生变化,那么从发生变化的这个指令层开始的所有镜像层 cache 全部失效。即从该指令行开始的镜像层将构建出新的镜像层,而不再使用 build cache,即使后面的指令并未发生变化。因为镜像关系本质上是一种树状关系,只要其上层节点变了,那么该发生变化节点的所有下层节点也就全部变化了。
ADD 或 COPY 指令内容变化
Dockerfile 文件内容没有变化,但 ADD 或 COPY 指令所复制的文件内容发生了变化,同样会使从该指令镜像层开始的后面所有镜像层的 build cache 失效。
RUN 指令外部依赖变化
与 ADD/COPY 指令相似。Dockerfile 文件内容没有变化,但 RUN 命令的外部依赖发生了变化,例如本例中要安装的 vim 软件源发生了变更(版本变化、下载地址变化等),那么从发生变化的这个指令层开始的所有镜像层 cache 全部失效。
指定不使用 build cache
有些时候为了确保在镜像构建过程中使用到新的数据,在镜像构建 docker build 时,通过添加 --no-cache 选项指定不使用 build cache。