Dockerfile 是文本文件,由构建镜像的指令组成。Docker 会按照从上到下的顺序执行 Dockerfile 中的指令来构建镜像。
以下是一个打包 Git 工具的 Dockerfile 示例:
FROM ubuntu:latest
LABEL maintainer="dia@allingeek.com"
RUN apt-get update && apt-get install -y git
ENTRYPOINT ["git"]
FROM
指令用于指定源镜像。在 Dockerfile 中,第一条指令必须是FROM
。如果是从空的镜像开始,则可以指定一个名为scratch
的特殊仓库;LABEL
指令用于给镜像添加元数据。比如例子中添加了作者信息;RUN
指令用于直接执行命令。例子中执行了apt-get
来安装 Git ;ENTRYPOINT
指令用来设置镜像的入口点。
可以通过 docker image build
来通过 Dockerfile 构建镜像:
# 最后面的 `.` 代表从当前目录查找 Dockerfile 文件
docker image build --tag ubuntu-git:auto .
--tag
或-t
用于指定构建镜像的完整仓库名称;- Docker 默认会寻找名为
Dockerfile
的文件来构建镜像,也可以通过-f
选项来指定 Dockerfile 的文件名。
构建过程
通过 Dockerfile 构建镜像与手动构建镜像类似,Dockerfile 中的每条指令都会创建一个新的容器,提交相应修改。在执行 docker build
后,Docker 会在控制台逐层打印构建任务信息。可以通过 --quiet
或 -q
来启用安静模式,该模式会抑制构建过程中(在控制台)的日志输出,但最终依然会输出一个镜像 ID 。
同时,Docker 会逐层缓存构建结果,当在重新执行 Dockerfile 构建时,Docker 会尝试使用历史的缓存结果。Docker 也提供了 --no-cache
选项来禁用缓存,但非必要时不应当使用该选项。
为了利用好缓存,在编写 Dockerfile 时应当注意指令间顺序。同时,一些 Dockerfile 指令支持传入多个参数,可以避免相同指令的多次使用:
ENV APPROOT="/app"
ENV APP="mailer.sh"
# 可通过一条指令直接达到目的
ENV APPROOT="/app" APP="mailer.sh"
构建完镜像后,可以通过 docker inspect
来查看镜像的元数据。
语法及常用指令
与大多数脚本一样,在 Dockerfile 可以通过 #
来写注释,也可以通过反斜杠 \
来对单条语句换行。以下是常用的 Dockerfile 指令
FROM
- 指定构建的源镜像LABEL
- 指定镜像的元数据ENV
- 指定环境变量USER
- 设置用户及 group IDWORKDIR
- 改变工作目录EXPOSE
- 设置暴露端口
运行命令相关指令:
RUN
- 执行命令ENTRYPOINT
- 设置入口点(启动程序)CMD
- 设置默认命令
这三条指令支持两种语法:
INSTRUCTION ["executable", "param1", "param2"]
- exec 形式INSTRUCTION command param1 param2
- shell 形式
对于 exec 形式,传入的参数必须是个 JSON 数组格式,即字符串所用的引号必须是双引号。
在 exec 形式下,ENTRYPOINT
通常可以跟 CMD
搭配着用,ENTRYPOINT
用于指定一个固定的命令及参数,而 CMD
指定剩余一些容易发生变化的参数:
FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]
修改文件系统相关指令:
COPY
- 复制文件VOLUME
- 创建卷ADD
- 添加本地或远程文件
在构建下层镜像时注入行为
如果通过 Dockerfile 构建后的镜像会被作为源镜像进一步构建,可以通过 ONBUILD 指令来设置被构建时需要预先执行的指令:
ONBUILD COPY [".", "/var/myapp"]
ONBUILD RUN go build /var/myapp
当通过该 Dockerfile 构建镜像时,不会执行 ONBUILD 中的指令,而是会记录在镜像的元数据中。当构建后的镜像被进一步构建时,Docker 会先执行存在元数据中的 ONBUILD 的指令集合。
多阶段构建
在 Dockerfile 里可以使用多条 FROM 指令,来应用多个构建阶段。比如对于一个 Web 服务,可以分为「构建时」和「运行时」两个阶段:
FROM node:alpine AS builder
# ...
RUN yarn install --frozen-lockfile
# ...
RUN yarn build
FROM node-alpine AS runner
COPY --from=builder ...
# ...
CMD ['node', 'server.js']
AS
用于给阶段命名;- 在
COPY
中可以用--from
来从目标构建阶段复制文件,参数值可以是阶段名称或索引。 - 最终生成的镜像仅会包含
runner
阶段里复制的文件,而不会包含builder
阶段中下载的依赖。
初始化进程
在容器中,如果应用程序创建了子进程,那么在应用程序退出时,这些子进程很有可能将变成僵尸进程(即仍在后台运行)。
创建容器时也可以使用 --init
选项来预先启动一个初始化进程,它会负责创建后续所有的子进程:
docker run -it --init alpine:3.6 nc -l -p 3000
Docker 背后会使用 tini
作为初始化进程工具。当容器退出时,初始化程序会结束所有的子进程,即解决了僵尸进程的问题。
健康检查
Docker 提供了健康检查的工具,来检测应用程序的运行状况。有两种方式来使用健康检查:
在 Dockerfile 中使用 HEALTHCHECK
指令:
# 检测 80 端口是否可用
HEALTHCHECK --interval=5s --retries=2 \
CMD nc -vz -w 2 localhost 80 || exit 1
- 默认情况下健康检查会每 30 秒运行一次,例子中指定为了 5 秒;
- 如果健康检查失败(退出状态码非零),Docker 会试图重新执行命令。在该例子中,如果重新尝试次数超过 2 ,则会将容器标记为不健康。
也可以在创建容器时使用 --health-cmd
选项:
docker run --name=healthcheck_example -d \
--health-cmd='nc -vz -w 2 localhost 80 || exit 1' \
nginx:1.13-alpine
健康检查工具还支持以下选项:
timeout
- 超时设置;start_period
- 容器启动时的宽限期。
镜像防御
作为镜像作者,通常很难预料镜像的所有使用场景。为了减少容器被攻击的可能,有几个防御措施可以考虑:
- 尽可能减少镜像内的软件数量;
- 通过 CAIID(内容可寻址镜像标识符)来作为基础镜像;
- 设置合理的默认用户;
- 删除具有 SUID 和 SGID 权限的可执行文件。
具体请见书 P182 。