一、介绍
Dockerfile 是一个文本格式的配置文件,用户可以使用它来快速构建自定义的镜像。Dockerfile 由一行行命令语句组成,并且支持以#开头的注释行。一般而言,Dockerfile 主体内容主要分为三部分:基础镜像信息、镜像操作指令和容器启动时执行指令。在配置好 Dockerfile 后可以使用 docker build 命令基于它去创建一个自动构建的镜像。
官方文档:https://docs.docker.com/engine/reference/builder/
二、基础指令
指令 | 说明 |
---|---|
FROM | 指定所创建镜像的基础镜像 |
ARG | 定义创建镜像过程中使用的变量 |
ENV | 定义环境变量 |
LABEL | 为生成的镜像添加元数据标签信息 |
EXPOSE | 通知容器在运行时监听指定的网络端口 |
VOLUME | 创建一个数据卷挂载点 |
WORKDIR | 定义工作目录 |
ONBUILD | 当生成的镜像被用作另一构建的基础镜像时,自动触发执行的操作指令 |
ADD | 添加内容到镜像 |
COPY | 复制内容到镜像 |
RUN | 运行指定命令 |
CMD | 启动容器是指定默认执行的命令 |
三、指令说明
以下仅为指令的部分说明,还有不少其它用法,详见官方文档。在做示例时,请先阅读第四部分构建,明白如何使用 docker build 命令基于写好的 Dockerfile 来构建镜像。
1、FROM
指定所创建镜像的基础镜像。任何 Dockerfile 中第一条指令都必须从 FROM 指令开始。
格式:
FROM <image> [AS <name>]
FROM <image>:<tag> [AS <name>]
示例:
FROM centos
FROM centos:7.6.1810
ARG ERSION=7.6.1810
FROM centos:${VERSION}
值得注意的是,ARG 是可以在 FROM 指令之前,但是先于 FROM 指令的 ARG 指令也仅用于 FROM 指令。因为先于 FROM 的 ARG 其实算是位于构建阶段之外,因此不能在 FROM 之后的任何指令中使用它。如果要在 FROM 之后使用它,需要再次使用 ARG 指令定义下先前定义的变量名并不设置值即可,如下:
ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version
一个 Dockerfile 文件中可以存在多个 FROM 指令以创建多个镜像,也可以将一个构建阶段作为对另一个构建阶段的依赖,只需在每条新 FROM 指令之前记录一次提交所输出的最后一个镜像ID 。注意每个 FROM 指令会清除由先前指令创建的任何状态。据此可以依据这个特性进行多步骤构建镜像。
可以通过在 FROM 指令中添加 AS name 来为新的构建阶段指定名称。该名称可以在后续的 FROM
和 COPY --from=<name>
指令中使用,以引用在此阶段构建的镜像。一般多用于多步骤构建。
2、ARG
定义构建镜像过程中使用的变量。在执行 docker build 构建镜像时,可以使用 –build-arg 选项来传递变量。
格式:
ARG <name>[=<default value>]
示例:
ARG user=www
docker build --build-arg user=www . # 构建命令
ARG user # Dockerfile指令
注:使用 ENV 指令定义的环境变量始终会覆盖 ARG 同名指令,无论 ARG 指令在 ENV 前还是后都覆盖。
3、ENV
定义环境变量。和 ARG 指令样都可以在镜像构建过程中被 RUN 指令使用,但是不同于 ARG 的是,ENV 定义的环境变量在镜像启动的容器中也会存在,而 ARG 定义的仅用于构建镜像过程中。
格式:
ENV <key>=<value> ...
示例:(引号和反斜杠可用于在值中包含空格)
ENV MY_NAME="John Doe"
ENV MY_DOG=Rex\ The\ Dog
ENV MY_CAT=fluffy
ENV MY_NAME="John Doe" MY_DOG=Rex\ The\ Dog \
MY_CAT=fluffy
4、LABEL
为生成的镜像添加元数据标签信息。这些元数据信息可以用来辅助过滤特定镜像。
格式:
LABEL <key>=<value> ...
示例:(LABEL是键值对,引号和反斜杠可用于在值中包含空格。)
LABEL version="1.0"
LABEL description="This is a small example"
一个镜像可以有多个 LABEL 标签。你可以在一行上指定多个标签。在 Docker 1.10 之前,这将减小了最终镜像的大小,但是情况不再如此。以下是在一条指令中指定多个标签的两种方式:
LABEL multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \
multi.label2="value2" \
other="value3"
5、EXPOSE
通知容器在运行时监听指定的网络端口。你可以指定端口是侦听 TCP 还是 UDP,如果未指定协议,则默认为 TCP。
格式:
EXPOSE <port> [<port>/<protocol>...]
示例:
EXPOSE 80/tcp
EXPOSE 80/udp
6、VOLUME
创建一个数据挂载点。运行容器时可以从本地主机或者其他容器挂载数据卷。数据卷只要不主动删除,一般会一直存在。一般用于持久化数据。
格式:
VOLUME ["/data"]
示例:(该值可以是 JSON 数组,也可以是具有多个参数的纯字符串。)
VOLUME ["/var/log/"] # 此处必须使用双引号
VOLUME /var/log
VOLUME /var/log /var/db
使用以下 Dockerfile 构建镜像,再使用构建的镜像 docker run 时会创建一个新的挂载点 /myvol,并会将 greeting 文件复制到新创建的卷中。
FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol
卷可以使用 docker volume ls 查看,不过一般是随机生成的一串字符。创建的数据卷放在 /var/lib/docker/volumes 目录下,而数据是放在数据卷的 _data 目录里面,我们可以在这个目录下看到 greeting 文件。
注:该指令是无法从 Dockerfile 内挂载主机目录的,即不能实现类似于 docker run 的 -v 选项将主机指定命令映射到容器内。
7、WORKDIR
定义工作目录。即为后续 RUN、CMD、COPY、ADD、ENTRYPOIN 指令配置工作的目录。
格式:
WORKDIR /path/to/workdir
示例:
可以使用多个 WORKDIR 指令,如果后续指令提供的是相对路径,则它将相对于上一条 WORKDIR 指令的路径。例如:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
这样最终的工作目录路径是 /a/b/c。
8、ONBUILD
当生成的镜像被用作另一构建的基础镜像时,自动触发执行的操作指令。任何构建指令都可以注册为触发器。
格式:
ONBUILD <INSTRUCTION>
示例:
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
注:不允许使用 ONBUILD ONBUILD。ONBUILD 指令可能不会触发 FROM 或 MAINTAINER 指令。
9、ADD
添加内容到镜像。
格式:
ADD <src> <dest>
该指令将复制指定的 <src>
路径下内容到镜像的
<src>
可以是当前构建目录下的一个相对路径(文件或者目录),也可以是一个远程URL。如果远程 URL 文件受身份验证保护,则需要使用RUN wget、 RUN curl 或从容器中使用其它工具,因为 ADD 指令不支持身份验证。
<src>
可以指定多个资源,如果它们是文件或目录,则将其路径解释为相对于当前构建目录下的相对路径。
如果 <src>
是一个 tar 压缩文件(gzip、bzip2 或 xz)会自动解压,但是自来远程 URL 的资源不会被解压缩。
<src>
支持通配符,并且匹配将使用 Go 的 filepath.Match 规则完成。
<dest>
可以是镜像内的绝对目录,也可以是相对于工作目录(WORKDIR)的相对路径
示例:
ADD home.txt /mydir/ # 添加"home.txt"文件
ADD hom* /mydir/ # 添加所有以"hom"开头的文件
ADD hom?.txt /mydir/ # ? 表示一个单字符,例如:"home.txt"
ADD test relativeDir/ # 相对路径。表示添加"test"文件到 `WORKDIR`/relativeDir/ 下
ADD test /absoluteDir/ # 绝对路径。表示添加"test"文件到 /absoluteDir/ 下
添加包含特殊字符(例如:[ 和 ])的文件或目录时,需要按照 Golang 规则转义那些路径,以防止将它们被视为匹配模式。例如,要添加名为 arr[0].txt 的文件,请使用以下命令;
ADD arr[[]0].txt /mydir/
注:
10、COPY
复制内容到镜像
格式:
COPY <src> <dest>
该指令将复制本地主机指定的 <src>
路径下内容到镜像的 <dest>
路径下。
<src>
是当前构建目录下的一个相对路径(文件或者目录),也就是说要复制的文件或者目录要处于构建目录下。
<src>
可以指定多个资源,如果它们是文件或目录,则将其路径解释为相对于当前构建目录下的相对路径。
<src>
支持通配符,并且匹配将使用 Go 的 filepath.Match 规则完成。
<dest>
可以是镜像内的绝对目录,也可以是相对于工作目录(WORKDIR)的相对路径
COPY 指令用法和上面的 ADD 指令类似,除了不支持自动解压缩和远程URL,都基本类似。
示例:
COPY home.txt /mydir/ # 复制"home.txt"文件
COPY hom* /mydir/ # 复制所有以"hom"开头的文件
COPY hom?.txt /mydir/ # ? 表示一个单字符,例如:"home.txt"
COPY test relativeDir/ # 相对路径。表示复制"test"文件到 `WORKDIR`/relativeDir/ 下
COPY test /absoluteDir/ # 绝对路径。表示复制"test"文件到 /absoluteDir/ 下
复制包含特殊字符(例如:[ 和 ])的文件或目录时,需要按照 Golang 规则转义那些路径,以防止将它们被视为匹配模式。例如,要添加名为 arr[0].txt 的文件,请使用以下命令;
COPY arr[[]0].txt /mydir/
注:
11、RUN
运行指定命令
格式:(有两种格式)
RUN <command>
RUN ["executable", "param1", "param2"]
第一种格式为 shell 形式,命令在 shell 中运行,默认情况,在 Linux 下以 "/bin/sh -c" 运行。
第二种格式为 exec 形式,其中指令会被解析为 JSON 数组形式,必须用双引号,用的是 exec 执行的,不会启动 shell 环境。
每条 RUN 指令将再当前镜像基础上执行指定命令,并提交为新的镜像层。当命令较长时可以使用 \ 来换行。
示例:
RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'
要使用 "/bin/sh" 以外的其它 shell,请使用 exec 形式传入所需的 shell,例如:
RUN ["/bin/bash", "-c", "echo hello"]
注:在 JSON 格式中,必须转义反斜杠。
12、CMD
指定启动容器时默认执行的命令。
格式:
CMD ["executable","param1","param2"]
(exec 形式,这是首选形式)
CMD ["param1","param2"]
(作为 ENTRYPOINT 的默认参数)
CMD command param1 param2
(shell 形式)
每个 Dockerfile 只能有一条 CMD 命令,如果指定了多条命令,只有最后一条会被执行。如果 docker run 时手动指定了运行的命令,则会覆盖 CMD 指定的命令。
示例:
如果使用的 shell 形式的 CMD,则将默认使用 “/bin/sh -c” 执行。例如:
FROM ubuntu
CMD echo "This is a test." | wc -
如果要将 <command>
没有 shell 的情况下运行,则必须将命令表示为 JSON 数组,并提供可执行文件的完整路径。 此数组形式是 CMD 的首选格式。任何其他参数必须在数组中分别表示为字符串。例如:
FROM ubuntu
CMD ["/usr/bin/wc","--help"]
四、构建
编写完 Dockerfile 之后,可以通过 docker build 命令来构建镜像。
命令格式:(URL是是一个Git仓库的位置)
docker build [OPTIONS] PATH | URL | -
该命令将读取指定目录下(包括子目录)的 Dockfile,并将该路径下所有数据作为上下文发送给 Docker守护进程。然后 Docker 会校验 Dockerfile 格式通过后,逐条执行其中的指令。此过程会将每个指令的结果提交到新镜像,然后最后输出最终新镜像的ID。注意,每条指令都是独立运行的,并会导致创建新的镜像。
所以不要用你的根目录作为构建的路径,因为它会将你硬盘驱动器的全部内容传输给 Docker守护进程。
示例:在当前目录下构建镜像,使用当前目录下 Dockerfile 文件,并指定镜像的标签。
docker build -t test/test1:v1 .
参数说明:
-t:指定镜像的标签
-f:指定Dockerfile名称,默认为"PATH/Dockerfile"(即当前路径下的Dockerfile)
五、.dockerignore文件
.dockerignore 文件用于忽略匹配路径或者文件,从而在构建时不将无关数据发送到 Docker 守护进程。它有助于避免不必要地将较大或敏感的文件和目录发送到 Docker 守护进程,并避免使用 ADD 或 COPY 将它们添加到镜像中。
以下是一个示例 .dockerignore 文件:
# comment
*/temp* # 排除任何名称以 /temp 开头的文件和目录。例如,排除了普通文件 /somedir/temporary.txt,也排除了目录 /somedir/temp。
*/*/temp* # 排除了从根以下下两个级别开始以 temp 开头的文件和目录。例如,/somedir/subdir/temporary.txt 被排除。
temp? # 排除根目录中名称为 temp 后接单个字符的文件和目录。例如,/tempa 和 /tempb 被排除。
以 ! 开头的行可用于排除。以下示例表示除 README.md 文件外,所有 md 文件都将排除在外。
*.md
!README.md
注:*(表示任意多个字符)、?(表示单个字符)、!(表示不匹配)。
六、多阶段构建
自 17.05 版本开始,Docker 支持多阶段构建镜像( Multi-stage build) 特性,它可以精简最终生成的镜像大小。
多阶段构建镜像允许在 Dockerfile 中使用多个 FROM 指令。两个 FROM 指令之间的所有指令会生产一个中间镜像,最后一个 FROM 指令之后的指令将生成最终镜像。每个 FROM 指令会清除由先前指令创建的任何状态,所以它能有效的精简最终生成的镜像大小。
中间镜像中的文件可以通过 COPY --from=<image-number>
或者 COPY --from=<name>
指令拷贝。其中 image-number 表示镜像编号,0为第一个构建的基础镜像;name 表示在 FROM 指令中使用 AS name 指定的名称。–from 选项也可以指定本地或者远程仓库的镜像。
对于需要编译的应用(如 C、Go、Java等)来说,这意味着要为应用构建 Docker 镜像是需要编译源代码以及打包目标代码,通常情况下这至少需要准备两个环境的 Docker 镜像:
• 编译环境镜像:包括完整的编译引擎、依赖库等,往往比较庞大。作用是编译源码为二进制文件。
• 运行环境镜像:利用编译好的二进制文件,运行应用,由于不需要编译环境,体积比较小。
而使用多步骤创建,我们可以只使用一个 Dockerfile 来定义整个构建过程。这样,就不需要定义多个 Dockerfile,也不需要使用数据卷来拷贝文件,并且保证了最终生成的运行环境镜像的精筒。
以 Go语言应用为例。创建一个新目录,进入到目录中,创建 main.go 文件。
root@cp:~# mkdir /go
root@cp:~# cd /go/
root@cp:/go# vim main.go
// main.go will output "Hello, Docker"
package main
import ("fmt")
func main() {
fmt.Println("Hello, Docker")
}
创建 Dockerfile,使用 golang:1.9 镜像编译应用二进制文件为 app,使用 alpine:latest 作为运行环境镜像。
root@cp:/go# vim Dockerfile
FROM golang:1.9 as builder
RUN mkdir -p /go/src/test
WORKDIR /go/src/test
COPY main.go .
RUN CGO_ENABLED=O 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"]
执行如下命令构建镜像。我们可以看到会生成两个镜像,test/test-go:latest 镜像就是我们最终生成的运行环境镜像,只有7.99MB。而
root@cp:/go# docker build -t test/test-go .
......
Successfully built d6b30b26fa21
Successfully tagged test/test-go:latest
root@cp:/go# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test/test-go latest d6b30b26fa21 14 seconds ago 7.99MB
<none> <none> 4ddee896749e 48 seconds ago 751MB
使用最终生成的运行环境镜像创建容器运行应用测试。
root@cp:/go# docker run -it test/test-go
Hello, Docker
六、注意事项
• 精简镜像用途:尽量让每个镜像的用途都比较单一集中,避免构造大而复杂、多功能的镜像。
• 选用合适的基础镜像:容器核心是应用,选择过大的基础镜像(centos、ubuntu)会导致生成的镜像臃肿。我们可以选择瘦身过的应用镜像,如node:slim
;也可以选择小巧的系统镜像,如 alpine
、busybox
。
• 尽可能减少镜像层数:通常来说镜像层数越多,镜像越大。我们可以尽量合并 RUN、ADD、COPY 指令。通常情况下,多个 RUN 指令可以合并为一条 RUN 指令。
• 根据实际情况正确使用镜像版本号:使用明确的版本号,而非依赖于默认的latest。通常镜像的版本号(TAG) 我们可以在 hub.docker.com 查询到。通过版本号我们可以尽可能避免环境不一致所导致的问题。
• 恰当使用多步骤构建镜像,有助于精简镜像大小。
• 合理使用.dockerignore文件,有助于避免发送不必要的数据。
参考:
书籍:《Docker技术入门与实战》 作者: 杨保华 / 戴王剑 / 曹亚仑
官方文档:https://docs.docker.com/engine/reference/builder/
文章:https://blog.fundebug.com/2017/05/02/about-docker-sock/ 作者:Fundebug