之前总结过 Docker 的使用,讲解了 Docker 的安装、相关概念以及基本使用。里面介绍的都是如何使用别人制作好的镜像,显然是不能满足我们的需要。接下来就来讲讲私人订制自己的镜像。

Dockerfile 简介

简单的说 Dockerfile 是用来告诉 docker 怎么构建镜像用的。

除了使用 Dockerfile 来构建镜像外,还可以使用 docker commit 来构建镜像。使用 docker commit 来构建镜像会把所有执行的操作都会被添加进镜像中。比如:文件改动,目录跳转,编译等一些无关的内容。这些东西存在镜像里就会导致镜像非常臃肿。

使用 Dockerfile 来定制镜像,可以很方便的知道镜像是如何构建的, docker commit 对于其他人来说是一个黑盒操作。另外的好处就是需要改动某条指令的时候,只需要改动 Dockerfile 然后重新构建。 这点 docker commit 做起来就很麻烦了。把 Dockerfile 提供给别人也容易定制成自己想要的。

Dockerfile 指令

了解了 Dockerfile 之后,就来看看 Dockerfile 的指令

FROM

FROM 指令的格式为: FROM <image>:<tag> tag 是可选项,可以不要

1
FROM ubuntu

FROM 用来指定基础镜像,我们使用 Dockerfile 定制镜像,其实就是在一个基础镜像上进行操作。在 Dockerfile 中 FROM 是必需的指令,且一定是第一个指令。

常见的基础镜像有 ubuntu centos alpine nginx node golang 等等。

一个特殊的基础镜像是 scratch ,这是一个空白的镜像,实际并不存在,这意味着你不以任何镜像为基础。这种镜像里的程序通常不需要操作系统提供运行支持,一切库都在可执行程序里。这种镜像体积也会很小。大多使用 Go 开发的程序会使用 scratch 作为基础镜像。

COPY

COPY 指令的格式为: COPY <src> <dest>

1
COPY index.html /root/www/

COPY 指令的作用是把主机的文件添加到镜像里,有一点需要注意的是 COPY 会对源文件的原数据进行保留。比如:读、写、执行权限。

ADD

ADD 指令的格式为: ADD <src> <dest>

1
ADD release.tar.gz /

ADDCOPY 类似,也是把主机的文件添加到镜像里。不同的是 ADD 可以是个 URL ,如果源路径是个压缩文件并且不是 URL ,会自动解压到目标路径。官方更建议使用 COPY ,因为 COPY 的语义比较明确就是拷贝文件。

RUN

RUN 指令的格式为: RUN <command> 或者 RUN ["executable","param1","param2"]

1
2
RUN apt-get update \
    && apt-get install -y wget curl

RUN 指令是用来执行命令行命令的。通常我们会用 RUN 来执行 shell 命令来为镜像准备环境。

如果有多条指令,需要使用 && 连成一条指令。因为每条 RUN 指令都会建立一层存储,而镜像是有层数限制的。

CMD

CMD 指令的格式为: CMD <command> 或者 CMD ["executable","param1","param2"] 或者 CMD ["param1","param2"]

1
2
CMD echo $PATH # CMD <command>格式
CMD [ "sh", "-c", "echo $PATH" ] # CMD["executable","param1","param2"] 格式

CMD 指令是容器启动后执行的指令,如果执行 docker run 后面跟了启动命令,这个就会被覆盖掉。需要注意的是 CMD 在 Dockerfile 中只能存在一条,如果有多条,只有最后一条才会生效。 CMD 的命令格式(第一种),其实会被包装成 CMD ["sh", "-c", "echo $PATH"]

CMD 的第三种格式,是用来为 ENTRYPOINT 提供默认参数使用的。后面讲到了我们再说。

ENTRYPOINT

ENTRYPOINT 指令的格式为: ENTRYPOINT command 或者 ENTRYPOINT ["executable", "param1", "param2"]

