一篇文章带你吃透 Dockerfile

引言

刚开始使用 Docker 时,我们直接从 Docker 的官方镜像仓库拉取镜像,然后根据这些镜像创建出容器并运行

实际上,Docker 官方镜像也是通过一定的方式构建出来的,如果能弄清楚其中的构建逻辑,我们也可以仿照官方镜像的构建过程,构建出自己想要的镜像

Dockerfile 就是这样一个用于描述 Docker 镜像构建过程的文本文件,这个文件可以包含多条构建指令,以及相关的描述

因此,在构建自定义 Docker 镜像之前,有必要先了解镜像构建的原理,以及 Dockerfile 的相关语法,才能写出高效的 Dockerfile,以便于缩短整体构建时间、减小最终镜像的大小

一、镜像构建原理

(一) Docker 架构模式回顾

如图所示,Docker 使用了 client / server 的架构模式。构建镜像时,用户在 Docker Client 输入构建命令,Docker 引擎以 REST API 的形式,向 Docker daemon 发送构建请求,然后 Docker daemon 就根据构建请求的内容,开始镜像构建的工作了,并向 Client 持续返回构建过程的信息,让用户可以看到当前的构建状态

(二) 镜像分层模型 

Docker 镜像是用于创建容器的只读模板,是通过 Dockerfile 中定义的指令构建而成的,构建结束后,会在原有的镜像层上生成一个新的镜像层,如下图所示

用 tomcat 镜像创建一个容器,会在 tomcat 镜像之上新建一个可写的容器层,在容器中写文件时,会保存到这个容器层中,如下图所示

(三) 基础镜像与父级镜像

用于构建基础镜像的 Dockerfile 不指定父级镜像,Docker 约定使用如下的形式定义基础镜像

FROM scratch

这里的 scratch 是一个空镜像,可以从零开始构建镜像,常被用来构建最小化镜像,如 busybox, debian, alpine 等镜像,省去了很多 Linux 命令,因此镜像很小。一般情况下,不需要自己构建基础镜像

构建自定义镜像时,通过 FROM 指定使用什么父级镜像。一般而言,官方的 tomcat 命令没有 yum, vim 等命令。可以将 tomcat 镜像作为父级镜像,然后安装 yum,vim 命令,然后构建自定义镜像,这样就可以在容器中直接使用这些命令了。如下图所示

(四) 构建上下文 / build context

Client 向 Docker daemon 发送的构建请求包含两部分,第一部分是最重要的 Dockerfile 文件,第二部分就是构建上下文

构建上下文是一些文件集合,这些文件可以是指定路径下的文件,也可以是远程资源中指定路径下的文件。构建过程中,Docker daemon 可以访问这些文件,并执行相应的操作

构建上下文可以分为以下 3 种情况

1. 路径上下文

构建命令中指定具体路径,该路径下的所有文件即为构建上下文,这些文件会被打包并发送到 Docker daemon 中,然后被解压

假设一个项目的文件结构如下

demo
├── Dockerfile
├── src
├── test
└── node_modules

 在项目根目录执行如下构建命令

docker build -t img-tag .

构建请求的第 1 部分是 Dockerfile,这个文件在当前目录下,文件名是默认名称,因此可以省略

相当于默认加上了 -t Dockerfile,Dockerfile 内容如下

# syntax=docker/dockerfile:1
FROM busybox
WORKDIR /src
COPY src .

构建请求的第 2 部分是 .  这个点代表当前目录,此时当前目录就是此次的构建上下文,Docker 引擎会整理该目录下的所有文件,把不被 .dockerignore 中的规则所匹配的文件都发送到 Docker daemon 中,如下图所示

如果此时位于项目根目录的上一级目录,构建命令如下

docker build -t img-tag -f ./demo/Dockerfile ./demo/

2. URL 上下文

Docker 还支持利用远程仓库 URL 构建镜像,此时指定的远程仓库目录就充当了构建上下文

docker build https://gitee.com:user/my-repo.git#master:docker

