Docker 命令 之 Dockerfile

摘要

Dockerfile 是什么?

  • Docker可以通过读取 Dockerfile 中的指令来自动构建镜像。

  • Dockerfile 是一个文本文档,其中包含用户可以在命令行上调用以组装镜像的所有指令。

  • 可以在Dockerfile中使用的指令:

指令 中文描述
ADD 将本地或远程的文件/目录添加到镜像中,支持自动解压 .tar 文件和使用 URL 下载远程资源。通常推荐使用 COPY,除非需要这些高级功能。
ARG 定义构建阶段使用的变量,可在 docker build 命令中通过 --build-arg 传入,变量仅在构建时有效,不会保留在最终镜像中。
CMD 指定容器默认执行的命令和参数,容器运行时若未指定命令,则使用该指令设置的命令。如果配置了多个CMD,则只有最后一个生效。和ENTRYPOINT共同使用时,作为传递给ENTRYPOINT的参数。可被 docker run 提供的命令覆盖。
COPY 将构建上下文中的文件或目录复制到镜像中。相比 ADD 更简单、安全,推荐优先使用。
ENTRYPOINT 指定容器启动时的主命令,不容易被 docker run 中的命令覆盖。可与 CMD 配合使用,用于提供默认参数。
ENV 设置环境变量,变量将在构建和容器运行时均可使用。例如:配置应用参数或系统路径等。
EXPOSE 声明容器运行时将开放的端口,仅用于文档说明或与容器编排工具配合,不会自动进行端口映射。
FROM 指定基础镜像,是 Dockerfile 的起点。也可以用于多阶段构建,通过多次使用 FROM 指令创建多个构建阶段。
HEALTHCHECK 定义容器健康检查命令,用于定期检测容器内部服务的健康状态,可结合容器编排系统实现故障自动恢复。
LABEL 为镜像添加键值对形式的元数据,例如版本、维护者、用途说明等,方便镜像管理与自动化处理。
MAINTAINER (已弃用)用于指定镜像维护者信息,推荐改用 LABEL 来设置作者信息。
ONBUILD 定义一个触发指令,当当前镜像作为基础镜像被其他 Dockerfile 使用时自动执行。常用于基础镜像的预设行为。
RUN 执行一条命令并提交结果作为新镜像层,常用于安装软件包、复制文件、设置权限等构建操作。
SHELL 更改 Dockerfile 中后续 RUNCMDENTRYPOINT 等指令的默认 shell(如使用 shpowershell)。
STOPSIGNAL 设置容器终止时发送的系统信号(如 SIGTERM),用于优雅关闭应用。
USER 设置执行后续命令时所使用的用户和用户组,增强容器的安全性,避免使用 root 权限。
VOLUME 定义容器内的挂载点,用于持久化数据或与宿主机/其他容器共享数据。运行容器时可指定挂载路径。
WORKDIR 设置工作目录,相当于执行 cd,用于简化后续命令中的路径。若目录不存在则自动创建。
  • 构建阶段指令:FROM, ARG, ENV, COPY, ADD, RUN, WORKDIR, LABEL, USER, SHELL, ONBUILD

  • 启动配置指令:CMD, ENTRYPOINT, HEALTHCHECK, EXPOSE, VOLUME, STOPSIGNAL

Dockerfile 指令介绍

FROM

  • FROM 指令用于指定基础镜像(base image),是每一个 Dockerfile 中必须的第一条指令。所有后续指令都是基于这个基础镜像构建的。

  • 语法

1
2
3
4
5
FROM <image>[:<tag>] [AS <stage-name>]
# 说明:
# <image>:镜像名称(可以是本地已有的,也可以是从 Docker Hub 或其他镜像仓库拉取的)
# [:<tag>]:镜像标签(可选,默认是 latest)
# [AS <stage-name>]:为该构建阶段指定名称,用于多阶段构建
  • 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 使用最新版本的镜像(默认标签是 latest)
FROM node
# 使用指定的标签
FROM node:16.13.2-alpine
# 使用私有镜像仓库
FROM private.registry.com/my-image:latest