1
2
ENTRYPOINT exec top -b # ENTRYPOINT command 格式
ENTRYPOINT ["top", "-b"] # ENTRYPOINT ["executable", "param1", "param2"] 格式

ENTRYPOINTCMD 类似,也是在容器启动后执行的命令。不同点在于执行 docker run 不会被覆盖,如果需要覆盖可以使用 --entrypoint 来覆盖,这增加了覆盖启动命令的难度。

前面说到 CMD ["param1","param2"] 格式是给 ENTRYPOINT 提供默认参数用的。当 Dockerfile 指定了 ENTRYPOINTCMD ,那么 CMD 就会作为参数传递给 ENTRYPOINT

1
2
CMD ["-l", "-s"]
ENTRYPOINT ["/usr/bin/ls"]

这两条指令其实就相当于 ls -l -s

这样做的好处是,在运行容器的时候可以把参数传递给 ENTRYPOINT 。举个例子

1
2
...
ENTRYPOINT ["/usr/bin/ls"]

在运行容器的时候就可以携带 -l 参数了 docker run myls -l 。这样 -l 就会成为 /usr/bin/ls 的参数了,而不用重新构建一个新的镜像。

ENV

ENV 指令的格式为: ENV <key> <value> 或者 ENV <key1>=<value1> <key2>=<value2>=

1
2
ENV VERSION 1.0 # ENV <key> <value> 格式
ENV VERSION=1.0 DEBUG=on #ENV <key1>=<value1> <key2>=<value2> 格式

ENV 指令是设置环境变量,很简单的指令,和通常说的环境变量是一个意思。

VOLUME

VOLUME 指令的格式为: VOLUME <path> 或者 VOLUME ["path1", "path2"]

1
2
VOLUME /data # VOLUME <path> 格式
VOLUME ["/data1", "/data2"] #VOLUME ["path1", "path2"] 格式

VOLUME 指令是将主机的目录挂载到容器里。我们希望在容器删掉后,里面的数据还保存着,可以通过 VOLUME 把主机里的目录挂载到容器中。挂载的命令是 docker run -v mydata:/data xxx

EXPOSE

EXPOSE 指令的格式为: <port> [<port>...]

1
EXPOSE 8080

EXPOSE 只是声明容器使用的端口,并不会与宿主机进行端口映射。可以通过 -p 指令在启动容器的时候进行映射 docker run -p 8080:8080 xxx

WORKDIR

WORKDIR 指令的格式为: WORKDIR /path/to/workdir

1
WORKDIR /www

WORKDIR 用来设置工作目录,相当于切换目录,也就相当于 cd 。在 Dockerfile 中不能使用 cd ,因为 cd 不在同一个环境中, Linux 中之所以能用是因为 cd 在同一个进程里。

构建镜像

说完了 Dockerfile 的指令,来看看怎么通过 Dockerfile 构建镜像。

在 Dockerfile 目录执行 docker build -t container1:v1 . 最后面有个点 . ,不要漏掉了。这个 . 表示的是 镜像构建上下文 。镜像构建上下文表示的是一个路径。在构建的时候会把 镜像构建上下文 路径里的内容打包传递给 docker 引擎,在执行 COPY 这类指令的时候使用相对路径,其实相对的就是 镜像构建上下文

除了把 Dockerfile 写在本地之外,还可以通过 Git Repo 构建、用 tar 包构建、从标准输入中读取 Dockerfile 构建、从标准输入中读取上下文压缩包进行构建。这些用的比较少,所以不再赘述。

总结

Docker 让我们开发部署搭建环境变得轻而易举,我们可以分分钟部署出一个一模一样的环境,这样一来把时间花在重要的开发上,而不是每次换台电脑或者同事使用都需要装半天的软件,配置一大堆参数。所有团队的成员都是一个环境,而不会发生,在我电脑上好好的,怎么到你这就不行了的无奈。

掌握了 Docker 我们还需要学会怎么自己定制我们需要的容器,这样才能很好的驾驭 Docker ,定制属于自己的镜像。

参考

Best practices for writing Dockerfiles | Docker Documentation