以上构建命令指定了一个 Gitee  项目的 master 分支,冒号(:)之前是 Git 检出的目标 URL, 冒号之后的 docker 是远程仓库根目录下的一个子目录,此时这个子目录就是构建上下文

Docker client 执行构建命令时,Docker 引擎首先会将远程仓库的 master 分支拉取到本地的一个临时目录中,然后将其中的 docker 目录下的文件作为构建上下文发送到 Docker daemon 中。拉取远程文件之后,又回到了路径上下文的步骤,如下图所示

3. 省略上下文

如果 Dockerfile 中的指令不需要对任何文件进行操作,可以省略构建上下文,此时不会向 Docker daemon 发送额外的文件,这可以提高构建速度

示例构建命令如下

docker build -t my-hello-world:latest -<<EOF
FROM busybox
RUN echo "hello world"
EOF

(五) 构建缓存

迭代过程中,Dockerfile 对应的资源会经常修改,因此需要频繁重新构建镜像,Docker 为了提高构建速度,设计了多种优化方案,其中最重要的就是构建缓存

下面会通过一个示例来说明构建缓存是如何工作的,Dockerfile 如下

# syntax=docker/dockerfile:1
FROM ubuntu:latest

RUN apt-get update && apt-get install -y build-essentials
COPY main.c Makefile /src/
WORKDIR /src/
RUN make build

镜像构建过程中,Dockerfile 中的指令会从上往下执行,每一个构建步骤的结果会被缓存起来,如图所示

此时再次构建,会直接使用构建缓存中的结果 (Using cache)

现在假设修改了 main.c 中的代码, 再次构建时,从 COPY main.c Makefile /src/ 这条指令开始,后续的构建缓存都会失效,如下图所示

如果不想使用构建缓存,执行构建命令时,可以传入 --no-cache

(六) 镜像构建过程

Docker Client 执行构建命令后,会经过以下步骤构建出最终镜像

  1. 确定构建上下文,如果构建上下文中有 .dockerignore 文件,解析该文件的匹配规则,将构建上下文中被匹配的文件资源排除
  2. 将 Dockerfile 和构建上下文发送给 Docker daemon
  3. Docker daemon 收到构建请求。以下的步骤都由 Docker daemon 完成,省略主语
  4. 逐条校验 Dockerfile 中的指令是否合法,如果不合法,立即结束构建。这一步可以确定一共有多少个构建步骤,便于后续分步构建时显示当前步骤,如 Step 1/2
  5. 逐条执行 Dockerfile 中的指令,每条指令都新创建一层。会生成临时 container 用于执行命令,该步骤结束后删除临时容器
  6. 生成最终镜像

二、.dockerignore 介绍

.dockerignore 是一个文本文件,用于排除构建上下文中的可以匹配的文件或文件夹,这与 .gitignore 很相似

.dockerignore 文件需要放在构建上下文的根目录下,在 Docker Client 发送构建请求之前,会先检查这个文件是否存在。如果该文件存在,就会解析这个文件,从构建上下文中删除可以匹配的文件资源,然后将构建上下文中剩下的文件发送到 Docker daemon

(一) 语法规则

这个文件需要遵循一定的语法规则

  1. 以 # 开头的行是备注,不会被解析为匹配规则
  2. 支持 ? 通配符,匹配单个字符
  3. 支持 * 通配符,匹配多个字符,只能匹配单级目录
  4. 支持 ** 通配符,可匹配多级目录
  5. 支持 ! 匹配符,声明某些文件资源不需要被排除
  6. 可以用 .dockerignore 排除 Dockerfile 和 .dockerignore 文件。Docker Client 仍然会将这两个文件发送到 Docker daemon,因为 Docker 底层需要。但 ADD 和 COPY 指令就无法操作这两个文件了

(二) 示例

一个 .dockerignore 文件如下

# this is a .dockerignore demo

*/demo*
*/*/demo*
demo?
**/mydemo*

