Dockerfile 是一个文本格式的配置文件,风格却像是一段代码,用户可以通过编写 Dockerfile 来创建自定义镜像。
本文将从 Dockerfile 的基本格式入手,罗列其支持的指令并进行介绍,并说明如何通过这些指令来构建我们想要的镜像。最后还会讲下一些编写 Dockerfile 的经验。
1. 基础结构
Docker 由一行行命令语句和以 # 开头的注释语句构成。主体内容分为四个部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。
下面从一个简单的 Dockerfile 例子开始说明:
# escape=\ (backslash)
# This dockerfile uses the ubuntu:xenial image
# VERSION 2 - EDITION 1
# Author: docker_user
# Command format: Instruction [arguments / command] ..
# Base image to use, this must be set as the first line
FROM ubuntu:xenial
# Maintainer: docker_user <docker_user at email.com> (@docker_user)
LABEL maintainer docker_user<docker_user@email.com>
# Command to update the image
RUN echo "deb http://archive.ubuntu.com/ubuntu/ xenial main universe" >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y nginx
RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf
# Command when creating a new container
CMD /usr/sbin/nginx
1.1 解析器指令
这个例子中,第一行是解析器命令,而不是普通的注释,它指定了这个 Dockerfile 文本的转义字符。
# escape=\ (backslash)
或者 # escape=` (backtick)
,默认转义字符是 \ 。
转义字符既用于转义一行中的字符,也用于转义换行符。这使得一条 Dockerfile 指令可以跨越多行。注意,无论解析器指令 escape 是否包含在 Dockerfile 中,转义都不会在 RUN 指令中执行,除非要转义的字符出现在行末尾。
在Windows环境下,将转义字符设置为反引号而不是 \ 将非常有用,这样,\ 就可以保持其作为目录路径分隔符的原有的含义,与 Windows PowerShell 中保持一致。
考虑下面的这个例子,它在Windows上可能会非常诡异地执行失败。第二行末尾的第二个 \ 将被视为用来转义换行符的转义字符,而不是第一个 \ 的转义目标。类似地,假设第三行末尾的 \ 本来是要作为一条指令进行处理的,但是它却将被视为行延续字符。这个 Dockerfile 的结果是,第二行和第三行被认为是一条指令:
FROM microsoft/nanoserver
COPY testfile.txt c:\\
RUN dir c:\
解决方式就是指定转义字符为 `
。
更多关于解析器指令的资料可以参考 Docker 官方文档
1.2 主体部分
一般而言,大部分镜像会包含一个 FROM
标签指定基础镜像信息,即要构建的新镜像是基于哪个旧镜像构建的。接下来是使用 LABEL 指令说明维护者信息,后面则是镜像操作指令,需要注意的是每运行一条 RUN 指令,镜像添加新的一层,并提交,最后是 CMD 指令,指定容器启动时的操作命令。
2. 指令说明
Dockerfile 中指令的一般格式是 INSTRUCTION arguments,类型有配置指令(配置镜像信息)和操作指令(具体执行操作)。
2.1 配置指令
2.1.1 ARG
定义了创建镜像过程中的变量。
格式为 ARG <name>[=<default value>]。
在执行 docker build 时,可以通过 -build-arg[=] 来为变量赋值。当镜像编译成功后, ARG 变量将不再存在(ENV 变量将一直保留在环境变量中)。
Docker 中内置了一些镜像创建变量,可以直接使用(无需区分大小写):HTTP_PROXY、hTTPS_PROXY、FTP_PROXY、NO_PROXY。
2.1.2 FROM
指定所创建镜像的基础镜像。
格式为 FROM <image> [AS <name>] 或 FROM <image>:<tag> [AS <name>] 或 FROM <image>@<digest> [AS <name>]。
任何 Dockerfile 的第一条指令(不算解析器指令),必须是 FROM 指令。如果在同一个 Dockerfile 中有多个镜像,可以使用多个 FROM 指令。
例子为
ARG VERSION=9.3
FROM debian:${VERSION}
2.1.3 LABEL
LABEL 指令可以为生成的镜像添加元数据标签信息。之后可以使用 docker ps -f lable key=value
快速过滤出特定镜像。
格式为 LABEL <key>=<value> <key>=<value> <key>=<value>....
例如:
LABEL author="wean" date="2019-1-16"
LABEL description="This is a mult-line \
text"
2.1.4 EXPOSE
声明镜像内服务监听的端口。
格式为 EXPOSE <port> [<port>/<protocol>...]
例如:
EXPOSE 22 80 443
需要注意的是,该指令只是起到声明的作用,并不会自动完成端口映射。
如果要映射端口出来,在启动容器时可以使用 -P 或 -p 参数,可以参考 操作 Docker 容器,查看端口映射的具体操作。
2.1.5 ENV
指定环境变量,在编译镜像过程中会被后续 RUN 指令使用,在镜像启动后也会存在于环境变量中。
例如:
ENV APP_VERSION=1.0.0
ENV PATH $PATH:/usr/local/bin
指令指定的环境变量在启动时也可以覆盖掉:docker run --env <key>=<value> built_image
注意当一条 ENV 指令中同时为多个环境变量赋值并且值也是从环境变量读取时;会为变量先赋值后更新。如下面指令,最终结果是 key1=value1 key2=value2
ENV key1=value2
ENV key1=value1 key2=${key1}
2.1.6 ENTRYPOINT
指定镜像的默认入口命令,该命令会在启动容器时作为跟命令执行,所有传入值作为该命令的参数。
支持两种格式:
- ENTRYPOINT ["executable", "param1", "param2"]: exec 调用执行
- ENTRYPOINT command param1 param2: shell 调用执行。
此时,若是指定了 CMD 指令或者 docker run 指定了启动命令,则会作为 ENTRYPOINT 根命令的参数。
每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个时,只有最后一个生效。
在运行时,可以被 --entrypoint 参数覆盖掉。
关于 ENRTYPOINT 和 CMD 的更多差异可以参考 https://www.cnblogs.com/lienhua34/p/5170335.html。
2.1.7 VOLUME
创建数据卷挂载点,挂载到本地的一个随机目录下。
格式为 VOLUME ["/data"]。
2.1.8 USER
指定运行容器时的用户名或者 UID,后续的 RUN 指令也会默认使用特定的用户身份。
格式为 USER daemon。
当服务不需要管理员权限时,可以在 Dockerfile 中创建需要所需要的用户,并通过 USER 命令指定该运行用户。例如:
RUN groupadd -r postgres && useradd --no-log-init -r -g postgress postgress
后续需要获取临时管理员权限可以使用 gosu 命令。
2.1.9 WORKDIR
为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录。
格式为 WORKDIR /path/to/workdir
可以使用多个 WORKDIR 指令,但是后续指令如果是相对路径,基于之前的指令指定的路径。
例如:
WORKDIR /a
WORKDIR b
结果是路径 /a/b,所以推荐要是需要写多个 WORKDIR,则每个 WORKDIR 都写绝对路径。
2.1.10 ONBUILD
指定当基于所生成的镜像创建子镜像时,自动执行的操作指令。
格式为 ONBUILD [INSTRUCTION]
由由于这个命令是隐式执行的,子镜像创建过程中察觉不到,所以应该在镜像标签中进行标注,例如 ruby:2.1-onbuild。
这个指令在创建专门用于自动编译、检查等操作的基础镜像时十分有用。
2.1.11 STOPSIGNAL
默认的stop-signal是SIGTERM,在docker stop的时候会给容器内PID为1的进程发送这个signal,通过--stop-signal可以设置自己需要的signal,主要的目的是为了让容器内的应用程序在接收到signal之后可以先做一些事情,实现容器的平滑退出,如果不做任何处理,容器将在一段时间之后强制退出,会造成业务的强制中断,这个时间默认是10s。
格式为 STOPSIGNAL signal
2.1.12 HEALTHCHECK
配置所启动容器如何进行健康检查(如何判断健康与否)。格式有两种:
- HEALTHCHECK [OPTIONS] CMD command:根据所执行命令返回值是否为 0 来判断
- HEALTHCHECK NONE: 禁止基础镜像中的健康检查
OPTIONS 支持如下参数:
- -interval=DURATION (default: 30s): 过多久检查一次。
- -timeout=DURATION (default: 30s): 每次检查的超时时间。
- retries=N (default: 3): 如果失败了,重试几次才最终确定失败。
2.1.13 SHELL
指定其他命令使用 shell 时的默认 shell 类型。
SHELL ["executable", "parameters"]。
默认值是 ["/bin/sh", "-c"]。
2.2 操作指令
2.2.1 RUN
运行指定命令。
格式为 RUN <command> 或 RUN ["executable", "param1", "param2"]。命令过长可以使用转义字符换行,比如默认的
每条 RUN 指令会生成一个新的镜像层。
2.2.2 CMD
CMD 容器用来指定容器启动时默认执行的命令。支持三种格式:
- CMD ["executable", "param1", "param2"]: 相当于执行 executable param1 param2,推荐方式
- CMD command parame1 param2: 在默认的 shell 中执行,提供给需要交互的应用
- CMD ["param1", "param2"]: 提供给 ENTRYPOINT 的默认参数
每个镜像只能有一个 CMD,多条会覆盖,可以在运行 (run) 容器时通过参数覆盖
2.2.3 ADD
添加内容到镜像。格式为 ADD <src> <dest>。
该命令复制指定的 <src> 下的内容到容器中的 <dest> 路径下。
其中 <src> 可以是 Dockerfile 所在目录的一个相对路径(文件或目录);也可以是一个 URL;还可以是一个 tar 文件(会自动解压为目录)。<dest> 可以是镜像内的绝对路径,或者相对于工作目录 WORKDIR 下的相对路径。且路径支持正则格式。
2.2.4 COPY
COPY 指令与 ADD 相似,语义上 CPOY 更贴切,推荐使用 COPY。
3. 创建镜像
编写完 Dockerfile 后就可以通过 docker [image] build [OPTIONS] PATH|URL|-
命令来创建镜像。
要注意的点有:
- 碰到 ADD、COPY 和 RUN 指令会生成一层新的镜像
- 执行命令的过程中会把 Dockerfile 文件所在的上下文都发给服务端。因此应该避免无关的文件存在上下文,或者使用 .dockerignore 文件。
- 非上下文路径的 Dockerfile 使用 -f 选项来指定其路径
- 要指定生成镜像的标签信息,可以使用 -t 选项。该选项可以重复使用多次来添加多个标签。
3.1 OPTIONS 命令选项
选项 | 说明 |
---|---|
-add-host list | 添加自定义的主机名到 IP 的映射 |
-build-arg list | 添加创建时的变量 |
-cache-from strings | 使用指定镜像作为缓存源 |
-cgroup-parent string | 继承上层 cgroup |
-compress | 使用 gzip 来压缩创建上下文数据 |
-cpu-period int | 分配的 CFS 调度器时长 |
-cpu-quota int | CPU 调度器总份额 |
-c, -cpu-shares int | CPU 权重 |
-cpuset-cpus string | 多 CPU 允许使用的 CPU |
-cpuset-mems string | 多 CPU 允许使用的内存 |
-disable-content-trust | 不进行镜像校验,默认为真 |
-f,-file string | Dockerfile 名称 |
-force-rm | 总是删除中间过程的容器 |
-iidfile string | 将镜像 ID 写入到文件 |
-isolation string | 容器的隔离机制 |
-label list | 配置镜像的元数据 |
-m,-memory bytes | 限制使用内存量 |
-memory-swap bytes | 限制内存和缓存的总量 |
-network string | 指定 RUN 命令时的网络模式 |
-no-cache | 创建镜像时不适用缓存 |
-platform string | 指定平台类型 |
-pull | 总是尝试获取镜像的最新版本 |
-q,-quiet | 不打印创建过程中的日志信息 |
-rm | 创建成功后自动删除中间过程容器,默认为真 |
-security-opt strings | 指定安全相关的选项 |
-shm-size bytes | /dev/shm 的大小 |
-squash | 将新创建的多层挤压放到一层中 |
-stream | 持续获取创建的上下文 |
-t,-tag list | 指定镜像的标签列表 |
-target string | 指定创建的目标阶段 |
-ulimit ulimit | 指定 ulimit 变量 |
3.2 选择父镜像
从这个镜像的继承图来看,我们可以选择两种镜像作为父镜像,一种是基础镜像(灰色),另一种是普通镜像(白色)。
3.3 使用 .dockerignore 文件
从前面我们可以知道,Dockerfile build 的过程中会把上下文一起发给服务端,如果有不想发送的文件(加快传输过程),可以使用一下模式忽略文件:
- /temp
- //temp*
- tmp?
- ~*
- Dockerfile
- !README.md
其中 * 表示任意多的字符, ? 代表单个字符, ! 代表不匹配(即不忽略指定的路径或文件)。
3.4 多步骤创建
从 17.05 版本开始,Docker 支持多步骤镜像创建,用来精简最终生成的镜像的大小。
举个简单的例子,制作一个 Java 程序镜像,分成两个步骤创建,第一步用包含 maven 库及 JDK 的编译镜像编译程序,第二部把生成的 jar 文件复制到运行镜像中,打包运行镜像就得到精简的镜像。
以 Go 语言为例,提供一个例子:
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello, Docker")
}
FROM golang:1.9 as builder
RUN mkdir -p /go/src/test
WORKDIR /go/src/test
COPY main.go .
RUN CGO_ENABLED=0 GOOS=linux go build -o app .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR/root/
COPY --from=builder /go/src/test/app .
CMD ["./app"]
编译镜像,可以看到最终大小只有 6.83 MB
>docker build -t wean2018/test-multistage:latest .
> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
wean2018/test-multistage latest 2e2eed03a664 53 seconds ago 6.83MB