概述

Dockerfile 是 Docker 中用于构建镜像的文本文件。它包含了一系列的指令和参数,用于定义镜像的基础操作、环境设置和应用程序配置等。通过 Dockerfile,可以自动化地构建、配置和部署 Docker 容器镜像,使得应用程序的部署过程更加可持续、可重复和自动化。

文件一般由四部分组成:

文件说明

Docker 以从上到下的顺序运行 Dockerfile 指令,以 # 字符开头则被视为注释,可以在文件中使用 RUNCMDFROMEXPOSEENV 等指令。 以 SpringBoot 应用构建为例,一个基本的 Dockerfile 文件内容声明如下:

# Dockerfile with SpringBoot Application
FROM java:8
ARG JAR_FILE
ADD target/${JAR_FILE} app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]

指令详解

如下整理 Dockerfile 指令,点击左侧链接可快速跳转至详细说明,更多用法请参考:

指令 说明
FROM 指定基础镜像,用于后续的指令构建
LABEL 添加镜像的元数据,使用键值对的形式
MAINTAINER已弃用 维护者信息
RUN 在构建过程中在镜像中执行命令
CMD 指定容器创建时的默认命令
ENTRYPOINT 设置容器创建时的主要命令
EXPOSE 声明容器运行时监听的特定网络端口
ENV 在容器内部设置环境变量
ADD 将文件、目录或远程URL复制到镜像中
COPY 将文件或目录复制到镜像中
VOLUME 为容器创建挂载点或声明卷
WORKDIR 设置后续指令的工作目录
USER 指定后续指令的用户上下文
ARG 定义在构建过程中传递给构建器的变量,可使用 docker build 命令设置
ONBUILD 当该镜像被用作另一个构建过程的基础时,添加触发器
STOPSIGNAL 设置发送给容器以退出的系统调用信号
HEALTHCHECK 定义周期性检查容器健康状态的命令
SHELL 覆盖 Docker 中默认的 shell,用于 RUNCMDENTRYPOINT 指令

FROM 指令

FROM 指令用于指定基础镜像,用于后续的指令构建,所以尽可能使用官方镜像,来作为容器的基础环境,有效的 Dockerfile 必须以 FROM 指令开头(用于 FROMARG 除外)。

用法:

FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]

基础示例:

镜像版本查找可通过:https://hub.docker.com/search

# 使用 centos 默认(latest)版本作为基础镜像
FROM centos
# 使用 centos 指定版本(7.9.2009)作为基础镜像
FROM centos:7.9.2009

指定镜像平台示例:

如果引用的是一个多平台镜像,需要交叉编译构建,可以通过 --platform 指定镜像平台,如:linux/amd64linux/arm64windows/amd64,如下:

# 使用 centos  默认(latest)版本作为基础镜像,并指定引用镜像平台为 linux/amd64
FROM --platform=linux/amd64 centos

多构建阶段示例:

可以在 Dockerfile 文件中使用多次 FROM ,以创建多个镜像或使用一个构建阶段作为另一个构建阶段的依赖项,每个 FROM 之前都会清除由先前指令创建的任何状态:

# 第一阶段构建,基于 Node 环境安装依赖打包应用
FROM node AS node1
LABEL maintainer="do@dodoo.co"
COPY ../../../env/linux/docker /app/
WORKDIR /app
RUN npm install
RUN npm build

# 第二阶段,基于 nginx 环境,将打包后静态资源 COPY 至 nginx 默认目录
FROM nginx
COPY --from=node1 /app/dist /usr/share/nginx/html

ARG 和 FROM 如何交互

FROM 指令支持调用由出现在第一个 FROM 之前的任何 ARG 指令声明的变量。

# 示例:使用 ARG 将变量指定基础镜像版本
ARG VERSION=latest
FROM nginx:${VERSION}
CMD /code/run-nginx

# 在 FROM 之前声明的 ARG 变量,不能在 FROM 之后的任何指令中使用
FROM php:${VERSION}
CMD /code/run-php