匹配规则说明,假设构建上下文根目录为 /

  1. 第一行是注释,忽略
  2. */demo* 表示排除构建上下文中第一级目录下以 demo 开头的文件夹或文件,比如 /test/demo-file.txt 和 /another/demo-directory/ 都会被排除
  3. */*/demo* 表示排除构建上下文中第二级目录下以 demo 开头的文件或文件夹
  4. demo? 表示排除构建上下文中以 demo 开头且后面只有一个其他字符的文件或文件夹,比如 demo1, demob
  5. **/mydemo* 表示排除构建上下文中任意目录下以 mydemo开头的文件或文件夹

另一个 .dockerignore 文件如下,假设构建上下文根目录为 /

# this is another .dockerignore demo

*.md
!README*.md
README-secret.md

匹配规则说明,假设构建上下文根目录为 / 

  1. *.md 表示排除构建上下文根目录下所有 Markdown 文件
  2. !README*.md 表示以 README 开头的 Markdown 文件不需要被排除
  3. README-secret.md 表示这个文件也需要被排除

三、Dockerfile 介绍

Dockerfile 是一个用于描述 Docker 镜像构建过程的文本文件,这个文件可以包含多条构建指令,以及相关的描述

Dockerfile 的构建指令需要遵循如下的语法

# Comment
INSTRUCTION arguments

# 开头的行绝大部分是注释,还有一小部分是解析器指令

构建指令分为 2 部分,第一部分是指令,第二部分是指令参数。指令不区分大小写,但是按照惯例,推荐使用大写形式,这样不容易与指令参数混淆

(一) 解析器指令 / parser directive

解析器指令也是以 # 开始,用来提示解释器对 Dockerfile 进行特殊处理,构建过程中它不会增加镜像层,也不会出现在构建过程中

解析器指令是可选的,如果需要定义,要遵循以下语法

# directive=value

# 解析器指令需要在空行、注释、构建指令之前

注意事项

  • 同一解析器指令不能重复
  • 不区分大小写,按照惯例,推荐小写
  • 空行、注释、构建指令之后,Docker 不再查找解析器指令,都当成注释
  • 按照惯例,解析器指令位于 Dockerfile 的第一行,在后面添加空行
  • 行内的空格被忽略,不支持跨行

Docker 目前支持两种解析器指令

  1. syntax
  2. escape

syntax 解析器指令,只有使用 BuildKit 作为构建器时才生效

escape 解析器指令,用于指定在 Dockerfile 中使用转义字符

在 Dockerfile 中,escape 默认为 \ 

# escape=\  

但 Windows 系统中的 \ 是路径分隔符,推荐将 escape 替换为 `,这和 PowerShell 是一致的

# escape=`

(二) 常用指令详解

常用指令总览

序号 指令名 功能描述
1 FROM 指定基础镜像或父级镜像
2 LABEL 为镜像添加元数据
3 ENV 设置环境变量
4 WORKDIR 指定后续指令的工作目录,类似于 Linux 中的 cd 命令
5 USER 指定当前构建阶段以及容器运行时的默认用户,以及可选的用户组
6 VOLUME

创建具有指定名称的挂载数据卷,用于数据持久化

7 ADD 将构建上下文中指定目录下的文件复制到镜像文件系统的指定位置
8 COPY 功能和语法与 ADD 类似,但是不会自动解压文件,也不能访问网络资源
9 EXPOSE 约定容器运行时监听的端口,通常用于容器与外界之间的通信
10 RUN 用于在构建镜像过程中执行命令
11 CMD 构建镜像成功后,所创建的容器启动时执行的命令,常与 ENTRYPOINT 结合使用
12 ENTRYPOINT

用于配置容器以可执行的方式运行,常与 CMD 结合使用

FROM

指定基础镜像或父级镜像

语法
  FROM [--platform=<platform>] <image> [AS <name>]
  FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
  FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
示例
  FROM redis
  FROM redis:7.0.5
  FROM redis@7614ae9453d1