# 使用多阶段构建
FROM golang:1.20 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp

FROM alpine:latest
COPY --from=builder /app/myapp /usr/local/bin/myapp
ENTRYPOINT ["myapp"]
## 这里用了两个阶段:
### builder 阶段用来编译应用;
### alpine 阶段用来打包最终镜像,只包含编译好的二进制文件,减少体积。

WORKDIR

  • WORKDIR 指定了工作目录,即后续所有指令(如 RUN、CMD、ENTRYPOINT、COPY、ADD 等)所运行的当前路径(working directory)。

  • 如果目录不存在,Docker 会自动创建它。

  • 每个 WORKDIR 都会创建一层镜像(layer),所以不要重复设置无意义的路径。

  • 语法

1
2
3
WORKDIR <path>
# 说明:
# <path>:要切换的工作目录,可以是绝对路径或相对路径。
  • 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
# 设置绝对路径
WORKDIR /app

# 连续设置多个工作目录(逐层嵌套)
WORKDIR /var
WORKDIR www
WORKDIR html
# 等同于:
WORKDIR /var/www/html

# 避免在 RUN cd some_dir 后继续执行依赖路径的命令,因为 Docker 每一条指令都是新的 shell 实例,cd 不会跨指令保留,应该改用 WORKDIR。
RUN cd some_dir # 错误
WORKDIR some_dir # 正确

ARG

  • ARG 用于在 构建镜像时(build-time)传入参数。这些参数只在 构建阶段有效,不会出现在最终镜像中,也不会在容器运行时被保留。

  • 语法

1
2
3
4
ARG <name>[=<default_value>]
# 说明:
# <name>:参数名称
# [=<default_value>]:参数的默认值,如果未传入参数,则使用默认值,如果为设置default_value,则构建镜像时必须传递参数,--build-arg
  • 示例

1
2
3
4
5
6
7
8
9
10
# 定义一个参数,在构建镜像时必须传入参数,--build-arg APP_ENV=development
ARG APP_ENV
# 带默认值的 ARG
ARG APP_ENV=development
# 使用参数
RUN echo "当前环境:${APP_ENV}"

# 与 FROM 一起用(从 Docker 17.05 起)
ARG BASE=ubuntu
FROM ${BASE}:22.04

ENV

  • ENV 指令用于在镜像构建过程中定义环境变量(Environment Variables)。这些变量可以在之后的构建步骤(比如 RUN、CMD 等)中使用,也会在容器运行时生效。

  • 语法

1
2
3
4
5
6
7
# 两种语法
ENV <key> <value>
ENV <key>=<value> ...
# 说明:
# <key>:环境变量的名称
# <value>:环境变量的值
# 如果定义多个变量,推荐使用 key=value 的形式。
  • 示例

1
2
3
4
5
6
7
8
9
10
11
12
# 定义一个环境变量
ENV APP_ENV=production
# 定义多个变量
ENV APP_PORT=8080 NODE_ENV=production
# 使用多行格式(提高可读性)
ENV PATH="/usr/local/bin:${PATH}" \
LANG="en_US.UTF-8" \
TZ="Asia/Shanghai"
# 也可以分开定义
ENV PATH="/usr/local/bin:$PATH"
ENV LANG="en_US.UTF-8"
ENV TZ="Asia/Shanghai"
  • 注意:ENV 指令定义的环境变量在构建阶段和运行阶段都会生效,但运行阶段会覆盖构建阶段定义的变量。

LABEL

  • LABEL 用于为镜像添加元数据标签,以 key=value 的形式存在。这些标签可以是作者信息、版本描述、用途说明、构建时间等。

  • 旧的 MAINTAINER 指令现在已被废弃,推荐使用 LABEL 来代替。

  • 每个 LABEL 都会创建一层镜像(layer),推荐一次设置多个。

  • 语法

1
2
3
4
LABEL <key>=<value> [<key>=<value>...]
# 说明:
# <key>:标签的键
# <value>:标签的值
  • 示例