若要使用在第一个 FROM 之前声明的 ARG 默认值,需要在构建阶段内声明一次没有默认值的 ARG 指令,如下:

ARG VERSION=latest
FROM nginx:${VERSION}

# 再次声明没有默认值的 ARG 指令
ARG VERSION
RUN echo $VERSION

RUN 指令

建议: RUN 指令在构建时将会生成一个新的镜像层,并执行 RUN 指令后的内容,为避免创建多个镜像层,建议将多条 RUN 指令进行合并。

用法:

# shell 模式
RUN <command>
# exec 模式
RUN ["executable","param1","param2"]

shell 模式示例:

命令在 shell 中执行:

可以将多个命令使用(&&)拼接,放在同一个 RUN 指令中,也可以使用反斜杠( \ )将复杂 RUN 命令换行,如下:

# 加 && 拼接多个命令,加 \ 将长命令进行换行
RUN /'source $HOME/.bashrc && \
echo $HOME'

# 替换为其他 shell ,可显示声明 shell ,以 /bin/bash 为例:
RUN /bin/bash -c echo hello

exec 模式示例:

exec 模式可以避免 shell 字符串修改,参数会被解析为 JSON 数组,因此参数必须使用双引号( "" )包裹,exec 不调用命令 shell,所以不会发生正常的 shell 处理,如下:

# 不会对变量进行替换
RUN ["echo", "$HOME"]

# 想要 shell 处理,则可以使用 shell 形式或直接执行 shell
RUN ["/bin/sh", "-c", "echo $HOME"]

如使用除 /bin/sh 之外的其他 shell,可做如下替换:

# 替换 /bin/sh 为 /bin/bash
RUN ["/bin/bash","-c","echo hello"]

RUN --mount 指令

允许创建可以访问的文件系统挂载,可以用于:

用法:

RUN --mount=[type=<TYPE>][,option=<value>[,option=<value>]...]

支持类型:

更多细节及用法,请参考官方文档:RUN --mount

type 描述
bind 默认 绑定挂载上下文目录(只读)。
cache 挂载临时目录以及缓存编译器和包管理器的目录。
tmpfs 允许在构建容器中装载 tmpfs
secret 允许构建容器访问安全文件(如私钥),而无需将其放入到镜像中。
ssh 允许构建容器通过 SSH 代理访问 SSH 密钥,并支持密码短语。

RUN --network 指令

允许控制命令运行的网络环境。

用法:

RUN --network=<TYPE>

支持类型:

更多细节及用法,请参考官方文档:RUN --network

type 描述
default 默认 在默认网络中运行。
none 在没有网络访问的情况下运行。
host 在主机的网络环境中运行。

RUN --security 指令

暂未在稳定的 docker 版本中使用

--security=insecure

构建在不安全模式下运行没有沙箱的命令,即允许需要提升权限的流程,相当于执行
docker run --privileged

--security=sandbox

默认沙箱模式可以通过 --security=sandbox 激活,但这是无操作的。

CMD 指令

用法:

# exec 模式(首选)
CMD ["executable","param1","param2"]
# 作为 ENTRYPOINT 的默认参数
CMD ["param1","param2"]
# shell 模式
CMD command param1 param2

示例:

# 使用 exec 模式
CMD ["/usr/bin/wc","--help"]

# 使用 shell 模式
CMD echo "this is a test." | wc -

LABEL 指令

将元数据添加到镜像中,镜像默认继承基础镜像的标签,如果标签已存在但具有不同的值,则最近应用的值将覆盖任何先前设置的值。

用法:

LABEL <key>=<value> <key>=<value> ...

示例:

LABEL 值使用双引号("")包裹,一个镜像可以包含多个 LABEL,可以在一行指定多个 LABEL ,也可以通过反斜杠( \ )进行换行:

# 一行指定多个 LABEL
LABEL multi.label1="value1" multi.label2="value2" multi.label3="value3"
LABEL version="1.0.0"

# 使用 \ 进行换行
LABEL description="hello \
world! "

MAINTAINER 指令 已弃用