注意事项

  1. tagdigest 是可选项,如果不指定,会使用 latest 版本作为基础镜像
  2. 如果不以任何镜像为基础,写法为:FROM scratch。scratch 是一个空镜像,可用于构建 debian, busybox 等超小镜像,真正地从零开始构建镜像

LABEL

为镜像添加元数据

语法
  LABEL <key>=<value> <key>=<value> <key>=<value> ...
示例
  LABEL author="Jason 315" version="1.0.0" description="Dockerfile案例"
  LABEL author="Jason 315" \
        version="1.0.0" \
        description="Dockerfile案例"

注意事项

  1. LABEL 定义键值对结构的元数据,同一条 LABEL 指令可以指定多个元数据,用空格分开,可以在同一行,也可以用反斜杠分割,放在多行
  2. 定义元数据值的时候,尽量使用双引号,而不是单引号,尤其是使用字符串插值的时候
  3. 当前镜像可以继承基础镜像或或父级镜像中的元数据,也可以覆盖
  4. 可以用如下命令查看元数据

docker image inspect -f='{ {json .ContainerConfig.Labels}}' my-image

ENV

设置环境变量

语法
  ENV <key>=<value> ...
  ENV <key> <value>
示例
  ENV MY_NAME="Jason315" MY_CAT="Tomcat"
  ENV MY_CAT Kitty

注意事项

  1. 第一种方式可以用一条指令同时设置多个环境变量,支持引号以及反斜杠作为续行符号
  2. 第二种方式一次只能设置一个环境变量,key 之后的部分都是 value。Docker 官方不推荐这种写法,并表明后期版本可能会删除这种写法
  3. 当前镜像可以继承基础镜像或或父级镜像中的环境变量,也可以覆盖。因此定义环境变量时要慎重,比如 ENV DEBIAN_FRONTEND=noninteractive 会改变 apt-get 的默认行为
  4. 使用 ENV 指令定义的环境变量,最终会持久化到容器中
  5. 可以在运行容器时,通过 --env <key>=<value>-e <key>=<value> 覆盖镜像中定义的环境变量
  6. 对于只在镜像构建过程中使用的变量,推荐使用 ARG,或者行内环境变量,这样不会被持久化到最终的镜像中
  7. 行内环境变量的一个示例 RUN TEMP_NAME="no persisit" ...
  8. 查看最终镜像中的环境变量

docker image inspect -f='{ {json .ContainerConfig.Env}}' my-image 

WORKDIR

指定后续指令的工作目录,类似于 Linux 中的 cd 命令

语法
  WORKDIR /path/to/workdir
示例
  WORKDIR /a
  WORKDIR b
  WORKDIR c
备注
  当前工作目录为 /a/b/c

 使用 Dockerfile 中设置的环境变量

# 其他指令
ENV DIR_PATH=/demo
WORKDIR $DIR_PATH/$DIR_NAME
RUN pwd

构建镜像时,pwd 的输出结果是 /demo,因为 $DIR_NAME 未显示指定,直接忽略

注意事项

  1. 默认工作目录是 /,即使未显式指定,也没有任何指令用到,也会自动创建
  2. 可以使用 Dockerfile 中显示指定的环境变量,包括父级镜像中的环境变量
  3. 同一个 Dockerfile 中,WORKDIR 命令可以使用多次,如果设置的是相对路径,则会追加到前一个 WORKDIR 命令指定的路径后
  4. 设置 WORKDIR 后,其后的 RUN, CMD, ENTRYPOINT, ADD, COPY 指令的相关操作是在当前工作目录下进行
  5. 由于父级镜像也可能设置工作目录,为了避免在未知的目录下进行操作,最佳实践是显示设置当前镜像的工作目录

USER

指定当前构建阶段以及容器运行时的默认用户,以及可选的用户组

语法
  USER <user>[:<group>]
  USER <user>[:<GID>]
  USER <UID>[:<GID>]
  USER <UID>[:<group>]
示例
  USER jason315
  USER jason315:jasonGroup