1
2
3
4
5
6
7
8
# 添加一个标签
LABEL maintainer="yourname@example.com"
# 换行格式(推荐),以下标签符合 OCI(Open Container Initiative) 镜像规范
LABEL \
org.opencontainers.image.title="MyApp" \
org.opencontainers.image.description="演示项目" \
org.opencontainers.image.version="1.0.0" \
org.opencontainers.image.authors="zhangsan@example.com"

USER

  • USER 指令用于指定后续指令(如 RUN、CMD、ENTRYPOINT、COPY 等)以哪个用户身份来执行。

  • 默认情况下,Docker 容器中的命令以 root 用户运行,这虽然灵活但不安全。使用 USER 可以让我们切换到普通用户,从而提升容器的安全性,防止潜在的权限滥用。

  • 语法

1
2
3
4
USER <user>[:<group>]
# 说明:
# <user>:用户名或 UID
# [:<group>]:可选,用户组名或 GID
  • 示例

1
2
3
4
5
6
# 设置用户
USER appuser
# 设置用户组
USER appuser:appgroup
# 使用 UID 和 GID
USER 1000:1000
  • 如果基础镜像中没有你想要的用户,需要在 Dockerfile 中手动创建

1
2
3
4
# 创建用户和组
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
# 切换用户
USER appuser

ADD

  • ADD 用于将本地文件或目录、远程文件(URL) 或 压缩包 复制到镜像中的指定位置。

  • 它的功能类似于 COPY,但比 COPY 多几个功能(解压、拉取远程文件等)。

  • 语法

1
2
3
4
ADD <src>... <dest>
# 说明:
# <src>:要复制的文件或目录,可以是本地文件、远程 URL、压缩包等
# <dest>:目标路径
  • 示例