设置镜像的作者信息,已弃用。

MAINTAINER <name>

推荐使用 LABEL 指令,可以设置你需要的任何元数据。

LABEL org.opencontainers.image.authors="do@dodoo.co"

EXPOSE 指令

声明 Docker 容器在运行时监听指定的端口及协议,如果不指定协议,默认为 TCP。 提示 EXPOSE 指令实际上并未发布端口,仅指定容器提供服务需要用到的端口。

用法:

EXPOSE <port> [<port>/<protocol> ...]

示例:

# 暴露 80 端口,udp 协议
EXPOSE 80/udp

# 如果同时暴露 tcp 和 udp 协议
EXPOSE 80/tcp
EXPOSE 80/udp

ENV 指令

设置环境变量,可用于后续所有指令中,与 LABEL 指令相同,允许设置多个变量。

子阶段会继承父阶段或任何祖先阶段的 ENV 设置的环境变量。

用法:

ENV <key>=<value> ...

示例:

# 设置单个变量
ENV CUSTOM_CODE=100

# 设置多个变量(引号:""、反斜杠:\、空格: 可用于变量值中)
ENV CUSTOM_KEY=key  CUSTOM_VALUE="value" CUSTOM_PREFIX=Hello\ World\ prefix

# 替代语法(后续版本可能会删除):可以省略等号,省略等号时,不允许一条指令中设置多个环境变量
ENV CUSTOM_KEY key

ADD 指令

从路径复制新文件、目录或远程文件URL <src> ,添加到镜像的文件系统中 <dest>

ADD 和 COPY 的区别和使用场景

ADDCOPY 的区别和使用场景:

注意:

用法:

ADD [--chown=<user>:<group>] [--chmod=<perms>] [--checksum=<checksum>] <src>... <dest>

# 包含空格的路径需要这种形式
ADD [--chown=<user>:<group>] [--chmod=<perms>] ["<src>",... "<dest>"]

示例:

# 将以 home 开头的所有文件,添加到 customDir 路径下
ADD home* /customDir/

# 用 ? 替换单个字符,将以 home 开头的 txt 文件,添加到 customDir 路径下
ADD home?.txt /customDir/

# 使用相对路径,将 test.txt 添加到工作目录下的 relativeDir 下
ADD test.txt relativeDir/

# 使用绝对路径,将 test.txt 添加到目录 absoluteDir 下
ADD test.txt /absoluteDir/

# 添加包含特殊字符的文件或目录时,需要遵循 Golang 规则对路径进行转义,防止被解析为匹配模式
ADD arr[[]0].txt /customDir/

# 指定文件目录、组权限
ADD --chown=1 files* /customDir/
ADD --chown=bin files* /customDir/

ADD 遵循原则

<src> 路径必须在构建的上下文中

COPY 指令

从容器的文件系统中复制新文件或目录将他们添加到路径中

用法:

COPY [--chown=<user>:<group>] [--chmod=<perms>] <src>... <dest>

# 包含空格的路径需要这种形式
COPY [--chown=<user>:<group>] [--chmod=<perms>] ["<src>",... "<dest>"]

示例:

# 将以 home 开头的所有文件,复制到 customDir 路径下
COPY home* /customDir/

# 用 ? 替换单个字符,将以 home 开头的 txt 文件,复制到 customDir 路径下
COPY home?.txt /customDir/

# 使用相对路径,将 test.txt 复制到工作目录下的 relativeDir 下
COPY test.txt relativeDir/

# 使用绝对路径,将 test.txt 复制到目录 absoluteDir 下
COPY test.txt /absoluteDir/

# 复制包含特殊字符的文件或目录时,需要遵循 Golang 规则对路径进行转义,防止被解析为匹配模式
COPY arr[[]0].txt /customDir/

# 从指定构建阶段 base 寻找源文件 /tmp/example.txt 并复制到 /target/exmaple.txt
FROM ubuntu AS base
COPY --from=base /tmp/example.txt /target/example.txt

COPY 遵循规则

ENTRYPOINT 指令