注意事项

  1. 指定用户时,可以指定用户名或用户 ID(UID)
  2. 用户组是可选的,可以指定用户组名或用户组 ID(GID)
  3. 使用 USER 指定用户后,Dockerfile 中在此之后的 RUN, CMD, ENTRYPOINT 指令都会使用这个用户,此外,这个用户也是容器运行时的默认用户
  4. 运行容器时,可以通过 -u 参数覆盖 Dockerfile 中设置的默认用户
  5. 不指定用户组时,使用默认用户组 root
  6. 在 Windows 系统中指定非内置用户时,需要先用 net user /add 命令创建用户
FROM microsoft/windowsservercore
# 在容器中创建 Windows 用户
RUN net user /add my_user
# 设置后续指令的用户
USER my_user

VOLUME 

创建具有指定名称的挂载数据卷,用于数据持久化

语法
  VOLUME ["volume1", "volume2", ...]
  VOLUME volume1 volume2 ...
示例
  VOLUME ["/demo/data", "/demo/logs"]
  VOLUME /myvol

作用及特性

  1. 数据持久化,避免容器重启后丢失重要数据
  2. 修改数据卷时不会对容器产生影响,防止容器不断膨胀
  3. 有利于多个容器共享数据

注意事项

  1. 以数组的形式定义时,将以 JSON 数组的形式解析,必须使用双引号,而不是单引号
  2. 定义 VOLUME 后,Dockerfile 的后续指令对该数据卷所做的修改操作会被丢弃

ADD

将构建上下文中指定目录下的文件(src)复制到镜像文件系统的指定位置(dest)

语法
  ADD [--chown=<user>:<group>] [--checksum=<checksum>] <src>... <dest>
  # 路径中含有空格的情况,可以使用第二种方式
  ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
  # 处于实验阶段的特性,直接添加 Git 资源到镜像文件系统中
  ADD <git ref> <dir>
示例
  ADD demo.txt /dest_dir/demo.txt    # 将 demo.txt 复制到 /dest_dir 下的 demo.txt
  ADD demo* /dest_dir/               # 将以 demo 开头的文件复制到 /dest_dir 下
  ADD dem?.txt /dest_dir/            # ? 被一个单字符替换,可以是 demo.txt, 将符合条件的文件复制到 /dest_dir 下
  ADD test/ /dest_dir/               # 将 test 文件夹下的所有文件都复制到 /dest_dir/ 下
  ADD test/ dest_dir/                # 将 test 文件夹下的所有文件都复制到 <WORKDIR>/dest_dir/ 下
  ADD http://example.com/a/url.txt / # 从 URL 下载 url.txt 文件,另存为文件 /a/url.txt,其中文件夹 /a/ 是自动创建的

src 含有特殊字符时的语法

# 假设文件名为 demo[0].txt, 其中含有特殊符号 [ 和 ]
ADD demo[[]0].txt /dest_dir

注意事项

  1. 如果某一 ADD 指令对应的 src 资源有变更,Dockerfile 中这条指令后的构建缓存都会失效
  2. 只有 Dockerfile 用于构建 Linux 容器时才支持 --chown 特性,构建 Windows 容器时不支持
  3. src 支持设置多个文件资源,每个文件资源都会被解析为构建上下文中的相对路径
  4. src 支持通配符,主要是 * 和 ?,通过 Go 语言的 filepath.Match 规则进行匹配
  5. src 中的文件夹或文件含有特殊字符时,需要将其转义,防止被当成匹配模式
  6. 如果 src 是文件夹,其下的所有文件都会被复制,包括文件系统元数据
  7. 如果 src 包含多个文件资源,或者使用了通配符,dest 必须是文件夹,即 dest 以 / 结尾
  8. 如果 src 是压缩资源,将会被解压为一个文件夹,但远程 URL 对应的压缩资源不会被解压。此外,以压缩格式结尾的空文件也不会被解压,而是当成一个普通文件被复制到目标位置
  9. 如果 src 是远程 URL, 并且 dest 不以 / 结尾,Docker 从 URL 下载文件,存到 dest 中
  10. 如果 src 是远程 URL,URL 中含有非空路径,并且 dest 以 / 结尾,Docker 会推断文件名,根据 URL 中的路径,在目标位置创建相同路径,将下载的文件放入其中
  11. dest 可以是镜像文件系统下的绝对路径,或者是 WORKDIR 下的相对路径
  12. 如果 dest 不是以 / 结尾,Docker 会把它当成普通文件,src 中的内容会被写入这个文件中
  13. 如果目标位置下的某些目录不存在,会自动创建
  14. ADD 添加网络资源时不支持身份认证,可以使用 RUN wget 或者 RUN curl 实现这个功能