1
2
3
4
5
6
7
8
9
10
# 从本地文件复制
ADD app.jar /app.jar
# 复制多个
ADD app1.jar app2.jar /app/
# 通配符
ADD static-assets/*.html /app/public/
# 从远程 URL 复制
ADD https://example.com/app.jar /app.jar
# 从压缩包中复制
ADD static-assets.tar.gz /app/public/
  • 最佳实践是优先使用 COPY,只有在需要 ADD 的额外功能时才使用它。

COPY

  • COPY 指令用于将主机上的文件或目录复制到镜像的文件系统中。它是构建镜像过程中最常用的数据引入方式之一。

  • 与 ADD 类似,但功能更简单、明确、安全

  • 推荐优先使用 COPY,除非你确实需要 ADD 提供的自动解压或远程下载功能。

  • 语法

1
2
3
4
COPY <src>... <dest>
# 说明:
# <src>:要复制的文件或目录
# <dest>:目标路径
  • 示例

1
2
3
4
5
6
7
8
9
10
# 复制单个文件
COPY app.jar /app.jar
# 复制多个文件
COPY app1.jar app2.jar /app/
# 复制目录
COPY static-assets/ /app/public/
# 通配符
COPY static-assets/*.html /app/public/
# 设置目标文件属主属组
COPY --chown=appuser:appgroup app.jar /app.jar

RUN

  • RUN 指令用于在镜像构建阶段执行命令,结果会被打包进镜像层中。

  • 它可以用于安装依赖、编译代码、运行命令等。

  • 每一条 RUN 指令会创建一层镜像(layer),合并多个命令成一条 RUN,可以减少镜像层数(例如使用 && 串联)。

  • 语法

1
2
3
4
5
# 第一种格式:实际运行的是:/bin/sh -c "<命令字符串>"
RUN <命令字符串>

# 第二种格式
RUN ["可执行文件", "参数1", "参数2", ...]
  • 示例

1
2
3
4
5
6
7
8
9
10
# 命令字符串
RUN apt-get update && apt-get install -y curl
# 构建多个命令(用 && 串联)
RUN apt-get update && \
apt-get install -y python3 && \
rm -rf /var/lib/apt/lists/*


# 可执行文件参数
RUN ["/bin/sh", "-c", "curl https://example.com/app.jar > app.jar"]

CMD

  • CMD 用于指定容器启动时默认执行的命令及其参数。

  • 如果用户在运行容器时没有手动指定其他命令,Docker 就会使用 CMD 提供的内容。

  • 它可以定义多个,但只有最后一个会被使用。

  • 语法

1
2
3
4
5
6
7
# Shell 形式(字符串)
CMD command param1 param2
# 等价于 /bin/sh -c "command param1 param2"


# Exec 形式(数组)
CMD ["executable", "param1", "param2", ...]
  • 示例

1
2
3
4
5
6
7
8
9
# 简单 shell 命令
CMD echo "Hello from container"

# Exec 形式
CMD ["npm", "start"]

# 作为 ENTRYPOINT 的参数
ENTRYPOINT ["python3", "app.py"]
CMD ["--host=0.0.0.0", "--port=8080"]
  • 用户可以覆盖 CMD:

1
docker run <myapp> npm run test

ENTRYPOINT

  • ENTRYPOINT 定义容器启动时执行的主命令,相比 CMD,它不容易被覆盖,更适合制作“专用型”容器(如 nginx、python 脚本等)。

  • 你可以把 ENTRYPOINT 理解为容器的“主程序”,而 CMD 是为它提供的默认“命令行参数”。

  • 语法

1
2
3
4
5
6
# Shell 形式(字符串)
ENTRYPOINT command param1 param2
# 等价于 /bin/sh -c "command param1 param2"

# Exec 形式(数组)
ENTRYPOINT ["executable", "param1", "param2", ...]
  • 示例

1
2
3
4
5
6
7
8
9
# 简单 shell 命令
ENTRYPOINT echo "Hello from container"

# Exec 形式
ENTRYPOINT ["npm", "start"]

# 结合 CMD 使用
ENTRYPOINT ["python3", "app.py"]
CMD ["--host=0.0.0.0", "--port=8080"]

EXPOSE

  • EXPOSE 用于声明容器将会监听的端口,让使用该镜像的人知道应该对外开放哪些端口。

  • ⚠️ 注意:EXPOSE 并不会真的开放端口,只是“声明”这个容器监听了这些端口。

  • 要让端口真正暴露出来,还需要在运行容器时加上 -p--publish 参数。

  • 语法

1
2
3
4
EXPOSE <port> [<port>/<protocol>...]
# 说明:
# <port>:端口号,可以是单个端口号,也可以是范围(如 8080-8085)
# <protocol>:协议,可以是 tcp 或 udp,默认为 tcp
  • 示例

1
2
3
4
EXPOSE 8080
EXPOSE 8080/udp
EXPOSE 8080-8085
EXPOSE 8080 8081 8082 8083 8084 8085

VOLUME

  • VOLUME 指令用于声明一个或多个容器中的挂载点(mount point),用于持久化数据或与宿主机/其他容器共享数据。

  • 语法:

1
2
3
VOLUME ["/path/in/container", ...]
# 路径必须是容器内部的绝对路径
# 可以一次声明一个,也可以是多个。
  • 示例

1
2
3
4
# 声明一个挂载点
VOLUME /app/public
# 声明多个挂载点
VOLUME ["/app/public", "/app/logs"]
  • 当容器运行时,可以将镜像中声明的挂载点映射到宿主机上,从而实现持久化数据。

1
docker run -v /host/path:/app/public myimage
  • 如果你没有手动绑定挂载,Docker 会自动创建一个匿名卷,卷的内容默认保存在宿主机的 /var/lib/docker/volumes 下。

HEALTHCHECK

  • HEALTHCHECK 用来定义容器运行时的健康检查命令,定期检测容器内服务的状态,帮助编排工具(Docker Swarm、Kubernetes 等)判断容器是否健康。

  • 如果健康检查失败,Docker 会将容器标记为 unhealthy,便于自动重启或替换。

  • 语法

1
2
3
4
5
6
7
8
HEALTHCHECK <options> CMD <command>
# 说明:
# <options>:可选项,用于设置健康检查的选项,如超时时间、重试次数等。
# <command>:健康检查命令,可以是任何有效的 shell 命令。
# 必须返回退出码:
# 0 表示健康
# 1 表示不健康
# 2 表示未知
  • 可选参数(OPTIONS)

参数 说明 默认值
--interval=DURATION 两次健康检查之间的时间间隔 30s
--timeout=DURATION 单次检测命令的超时时间 30s
--start-period=DURATION 容器启动后,开始健康检查前的等待时间 0s
--retries=N 连续失败几次后判定容器不健康 3
  • 示例

1
2
# 使用 curl 检测 Web 服务是否响应
HEALTHCHECK --interval=5s --timeout=3s --retries=3 CMD curl -f http://localhost:8080/health || exit 1
  • 运行容器后,可以用命令查看健康状态

1
2
3
4
5
6
7
8
9
# 如果镜像中有健康检查,可以查看容器状态(STATUS)
docker ps
# STATUS 列会显示:
# healthy
# unhealthy
# starting(启动中)

# 查看更详细的信息
docker inspect --format='{{json .State.Health}}' <container-id>

SHELL

  • SHELL 指令用来自定义后续 RUN、CMD 和 ENTRYPOINT 指令所使用的默认 shell 程序和参数。

  • 默认情况下:

    • 在 Linux 镜像中,Docker 使用 /bin/sh -c
    • 在 Windows 镜像中,使用 cmd /S /C
  • 使用 SHELL,你可以替换为其他 shell,如 Bash、PowerShell、zsh 等。

  • 语法

1
SHELL ["executable", "param1", "param2", ...]
  • 示例

1
2
3
4
5
6
7
8
9
10
11
12
# 切换到 bash
SHELL ["bash", "-c"]

# 多次切换
SHELL ["/bin/bash", "-c"]
RUN echo "Hello from Bash" # 在 Bash 中执行
SHELL ["/bin/sh", "-c"]
RUN echo "Now back to sh" # 在 sh 中执行


# Windows
SHELL ["powershell", "-Command"]

STOPSIGNAL

  • STOPSIGNAL 指定当容器收到 docker stop 命令时,发送给容器主进程的信号类型。

  • 默认情况下,Docker 会向容器的主进程发送 SIGTERM 信号,让它有机会优雅地退出(在超时时未退出则发 SIGKILL 强制终止)。

  • 默认情况,大多数程序(如 nginx),不需要设置(默认 SIGTERM)

  • 但有些程序可能需要使用不同的信号,比如 SIGINT、SIGHUP,这时你可以通过 STOPSIGNAL 来修改。

  • 语法

1
2
3
4
5
STOPSIGNAL <signal>
# 说明:
# 其中 <signal> 可以是:
# 信号名称,例如:SIGTERM、SIGKILL、SIGINT、SIGHUP
# 或信号编号,例如:15(等价于 SIGTERM)
  • 示例

1
2
3
4
5
# 这是默认行为,不写也一样。
STOPSIGNAL SIGTERM

# 修改为 SIGINT
STOPSIGNAL SIGINT

ONBUILD

  • ONBUILD 用于定义延迟执行的构建指令,即这些命令不会在当前 Dockerfile 构建时执行,而是在 以当前镜像为基础的子镜像中构建时触发执行。

  • 它的典型用途是:构建一个“通用基础镜像”,让使用者在自己的 Dockerfile 中 FROM 它时自动继承一些操作(比如 COPY、RUN 等)。

  • ONBUILD 是一种设计模式,方便基础镜像作者预先定义“未来子镜像构建时一定要执行的步骤”,而不是在基础镜像中“硬编码”那些步骤。

  • 语法

1
2
3
ONBUILD <INSTRUCTION>
# 说明:
# <INSTRUCTION>:必须是一个合法的 Dockerfile 指令,如 RUN、COPY、ADD、CMD 等(但不能是 FROM, ONBUILD, HEALTHCHECK 等)。
  • 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 基础镜像中使用 ONBUILD
# 文件:Dockerfile.base
FROM node:18
WORKDIR /app
ONBUILD COPY . /app
ONBUILD RUN npm install

# 构建基础镜像
docker build -t my-node-base -f Dockerfile.base .

# 使用基础镜像构建子镜像
# 文件:Dockerfile(子镜像)
FROM my-node-base
CMD ["node", "index.js"]
# 构建子镜像时,等于自动插入了:
COPY . /app
RUN npm install
  • ONBUILD 是一种“构建钩子”机制

    • ONBUILD 可以理解成“钩子”或“触发器”:
      • 在基础镜像构建时不执行
      • 但当某人以这个基础镜像为起点写自己的 Dockerfile,并构建时,这些 ONBUILD 里的指令自动插入执行
    • 这样:
      • 基础镜像只负责定义环境(node、npm版本、系统依赖等),保持轻量
      • 下游项目可以不用写重复的代码复制和安装指令,自动继承基础镜像预定义的构建步骤
      • 代码复制和依赖安装在下游镜像构建时执行,使用自己的上下文(也就是项目代码)
  • ONBUILD 总结

特性 说明
⏱ 延迟执行 构建基础镜像时不会执行,在子镜像构建时触发
✅ 支持指令 例如 RUN, COPY, ADD, CMD, WORKDIR, ENV
❌ 不支持 FROM, ONBUILD, HEALTHCHECK, SHELL, STOPSIGNAL
👎 不推荐滥用 会隐藏构建行为,降低可维护性
✅ 推荐场景 团队共享模板、构建“标准开发镜像”

Dockerfile 示例: Spring Boot 应用

  • 目录结构

1
2
3
4
5
6
7
my-springboot-app/
├── Dockerfile
├── target/
│ └── app.jar
├── static-assets.tar.gz
└── ...

  • Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 使用轻量级 Alpine 版本的 OpenJDK 17 官方镜像,适合部署 Spring Boot 应用
FROM openjdk:17-alpine

# 设置构建时变量,默认使用构建好的 jar 文件
ARG JAR_FILE=target/app.jar

# 设置运行时环境变量
ENV JAVA_OPTS="-Xms4096M -Xmx4096M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M"
ENV TZ=Asia/Shanghai

# 镜像元信息
LABEL maintainer="yourname@example.com"
LABEL version="1.0.0"
LABEL description="用于部署 Spring Boot 应用的生产级镜像"

# 设置工作目录
WORKDIR /app

# 示例 RUN:安装 curl(用于容器健康检查或调试)
RUN apk add --no-cache curl

# 复制 Spring Boot 构建生成的 jar 包
COPY ${JAR_FILE} app.jar

# 解压静态资源到容器中(ADD 可以自动解压 tar.gz)
ADD static-assets.tar.gz /app/public/

# 声明暴露的应用端口(Spring Boot 默认是 8080)
EXPOSE 8080

# 容器健康检查:访问 actuator 健康端点
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1

# 容器终止时优雅关闭(Java 推荐 SIGTERM)
STOPSIGNAL SIGTERM

# 设置容器启动时的主命令(ENTRYPOINT 不会被 docker run 参数覆盖)
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

# CMD 提供默认的运行参数,可以被 docker run 覆盖
# 这里通过 Spring Boot 参数设置启动环境和端口号
CMD ["--spring.profiles.active=app", "--server.port=8080"]
# 这里使用的是 ENTRYPOINT + CMD 的混合模式
# 完整命令: java $JAVA_OPTS -jar app.jar --spring.profiles.active=app --server.port=8080

# 声明一个卷挂载点,运行容器时可将该路径映射到宿主机,实现数据持久化
VOLUME ["/app/logs"]
  • 构建镜像

1
2
# 构建镜像时用 --build-arg 指定构建参数,如果需要多个,就配置多个 --build-arg
docker build --build-arg JAR_FILE=target/app.jar -t my-springboot-app:latest .
  • 运行容器

1
2
3
4
5
docker run -d --name my-springboot-app \
-p 8080:8080 \ # 映射容器端口到主机端口
-e JAVA_OPTS="-Xms512m -Xmx1024m" \ # 设置环境变量
-v /path/to/logs:/app/logs \ # 挂载卷到宿主目录
my-springboot-app:latest