用法: 当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令

提示 CMDENTRYPOINT 命令都定义了容器运行时的运行命令

dockerfile

# exec 格式
ENTRYPOINT ["executable", "param1", "param2"]

# shell 格式
ENTRYPOINT command param1 param2

exec 格式示例:

# 替换 CMD 参数
ENTRYPOINT ["/bin/echo","Hello"]
CMD ["world"] # 表示默认参数,没提供参数,则使用此参数

docker run -it [image] # 输出 Hello world
docker run -it [image] "Docker" # 输出 Hello Docker, 不使用默认参数
docker run -it [image] "James" "Bond" # 输出 Hello James Bond, 使用多个参数

# ENTRYPOINT 设置默认命令参数,使用 CMD 设置可变动参数
ENTRYPOINT ["top","-b"]
CMD ["-c"]

docker run -it [image] -H # 实际执行 top -b -H

shell 格式示例:

ENTRYPOINT exec top -b

VOLUME 指令

创建一个匿名的数据挂载点,运行容器时,可以从本地主机或其他容器挂载数据卷,一般用来存放数据库和需要保持的数据等

用法:

VOLUME ["/data"]

示例:

# 用法中的 "/data" 可以是一个 JSON 数组
VOLUME ["/var/log/"]

# 也可以是一个或多个纯字符串
VOLUME /var/log
VOLUME /var/log /var/db

注意事项

主机目录(挂载点)在容器运行期间创建:主机目录(挂载点)本质上是与主机相关的,而我们无法保证给定的主机目录在所有主机上都可用,因此为了保持镜像的可移植性,我们不能从 Dockerfile 中挂载主机目录,所以 VOLUME 指令不支持指定 host-dir 参数,只能在创建或运行容器时指定挂载点

USER 指令

指定运行容器时的用户名或 UID ,当容器运行的服务不需要管理员权限时,可以先建立一个特定的用户和用户组,为它分配必要的权限,使用 USER 切换到这个用户

用法:

# 指定 用户名、组名
USER <user>[:<group>]

# 指定 用户ID、组ID
USER <UID>[:<GID>]

示例:

# 设置用户名 www
USER www

注意事项

# 在容器创建新用户
RUN net user /add www

# 设置用户
USER wwww

WORKDIR 指令

用法:

WORKDIR /path/to/workdir

示例:

WORKDIR 指令可以在 Dockerfile 中多次使用,如果提供了相对路径,它将相对于前一个 WORKDIR 指令的路径,为了避免出错,推荐 WORKDIR 指令中只使用绝对路径

# 指定工作目录为 /tmp 
WORKDIR /tmp
COPY test.txt .

# WORKDIR 指令,可以使用 ENV 设置的环境变量
ENV DIRPATH=/path
WORKDIR DIRNAME
RUN pwd # /path/$DIRNAME

ARG 指令

用于定义一个变量,用户可以在构建 Docker 镜像时,使用 --build-arg 定义变量,如下:

新建一个 Dockerfile 文件,使用 ARG 指令定义参数 username,这里没有赋予默认值:

dockerfile

FROM ubuntu
ARG param
RUN echo $param

使用构建命令,并传入变量参数:

shell

docker build --build-arg param=helloworld -t myimage:1.0.0.beta .

警告

不建议在构建时使用 ARG 指令传递秘钥、用户凭证等信息,因为构建时的信息任何用户都可以通过 docker history 看到 !!!

用法:

ARG <name>[=<default value>]

示例:

# 如果在 FROM 指令之前指定,那么只能用于 FROM 指令中
ARG VERSION=latest
FROM ubuntu:${VERSION}

# 多阶段构建时,FROM 指令之前定义的 ARG 变量,每个 FROM 都能用
ARG VERSION=latest
FROM ubuntu:${VERSION}
RUN echo 'ubuntu...'

FROM node:${VERSION}
RUN npm -v

提示

ARGENV 的区别:

预定义ARG