COPY

功能和语法与 ADD 类似,但是不会自动解压文件,也不能访问网络资源

语法
  COPY [--chown=<user>:<group>] <src>... <dest>
  COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

EXPOSE

约定容器运行时监听的端口,通常用于容器与外界之间的通信

语法
  EXPOSE <port> [<port>/<protocol>...]
示例
  EXPOSE 8080
  EXPOSE 80/tcp
  EXPOSE 80/udp
  EXPOSE 9090/tcp 9090/udp

注意事项

  1. 支持 TCP 或者 UDP 协议,如果不显式指定协议,默认使用 TCP 协议
  2. 需要同时以 TCP 和 UDP 协议的方式暴露同一个端口时,需要分别指定
  3. EXPOSE 并不会真正将端口发布到宿主机,而是作为一种约定,让镜像使用者在运行容器时,用 -p 分别发布约定端口,或者 -P 发布所有约定端口
  4. 如果没有暴露端口,运行容器是也可以通过 -p 的方式映射端口

RUN

用于在构建镜像过程中执行命令,有两种执行方式

第一种,以 shell 的方式执行

语法
  RUN <command>
示例
  RUN echo "Hello Dockerfile" 

在这种方式下,命令是由 Shell 执行的,Linux 系统的默认 Shell 是 /bin/sh -c,Windows 系统的默认 Shell 是 cmd /S /C。可以通过 SHELL 指令修改默认 Shell 

第二种,以 exec 的方式执行

语法
  RUN ["executable", "param1", "param2"]
示例
  RUN ["echo", "Hello Dockerfile"]
  RUN ["sh", "-c", "echo $HOME"]
  RUN ["/bin/bash", "-c", "echo", "Hello Dockerfile"] 

这种方式不会进行某些 Shell 处理,如环境变量替换。如果要进行变量替换等操作,有两种解决方案,其一,以 Shell 方式执行,其二,直接执行 sh 命令。最终的变量替换处理是由 Shell 完成的,而不是 Docker

# 方式一,回到 Shell 执行方式
RUN echo $HOME
# 方式二,直接执行 sh 命令
RUN ["sh", "-c", "echo $HOME"]

注意事项

  1. exec 方式是通过 JSON 数组的方式解析的,因此要使用双引号,而不是单引号
  2. Windows 系统下需要对反斜杠 \ 进行转义
  3. RUN 指令每执行一次都会新建一个镜像层。为了避免创建过多的镜像层,可以用 && 符号连接多个命令
  4. 在下一次构建期间,RUN 指令缓存不会自动失效。构建镜像时,可以用 --no-cache 标志让该指令缓存失效;此外,如果 ADDCOPY 指令对应的文件资源如果有变更,也会让其后的 RUN 指令缓存失效

CMD

构建镜像成功后,所创建的容器启动时执行的命令。CMD 指令有 3 种形式

语法
  CMD command param1 param2 # shell 方式
  CMD ["executable","param1","param2"] # exec 方式,这是推荐的方式
  CMD ["param1","param2"]   # 作为 ENTRYPOINT 的默认参数,这种方式是 exec 方式的特殊形式
示例
  CMD echo "Hello Dockerfile"
  CMD ["echo", "Hello Dockerfile"]
  
  FROM ubuntu
  CMD ["/usr/bin/wc", "--help"]

注意事项

  1. Dockerfile 中只能有一条 CMD 指令生效,即使指定了多条,也只有最后一条生效
  2. 虽然 Dockerfie 中只有最后一条 CMD 生效,但每一条 CMD 指令会新增一个镜像层,因此只定义一条 CMD 指令,可以使用 && 连接多个命令
  3. exec 方式是通过 JSON 数组的方式解析的,因此要使用双引号,而不是单引号
  4. 与 RUN 指令不同,RUN 指令是在构建镜像的过程中执行,CMD 命令是在容器启动时执行
  5. docker run <image> 后的命令行参数会覆盖 CMD 中的命令

ENTRYPOINT

用于配置容器以可执行的方式运行。ENTRYPOINT 指令有 2 种形式

语法
  ENTRYPOINT ["executable", "param1", "param2"] # 推荐方式
  ENTRYPOINT command param1 param2
示例 1
  FROM ubuntu
  ENTRYPOINT ["top", "-b"]
  CMD ["-c"]
示例 2
  FROM ubuntu
  ENTRYPOINT exec top -b

注意事项

  1. Dockerfile 中只有最后一条 ENTRYPOINT 指令生效
  2. 运行容器时,可以用 docker run --entrypoint 覆盖 ENTRYPOINT 指令
  3. 如果指定了exec 形式的 ENTRYPOINT,docker run <image> 中的命令行参数会追加到 JSON 数组的后面
  4. exec 方式是通过 JSON 数组的方式解析的,因此要使用双引号,而不是单引号
  5. shell 形式的 ENTRYPOINT 会使 CMD 命令 和 docker run <image> 中的命令行参数失效。它有一个缺点,ENTRYPOINT 命令将作为 /bin/sh -c 的子命令,不会传递信号。比如,停止容器时,容器内接收不到 SIGTERM 信号,这并不是预期的效果,可以在命令前添加 exec 来解决,如 ENTRYPOINT exec top -b
  6. 指定 ENTRYPOINT 后,CMD 的内容将作为默认参数传给 ENTRYPOINT 指令,形如 <ENTRYPOINT> <CMD>
  7. 如果 CMD 是在基础镜像中定义的,当前镜像定义的 ENTRYPOINT 会将 CMD 的值重置为空值,这种情况下,需要重新定义 CMD

(三) 其他指令介绍

MAINTAINER(deprecated)

设置镜像的 Author 字段,新版本即将废弃,推荐使用 LABEL 代替

语法
  MAINTAINER <name>
示例
  MAINTAINER jason315

ARG

定义构建变量,便于在 Dockerfile 中接收外部参数,这些变量不会持久化到镜像层中

语法
  ARG <name>[=<default value>]
示例 1
  ARG build_user # 不带默认值的构建变量
  ARG build_comment="built by somebody" # 带默认值的构建变量
示例 2
  ARG build_user=Jason315
  ENV build_user=Jason520
  RUN echo $build_user # 此时 build_user 已经被 ENV 指令覆盖为 Jason520

构建镜像时,可以通过 --build-arg 传入构建变量

docker build -t my-img --build-arg build_user=Jason315 .

Docker 有一组预定义的构建变量,可以直接使用

  • HTTP_PROXY | http_proxy
  • HTTPS_PROXY | https_proxy
  • FTP_PROXY | ftp_proxy
  • NO_PROXY | no_proxy
  • ALL_PROXY | all_proxy

注意事项

  1. 不推荐使用构建变量传递敏感信息
  2. 对于有默认值的构建变量,如果构建时传入参数,Dockerfile 中使用传入的参数,如果不传入,使用默认值
  3. 构建变量从 Dockerfile 定义的行开始生效,一直到当前构建阶段结束
  4. 构建变量会被其后定义的同名环境变量覆盖
  5. 构建变量不会被持久化到镜像层中,但构建变量的变化可能会使构建缓存失效,这将在构建变量第一次使用时触发,而不是定义的行