Docker 中有一组预定义的变量,无需 Dockerfile 中的 ARG 指令指定,在运行容器时通过 --build-arg 参数指定即可,而且这些预定义变量不会在 docker history 中记录。

预定义变量如下:

ONBUILD 指令

ONBUILD 是一个特殊的指令,它后面跟着的是其他指令,比如 RUNCOPY 等,而这些指令,在当前镜像构建时并不会被执行,只有当前镜像作为基础镜像,去构建下一级镜像的时候才会被执行。

Dockerfile 中的其它指令都是为了定制当前镜像而准备的,唯有 ONBUILD 是为了帮助别人定制自己而准备的

ONBUILD 工作原理

  1. 当遇到 ONBUILD 指令时,构建器会向正在构建的镜像的元数据添加触发器。没有ONBUILD指令时,不会影响当前构建。
  2. 构建结束时,所有触发器的列表存储在镜像清单中的 .Container.OnBuild 键中。可以使用命令 docker inspect 检查它们。
  3. 使用 FROM 指令将镜像用作新构建的基础镜像时,作为处理 FROM 指令的一部分,下游构建器将查找 ONBUILD 触发器,并按照注册的顺序执行它们。如果任何一个触发器失败,那么 FROM 指令将被中止,从而导致构建失败。如果所有触发器都成功,则 FROM 指令将完成,构建将照常继续。
  4. 触发器在执行后从当次构建的镜像中清除,换句话说,它们不是由“孙子”构建继承的。 :::

用法:

ONBUILD <INSTRUCTION>

示例:

# 当被作为基础镜像进行构建时,触发器执行 RUN 指令自动创建 /app/src 目录
ONBUILD RUN mkdir /app/src

提示

STOPSIGNAL 指令

设置停止容器所要发送的系统调用信号,所使用的信号必须是内核系统调用表中的合法的值,如:9SIGKILL

用法:

默认停止信号是 SIGTERM ,在 docker stop 的时候会给容器内 PID1 的进程发送这个 signal,通过 --stop-signal 可以设置自己需要的 signal,主要目的是为了让容器内的应用程序在接收到 signal

之后可以先做一些事情,实现容器的平滑退出,如果不做任何处理,容器将在一段时间之后强制退出,会造成业务的强制中断,这个时间默认是 10s

STOPSIGNAL signal

HEALTHCHECK 指令

用于健康检查,通过 HEALTHCHECK 指令告诉 Docker 如何测试容器以检查它是否仍在工作,当镜像指定了 HEALTHCHECK 指令后,启动容器时,初始状态会为 starting , 检查通过后会变为 healthy ,如果连续失败一定次数后,则会变为 unhealthy

选项参数:

OPTIONS 描述
--interval=DURATION 运行检查间隔时间,默认:30s
--timeout=DURATION 超时时间,默认:30s
--start-period=DURATION 初始化时间,默认:0s
--start-interval=DURATION 两次检查时间间隔,默认:5s
--retries=N 最大重试次数,默认:3

用法:

HEALTHCHECK 返回值:

dockerfile

# 通过容器内运行命令来检查容器运行状况
HEALTHCHECK [OPTIONS] CMD command

# 禁用从基础镜像继承的任何健康检查
HEALTHCHECK NONE

示例:

# 每隔 5min 检查一次,通过 curl 检查 Web 服务是否在正常工作
HEALTHCHECK --interval=5m --timeout=3s  CMD curl -f http://localhost/ || exit 1

SHELL 指令

用于设置执行命令所使用的默认 shell 类型

SHELL 指令必须在 Dockerfile 中以 JSON 格式编写,SHELL 指令可以出现多次,每条 SHELL 指令都会覆盖所有先前的 SHELL 指令,并影响所有后续指令。

用法:

SHELL ["executable","parameters"]

示例:

# Linux 下,指定 /bin/bash
SHELL ["/bin/bash","-c"]
RUN echo hello

# Windows 下,指定 Powershell
SHELL ["powershell","-command"]
RUN Write-Host hello

# Windows 下,指定 cmd 
SHELL ["cmd","/S","/C"]
RUN echo hello