STOPSIGNAL

设置在 docker stop 时被发送到容器内执行退出的系统调用信号。该信号可以是 SIG<NAME> 形式的信号名称,比如 SIGKILL;还可以是与内核系统调用表中的位置匹配的无符号数,比如 9

语法
  STOPSIGNAL signal
示例
  STOPSIGNAL SIGTERM
  STOPSIGNAL 9

注意事项

  1. 如果不显式指定,默认的 STOPSIGNAL 是 SIGTERM
  2. 新建或运行容器时,可以通过 --stop-signal 覆盖 STOPSIGNAL

HEALTHCHECK

在容器内执行某些命令,用于检查容器的健康状况,确认容器是否还在正常运行

语法
  HEALTHCHECK [OPTIONS] CMD command # 在容器内运行命令,检查容器的健康状况
  HEALTHCHECK NONE # 禁用任何健康检查,包括从基础镜像继承而来的
CMD 前的可选参数
  --interval=DURATION (执行时间间隔,默认值 30s)
  --timeout=DURATION (执行单次命令的超时时间,如果超时,会增加失败次数,默认值 30s)
  --start-period=DURATION (启动时间,在该时间段内,检查失败不计入总失败次数,默认值 0s)
  --retries=N (健康检查失败后的重试次数,默认值 3)
示例
  HEALTHCHECK --interval=5m --timeout=3s \
    CMD curl -f http://localhost/ || exit 1

健康检查命令的退出状态码有 3 种情况

  • 0: healthy - 容器正常运行中,处于可用状态
  • 1: unhealthy - 容器运行状态异常,不可用
  • 2:reserved - 未使用退出状态码

注意事项

  1. Dockerfile 中定义了 HEALTHCHECK 指令,启动容器后,状态中会多出健康状态(Health Status)这一项,值为 healthy 或者 unhealthy
  2. 同一 Dockerfile 中,只有最后一条 HEALTHCHECK 生效

SHELL

设置以 shell 方式执行的指令的默认 Shell,这些指令包括 RUN, CMD, ENTRYPOINT

语法
  SHELL ["executable", "parameters"]
示例 1
  FROM ubuntu
  SHELL ["/bin/sh", "-c"]
示例 2
  FROM microsoft/windowsservercore
  SEHLL ["powershell", "-command"]

注意事项

  1. Linux 系统的默认 Shell 是 ["/bin/sh", "-c"]
  2. Windows 系统的默认 Shell 是 ["cmd", "/S", "/C"]
  3. SHELL 指令可多次使用,并影响后续以 shell 方式执行的 RUN, CMD, ENTRYPOINT 指令

ONBUILD

将一个触发指令添加到镜像中,当该镜像作为另一个镜像的基础镜像时执行。简单来说,另一个 Dockerfile 的 FROM 指令使用这个镜像作为父级镜像时触发执行

语法
  ONBUILD <INSTRUCTION>
定义 ONBUILD 的 Dockerfile
  FROM ubuntu
  ONBUILD ["echo", "I'm used as a base image"]
构建父级镜像
  docker build -t onbuild-image .
触发 ONBUILD 的 Dockerfile
  FROM onbuild-image
  RUN ["echo", "I just trigger the ONBUILD instruction"]

查看定义 ONBUILD 所在的镜像暴露的触发器列表

docker inspect -f='{
   
   {json .ContainerConfig.OnBuild}}' onbuild-image

注意事项 

  1. 除 FROM, MAINTAINER, ONBUILD 以外的构建指令都可以作为触发指令
  2. 触发指令不会影响定义触发指令的所在的镜像,构建结束后,触发器列表会被保存在当前镜像的元数据中的 ONBUILD 属性中,可以用 docker inspect 命令查看

参考文档

Dockerfile 官方文档

Dockerfile 详解

猜你喜欢

转载自blog.csdn.net/u013481793/article/details/128338417