介绍

Docker 可以通过读取 Dockerfile 中的指令来自动构建镜像。Dockerfile 是一个文本文档,其中包含用户可以在命令行上调用来组装镜像的所有命令。

参考来源:https://docs.docker.com/reference/dockerfile/

这是图片

常用指令

ARG

ARG-定义

定义一个变量,用户可以在构建时docker build使用--build-arg <varname>=<value>标志的命令将该变量传递给构建器。可以包含默认值,如:ARG user1=someuser,在构建时没有传递user1的值,则构建器将使用默认值someuser

ARG-语法

ARG <name>[=<default value>] [<name>[=<default value>]...]

ARG-作用域

ARG指令定义的变量仅在构建阶段可用,不会保留在最终镜像中

ARG-使用位置

  • 可以在FROM指令之前使用,但这种情况下只能被FROM指令使用
  • FROM之后的ARG在整个构建阶段都可用

ARG-预定义ARG

Docker 预定义了一些 ARG 变量,无需声明即可使用:

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

ARG-示例:

1
2
3
4
5
6
7
8
9
10
# 在 FROM 之前定义
ARG VERSION=latest
FROM busybox:$VERSION

# 多个 ARG 定义
ARG USER=john
ARG BUILD_DATE

# 在构建时传递值
# docker build --build-arg USER=peter --build-arg BUILD_DATE=2024-02-22 .

ARG-最佳实践

  • 将频繁变化的 ARG 放在 Dockerfile 的后面,以充分利用构建缓存
  • 不要在 ARG 中存储敏感信息,因为它们可以通过 docker history 命令查看
  • 如果需要在运行时使用变量,应该使用 ENV 指令

FROM

FROM-定义

指定基础镜像,用于创建一个新的构建阶段。这是构建Docker镜像时的起点,通常是Dockerfile中的第一条指令。

FROM-语法

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

FROM-参数说明

  • --platform:指定目标平台(例如:linux/amd64, linux/arm64)
  • image:基础镜像名称
  • tag:镜像标签,默认为latest
  • digest:镜像的摘要值,用于指定确切的镜像版本
  • AS name:为构建阶段命名,用于多阶段构建

FROM-特点

  • 一个Dockerfile可以包含多个FROM指令,用于创建多阶段构建
  • 每个FROM指令都会清除之前阶段创建的任何状态
  • ARGcommentsparser directives是可以在FROM之前使用的指令

FROM-示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 基本用法
FROM ubuntu:20.04

# 指定平台
FROM --platform=linux/arm64 ubuntu:20.04

# 使用镜像摘要
FROM ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2

# 多阶段构建
FROM golang:1.17 AS builder
# ... 构建阶段的指令 ...

FROM alpine:3.14
COPY --from=builder /go/bin/app /usr/local/bin/app

FROM-最佳实践

  • 使用官方镜像作为基础镜像
  • 优先使用具体的标签而不是latest
  • 考虑使用轻量级基础镜像(如alpine)
  • 在多阶段构建中使用AS关键字来优化最终镜像大小

WORKDIR

WORKDIR-定义

设置工作目录,用于后续的 Dockerfile 指令(如 RUNCMDENTRYPOINTCOPYADD)的执行环境,默认为/

WORKDIR-语法

WORKDIR /path/to/workdir

WORKDIR-特点

  • 如果指定的目录不存在,Docker 会自动创建该目录且创建一个新的层,反之不会
  • 可以使用多个 WORKDIR 指令,每个指令都相对于前一个路径
  • 支持使用环境变量,如:WORKDIR $HOME/app
  • 可以使用绝对路径或相对路径

WORKDIR-示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 基本用法
WORKDIR /app

# 使用环境变量
ENV APP_DIR=/application
WORKDIR $APP_DIR

# 多个 WORKDIR 指令
WORKDIR /app
WORKDIR src
WORKDIR test
# 最终工作目录为 /app/src/test

# 相对路径用法
WORKDIR /app
WORKDIR dist
RUN pwd # 输出: /app/dist

WORKDIR-最佳实践

  • 使用绝对路径而不是相对路径,以避免混淆
  • 避免使用 RUN cd /path 命令,应该使用 WORKDIR 来改变目录
  • 为了可维护性,保持工作目录结构清晰
  • 在多阶段构建中,每个阶段都应该明确设置 WORKDIR
  • 确保目录名称符合项目的命名规范

COPY

COPY-定义

将文件或目录从构建上下文复制到镜像中。COPY指令会在复制文件时保留文件的所有元数据(如文件权限、创建时间等)。

COPY-语法

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

COPY-参数说明

  • --chown:指定复制到容器内的文件的所有者和组
  • --chmod:指定复制到容器内的文件的权限
  • --link:创建硬链接而不是复制文件(可以提高构建效率)
  • <src>:源文件或目录,支持通配符
  • <dest>:目标路径

COPY-特点

  • 只能复制构建上下文中的文件
  • 如果指定多个源文件,目标必须是目录(以/结尾)
  • 会保留文件的元数据(权限、时间戳等)
  • 支持Go的filepath.Match规则的通配符

COPY-示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 基本用法
COPY app.js /app/

# 复制多个文件
COPY package.json package-lock.json /app/

# 使用通配符
COPY *.json /app/

# 设置权限和所有者
COPY --chown=node:node --chmod=755 app.js /app/

# 使用JSON数组语法
COPY ["app.js", "/app/"]

# 复制目录
COPY src/ /app/src/

COPY-最佳实践

  • 优先使用COPY而不是ADD,除非需要ADD的特殊功能
  • 将不常变化的文件先复制,以利用Docker的缓存机制
  • 明确指定文件权限和所有者,确保安全性
  • 使用.dockerignore排除不需要的文件
  • 在多阶段构建中使用COPY --from来复制文件

COPY-ADD主要区别

  1. 功能范围

    • COPY:仅支持从构建上下文复制文件/目录到镜像中
    • ADD:支持额外的特性:
      • 自动解压tar文件(gzipbzip2xz等格式)
      • 支持从URL下载文件
  2. 使用推荐

    • 如果不需要自动解压或从URL获取文件,优先使用COPY
    • COPY的行为更可预测,更直观
    • 需要解压文件时,推荐使用RUN命令配合tar,而不是依赖ADD
    • 需要下载文件时,推荐使用RUN命令配合wget/curl,而不是ADD
  3. 示例对比

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 使用 COPY 的推荐方式
    COPY app.js /app/
    COPY package.json /app/

    # 不推荐使用 ADD 下载文件
    ADD http://example.com/file.txt /app/
    # 推荐使用 RUN 配合 curl
    RUN curl -O http://example.com/file.txt

    # 不推荐使用 ADD 解压文件
    ADD app.tar.gz /app/
    # 推荐使用 RUN 配合 tar
    COPY app.tar.gz /tmp/
    RUN tar -xzf /tmp/app.tar.gz -C /app/ && rm /tmp/app.tar.gz

ADD

ADD-定义

将文件、目录或远程URL从源位置复制到镜像的目标路径中。相比COPY指令,ADD具有额外的功能,如自动解压tar文件和支持远程URL。

ADD-语法

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

ADD-参数说明

  • --chown:指定添加到容器内的文件的所有者和组
  • --chmod:指定添加到容器内的文件的权限
  • --checksum:验证远程资源
  • <src>:源文件、目录或URL
  • <dest>:目标路径

ADD-特点

  • 可以从远程URL获取资源
  • 自动解压本地tar文件(格式为:gzip、bzip2、xz)
  • 支持Go的filepath.Match规则的通配符
  • 源路径必须在构建上下文内(URL除外)
  • 如果源是URL且目标没有以斜杠结尾,则文件将被下载并复制到目标
  • 如果源是本地tar归档且目标没有以斜杠结尾,则归档将被解压到目标路径

ADD-示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 基本用法
ADD app.tar.gz /app/

# 从URL添加文件
ADD https://example.com/file.txt /app/

# 设置权限和所有者
ADD --chown=node:node --chmod=755 app.tar.gz /app/

# 使用校验和验证远程文件
ADD --checksum=sha256:24454f830cdb571e2c4ad15481119c43c4d962127862629d0e9fe6740d66c4bc https://example.com/file.txt /app/

# 使用通配符
ADD config* /app/config/

# 使用JSON数组语法
ADD ["app.tar.gz", "/app/"]

ADD-最佳实践

  • 仅在需要自动解压tar文件或从URL获取资源时使用ADD
  • 对于简单的文件复制操作,优先使用COPY指令
  • 使用--checksum参数验证远程资源的完整性
  • 避免使用ADD下载并解压远程tar文件,建议分别使用RUN wget/curltar命令
  • 当使用远程URL时,确保使用HTTPS协议以确保安全性
  • 注意URL资源会被缓存,如需更新需要使用不同的URL

RUN

RUN-定义

在构建镜像时执行命令并创建新的镜像层。是最常用的 Dockerfile 指令之一,用于安装包、创建文件和目录、创建链接等。

RUN-语法

RUN [OPTIONS] <command>(Shell 格式)
RUN [OPTIONS] ["executable", "param1", "param2"](Exec 格式)

RUN-参数说明

  • --network:指定运行命令时的网络模式
    • default:在默认网络中运行
    • none:在无网络环境中运行
    • host:使用主机网络运行
  • --mount:在执行命令时挂载额外的文件系统
  • --security:设置安全选项

RUN-特点

  • 每个 RUN 指令都会创建一个新的镜像层
  • Shell 格式默认在 Linux 上使用 /bin/sh -c,在 Windows 上使用 cmd /S /C
  • Exec 格式直接调用命令,不会启动 shell 解释器
  • 支持使用 \ 将长命令分成多行
  • 可以使用环境变量

RUN-示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Shell 格式基本用法
RUN apt-get update && apt-get install -y \
package1 \
package2 \
package3

# Exec 格式
RUN ["apt-get", "install", "-y", "nginx"]

# 使用 --network 参数
RUN --network=none pip install --no-cache-dir .

# 使用环境变量
RUN echo "Building version: $VERSION"

# 多行命令示例
RUN set -ex \
&& apt-get update \
&& apt-get install -y \
curl \
nginx \
&& rm -rf /var/lib/apt/lists/* \
&& nginx -v

RUN-最佳实践

  • 将多个相关命令合并到一个 RUN 指令中,使用 && 连接,减少镜像层数
  • 及时清理不需要的文件,减小镜像大小
  • 使用 --no-cache 等选项避免缓存文件
  • 优先使用 Shell 格式,除非需要特定 shell 或无 shell 环境
  • 在包管理器命令中使用 -y 标志实现自动确认
  • 记得在安装包后清理包管理器缓存
  • 考虑使用 set -e 确保命令失败时构建失败

RUN-CMD主要区别

  1. 执行时机

    • RUN:在构建镜像时执行命令
    • CMD:在容器启动时执行命令
  2. 使用次数

    • RUN:可以有多个,每个都会创建新的镜像层
    • CMD:只能有一个有效命令,多个时只有最后一个生效
  3. 指令目的

    • RUN:用于安装软件包、创建文件等构建镜像所需的操作
    • CMD:为容器提供默认的启动命令或参数
  4. 命令覆盖

    • RUN:不能被覆盖,是镜像的固定组成部分
    • CMD:可以被docker run的命令行参数覆盖
  5. 示例对比

    1
    2
    3
    4
    5
    # RUN 示例 - 安装软件包
    RUN apt-get update && apt-get install -y nginx

    # CMD 示例 - 设置容器启动命令
    CMD ["nginx", "-g", "daemon off;"]

CMD

CMD-定义

指定容器启动时要运行的默认命令。每个Dockerfile只能有一个CMD指令,如果指定了多个,只有最后一个生效。

CMD-语法

CMD ["executable", "param1", "param2"](exec 格式,推荐)
CMD ["param1", "param2"](作为 ENTRYPOINT 的默认参数)
CMD command param1 param2(shell 格式)

CMD-特点

  • 容器启动时的默认命令
  • 可以被docker run命令行参数覆盖
  • 如果与ENTRYPOINT一起使用,则CMD指定的参数会作为ENTRYPOINT的默认参数
  • exec格式使用JSON数组,可以正确处理Unix信号
  • shell格式会以/bin/sh -c的方式执行命令

CMD-示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# exec 格式(推荐)
CMD ["nginx", "-g", "daemon off;"]

# shell 格式
CMD nginx -g "daemon off;"

# 作为 ENTRYPOINT 的默认参数
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]

# 运行Python应用
CMD ["python", "app.py"]

# 启动Web服务器
CMD ["npm", "start"]

CMD-最佳实践

  • 优先使用exec格式而不是shell格式
  • 为长期运行的程序(如服务器)提供合适的默认命令
  • 确保前台运行主进程(避免使用systemctlservice等后台启动命令)
  • 如果需要运行shell脚本,使用exec格式并显式调用shell
  • 当与ENTRYPOINT配合使用时,两者都应使用exec格式
  • 在开发环境中可以提供调试友好的默认命令

EXPOSE

EXPOSE-定义

声明容器运行时监听的端口。这个指令只是声明容器打算使用的端口,实际上并不会自动打开这些端口。

EXPOSE-语法

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

EXPOSE-特点

  • 默认协议为TCP,可以显式指定为UDP
  • 可以同时指定多个端口
  • 可以为同一个端口指定多个协议
  • 仅作为文档说明使用,不会实际映射端口
  • 实际端口映射需要在docker run时使用-p-P参数,-p <host_port>:<container_port> 将容器端口映射到指定主机端口,-P 将所有 EXPOSE 的端口自动映射到主机的随机端口

EXPOSE-示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 基本用法
EXPOSE 80

# 指定多个端口
EXPOSE 80 443

# 指定协议
EXPOSE 53/tcp 53/udp

# 同时暴露 TCP 和 UDP
EXPOSE 80/tcp 80/udp

# 在运行时使用这些端口
# docker run -p 80:80 -p 443:443 my-image
# 或使用 -P 自动映射
# docker run -P my-image

EXPOSE-最佳实践

  • 为应用程序使用的所有端口添加EXPOSE指令
  • 使用标准端口号(如HTTP80HTTPS443
  • 在文档中清晰说明暴露的端口用途
  • 考虑安全性,只暴露必要的端口
  • 在使用-P时注意端口随机映射可能带来的影响
  • 在生产环境中优先使用显式端口映射(-p)而不是自动映射(-P

ENV

ENV-定义

设置环境变量,这些变量在容器运行时可用,并且可以被后续的Dockerfile指令使用。与ARG不同,ENV设置的变量会持久化到最终的镜像中。

ENV-语法

ENV <key>=<value> ...
ENV <key> <value>

ENV-特点

  • 在构建过程中和容器运行时都可用
  • 可以在一条指令中设置多个环境变量
  • 值中包含空格时需要使用引号
  • 可以被运行时通过docker run --env-e标志覆盖
  • 环境变量在镜像中持久化

ENV-示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 基本用法
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

# 设置路径变量
ENV PATH=/usr/local/nginx/bin:$PATH

# 设置应用配置
ENV APP_ENV=production \
APP_DEBUG=false \
APP_PORT=8080

# 设置代理
ENV http_proxy=http://proxy.example.com:8080 \
https_proxy=http://proxy.example.com:8080

ENV-最佳实践

  • 使用有意义的变量名,遵循命名规范
  • 对于敏感信息,考虑使用构建时ARG或运行时secrets
  • 相关的环境变量应该组合在一起设置
  • 为环境变量提供合理的默认值
  • 在生产环境中谨慎使用环境变量覆盖
  • 使用环境变量来配置应用行为,而不是硬编码值

VOLUME

VOLUME-定义

创建一个挂载点,用于持久化数据或共享数据。这个指令告诉Docker容器运行时应该将指定的路径当作外部挂载的卷。

VOLUME-语法

VOLUME ["/path/to/dir"](JSON数组格式)
VOLUME /path/to/dir(普通格式)

VOLUME-特点

  • 容器启动时会自动创建指定的挂载点
  • 支持指定多个挂载点
  • 挂载点的数据在容器外部持久化
  • 多个容器可以共享挂载点
  • 如果挂载点目录已经包含数据,这些数据会被复制到卷中

VOLUME-示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 基本用法
VOLUME /data

# JSON数组格式
VOLUME ["/data"]

# 多个挂载点
VOLUME ["/data", "/var/log", "/var/db"]

# 常见用例
VOLUME /var/lib/mysql
VOLUME /var/log/nginx
VOLUME /app/data

# 在运行时使用这些卷
# docker run -v host_path:/data my_image

VOLUME-最佳实践

  • 用于存储数据库文件、日志文件等需要持久化的数据,不强制要求在Dockerfile中指定,主要是给镜像使用者一个提示,表明这些目录可能会保存重要数据
  • 避免在挂载点中存储临时数据
  • 使用有意义的目录路径名称
  • 考虑使用命名卷而不是匿名卷
  • 在生产环境中注意数据备份策略
  • 注意卷的权限设置,确保容器进程有适当的访问权限

LABEL

LABEL-定义

为镜像添加元数据,以键值对的形式存储镜像的额外信息,如维护者信息、版本信息、描述等。这些标签可以帮助组织和管理镜像。

LABEL-语法

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

LABEL-特点

  • 一个镜像可以有多个标签
  • 可以在一条指令中指定多个标签
  • 标签值中如包含空格,需要使用引号
  • 可以使用环境变量
  • 可以通过docker inspect命令查看镜像的标签

LABEL-示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 基本用法
LABEL version="1.0"

# 多个标签
LABEL maintainer="John Doe <john@example.com>" \
version="1.0" \
description="This is my application"

# 使用引号处理空格
LABEL description="This is a long description for my application"

# 常用标签组合
LABEL org.opencontainers.image.title="MyApp" \
org.opencontainers.image.description="My application description" \
org.opencontainers.image.version="1.0.0" \
org.opencontainers.image.authors="John Doe" \
org.opencontainers.image.vendor="MyCompany" \
org.opencontainers.image.created="2024-02-22"

LABEL-最佳实践

  • 使用有意义的标签名和值
  • 遵循标准命名约定,如使用 org.opencontainers.image.* 前缀
  • 在一条LABEL指令中组合相关的标签
  • 保持标签信息的更新
  • 不要在标签中存储敏感信息
  • 使用标签来记录版本、构建日期等重要信息

USER

USER-定义

指定运行容器时使用的用户名或UID,以及可选的用户组或GID。这个指令会影响后续的RUNCMDENTRYPOINT指令。

USER-语法

USER <user>[:<group>]
USER <UID>[:<GID>]

USER-特点

  • 影响后续指令的执行环境
  • 可以使用用户名或UID
  • 可以可选地指定组名或GID
  • 如果用户不存在,需要先创建
  • 容器运行时的默认用户是root

USER-示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 使用用户名
USER nginx

# 使用 UID
USER 1000

# 指定用户和组
USER nginx:nginx

# 使用 UID:GID
USER 1000:1000

# 创建用户并切换
RUN useradd -r -u 1001 appuser
USER appuser

# 在多阶段构建中使用
FROM node:alpine AS builder
USER node
# ... 构建阶段的指令 ...

FROM node:alpine
USER node
COPY --from=builder --chown=node:node /home/node/app /home/node/app

USER-最佳实践

  • 避免使用root用户运行应用
  • 为应用创建专门的用户
  • 确保用户具有必要的权限
  • 在切换用户前创建必要的目录和文件
  • 使用数字ID可以避免用户名冲突
  • 考虑使用非特权用户来提高安全性

USER-安全考虑

  • 不要使用root用户运行应用程序
  • 为用户分配最小必要权限
  • 在生产环境中使用非特权用户
  • 注意文件权限和所有权
  • 考虑使用多阶段构建来管理权限

HEALTHCHECK

HEALTHCHECK-定义

用于告诉Docker如何测试容器是否仍在工作。可以检测容器内运行的应用程序是否正常工作,而不是仅仅检查容器是否在运行。

HEALTHCHECK-语法

HEALTHCHECK [OPTIONS] CMD command(执行命令检查健康状态)
HEALTHCHECK NONE(禁用从基础镜像继承的健康检查)

HEALTHCHECK-参数说明

  • --interval=DURATION:两次健康检查的间隔,默认为30s
  • --timeout=DURATION:健康检查命令运行超时时间,默认为30s
  • --start-period=DURATION:容器启动的初始化时间,默认为0s
  • --start-interval=DURATION:容器启动后,等待健康检查的时间,默认为0s
  • --retries=N:当检查失败时,重试的次数,默认为3次

HEALTHCHECK-特点

  • 容器可以处于以下健康状态:
    • starting:启动状态,正在等待首次健康检查
    • healthy:健康状态,最近的健康检查通过
    • unhealthy:不健康状态,最近的健康检查失败
  • 健康检查命令的退出码决定容器的健康状态:
    • 0:成功,容器健康
    • 1:不健康,容器有问题
    • 2:保留,不要使用这个退出码
  • Dockerfile 中只能有一个HEALTHCHECK指令,如果列出多个指令,则只有最后一个指令HEALTHCHECK会生效

HEALTHCHECK-示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 基本用法
HEALTHCHECK CMD curl -f http://localhost/ || exit 1

# 使用选项
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost/ || exit 1

# 禁用健康检查
HEALTHCHECK NONE

# 检查Web服务
HEALTHCHECK --interval=1m --timeout=3s \
CMD wget --quiet --tries=1 --spider http://localhost:8080/health || exit 1

# 使用shell脚本进行复杂检查
HEALTHCHECK --interval=5m --timeout=3s \
CMD /healthcheck.sh

# 检查Redis服务
HEALTHCHECK --interval=5s --timeout=3s \
CMD redis-cli ping || exit 1

HEALTHCHECK-最佳实践

  • 设置合适的检查间隔,避免过于频繁
  • 确保检查命令轻量且可靠
  • 为检查命令设置合理的超时时间
  • 在容器启动时设置适当的初始化期
  • 实现有意义的健康检查逻辑
  • 考虑在生产环境中使用更复杂的健康检查
  • 记录健康检查的日志以便调试

SHELL

SHELL-定义

指定用于执行命令的默认shell。这会影响到RUN指令和CMD指令的shell形式的执行方式。如果不指定,Linux上默认为["/bin/sh", "-c"],Windows上默认为["cmd", "/S", "/C"]

SHELL-语法

SHELL ["executable", "parameters"]

SHELL-特点

  • 影响后续RUNCMD指令的shell形式执行
  • 可以在Dockerfile中多次使用,每次使用都会影响其后的指令
  • 必须使用JSON数组格式
  • 对exec形式的RUNCMDENTRYPOINT指令没有影响
  • 每个SHELL指令会覆盖所有先前的SHELL指令

SHELL-示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 使用bash作为默认shell
SHELL ["/bin/bash", "-c"]

# 使用PowerShell(Windows)
SHELL ["powershell", "-Command"]

# 使用带参数的bash
SHELL ["/bin/bash", "-c", "-l"]

# 在同一Dockerfile中切换shell
SHELL ["/bin/bash", "-c"]
RUN echo "Using bash"

SHELL ["/bin/sh", "-c"]
RUN echo "Using sh"

# 使用zsh
SHELL ["/bin/zsh", "-c"]

SHELL-最佳实践

  • 在需要特定shell功能时才更改默认shell
  • 确保指定的shell在基础镜像中可用
  • 考虑shell切换对脚本兼容性的影响
  • 在多平台镜像中注意不同操作系统的shell差异
  • 记录SHELL更改的原因,以便维护
  • 避免不必要的shell切换,保持一致性

SHELL-使用场景

  • 需要使用特定shell特性时(如bash的数组)
  • 在Windows容器中使用PowerShell
  • 需要特定shell环境变量或配置时
  • 执行依赖于特定shell功能的脚本
  • 在多平台构建中处理不同的shell要求

ENTRYPOINT

ENTRYPOINT-定义

配置容器启动时要运行的可执行文件。与CMD不同,ENTRYPOINT指定的命令不会被docker run的命令行参数覆盖,而是会将这些参数作为ENTRYPOINT命令的参数。

ENTRYPOINT-语法

ENTRYPOINT ["executable", "param1", "param2"](exec 格式,推荐)
ENTRYPOINT command param1 param2(shell 格式)

ENTRYPOINT-特点

  • 容器启动时的主命令
  • 不会被docker run命令行参数覆盖
  • docker run的命令行参数会被当作ENTRYPOINT的参数
  • 可以通过docker run --entrypoint覆盖
  • 如果与CMD一起使用,CMD会作为ENTRYPOINT的默认参数
  • 每个Dockerfile应该只有一个ENTRYPOINT,如果列出多个,只有最后一个生效

ENTRYPOINT-示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# exec 格式(推荐)
ENTRYPOINT ["nginx", "-g", "daemon off;"]

# 与 CMD 配合使用
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]

# 创建可执行的Docker镜像
ENTRYPOINT ["echo"]
# docker run image hello world
# 输出: hello world

# 使用shell脚本作为入口点
COPY docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]

# 数据库容器示例
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["mysqld"]

ENTRYPOINT-最佳实践

  • 使用exec格式而不是shell格式
  • 使用shell脚本作为入口点时,记得添加exec "$@"
  • 为容器应用程序提供默认的启动命令
  • 使用ENTRYPOINT脚本进行初始化工作
  • 合理组合ENTRYPOINTCMD
  • 确保入口点脚本具有可执行权限
  • 在入口点脚本中处理信号和进程管理

ENTRYPOINT-与CMD的区别

  1. 命令覆盖

    • ENTRYPOINT:不会被docker run参数覆盖
    • CMD:会被docker run参数覆盖
  2. 参数处理

    • ENTRYPOINTdocker run参数作为其参数
    • CMDdocker run参数替换整个命令
  3. 组合使用

    • ENTRYPOINT:定义容器主命令
    • CMD:提供默认参数
  4. 使用场景

    • ENTRYPOINT:适用于作为可执行程序使用的容器
    • CMD:适用于提供默认的容器启动命令

ONBUILD

ONBUILD-定义

ONBUILD指令用于在当前镜像被作为基础镜像时执行的命令。这些命令会在子镜像的构建过程中被触发执行,而不是在当前镜像构建时执行。

ONBUILD-语法

ONBUILD <INSTRUCTION>

ONBUILD-特点

  • 只在构建子镜像时触发执行
  • 不能嵌套使用
  • 不能使用FROMMAINTAINER指令
  • 构建失败时不会继续执行后续的ONBUILD指令
  • 可以通过docker inspect查看镜像的ONBUILD触发器

ONBUILD-示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 基本用法
ONBUILD COPY . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src

# 在基础镜像中定义构建步骤
ONBUILD ADD . /app
ONBUILD RUN npm install

# 设置构建时的环境变量
ONBUILD ENV NODE_ENV=production

# 在子镜像构建时执行测试
ONBUILD RUN npm test

# 复杂构建步骤
ONBUILD COPY package.json /app/
ONBUILD RUN cd /app && npm install
ONBUILD COPY . /app/

ONBUILD-最佳实践

  • 用于创建可复用的基础镜像
  • 将通用的构建步骤放在ONBUILD
  • 清晰记录ONBUILD触发器的用途
  • 避免在ONBUILD中包含敏感信息
  • 保持ONBUILD指令的简单和可维护性
  • 在文档中说明ONBUILD的行为

STOPSIGNAL

STOPSIGNAL-定义

指定停止容器时要发送的系统调用信号。这个信号将被发送到容器的主进程(PID 1)。如果主进程没有正确处理这个信号,则在超时后容器将被强制终止。

STOPSIGNAL-语法

STOPSIGNAL signal

STOPSIGNAL-特点

  • 可以使用信号名称(如 SIGTERM)或信号编号(如 15)
  • 如果不指定,默认为 SIGTERM
  • 可以被docker stop命令的--signal选项覆盖
  • 影响容器的优雅停止行为
  • 在 Dockerfile 中只能出现一次

STOPSIGNAL-示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 使用信号名称
STOPSIGNAL SIGTERM

# 使用信号编号
STOPSIGNAL 15

# 使用 SIGQUIT 用于 Nginx
STOPSIGNAL SIGQUIT

# 使用 SIGINT(Ctrl+C)
STOPSIGNAL SIGINT

# 使用 SIGUSR1 用于自定义信号处理
STOPSIGNAL SIGUSR1

STOPSIGNAL-最佳实践

  • 为应用程序选择合适的停止信号
  • 确保应用程序正确处理指定的信号
  • 考虑使用 SIGTERM 允许优雅关闭
  • 在文档中说明选择特定信号的原因
  • 测试信号处理以确保正确的关闭行为
  • 考虑设置适当的超时时间(通过docker stop

完整示例

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
# 第一阶段:使用 ARG 定义构建参数
# 可以在构建时通过 --build-arg 指定版本,默认为 3.9
ARG PYTHON_VERSION=3.9

# 选择基础镜像
FROM python:${PYTHON_VERSION}-slim as builder

# 设置工作目录
WORKDIR /app

# 复制依赖文件
COPY requirements.txt .

# 安装依赖,使用 --user 选项将包安装到用户目录
RUN pip install --no-cache-dir --user -r requirements.txt

# 设置环境变量,将用户安装的包路径添加到 PATH
ENV PATH="/root/.local/bin:${PATH}"

# 第二阶段:构建最终镜像
FROM python:${PYTHON_VERSION}-slim

# 再次设置工作目录
WORKDIR /app

# 从第一阶段复制安装好的依赖
COPY --from=builder /root/.local /root/.local

# 复制应用代码
COPY . .

# 设置环境变量,用于 Flask 应用
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0

# 暴露端口
EXPOSE 5000

# 定义容器启动时执行的命令
CMD ["flask", "run"]

注意事项

  1. 指令不区分大小写,但是惯例是都大写,以便与其他参数区分开来。
  2. Docker是按顺序执行Dockerfile中的指令。
  3. 一般情况都以FROM指令开头(以下 3 中情况都可以在其前面)。
  4. Parser directives(解析器指令)必须位于 Dockerfile 的顶部且只能使用一次。
  5. 环境变量在Dockerfile中用$variable_name或表示${variable_name},它们被视为等效,括号语法通常用于解决变量名中没有空格的问题。更多详情见Environment replacement,支持环境变量的有ADDCOPYENVEXPOSEFROMLABELSTOPSIGNALUSERVOLUMEWORKDIRONBUILD
  6. 可以使用.dockerignore文件从构建上下文中排除文件和目录。
  7. docker build时需要指定一个目录作为构建上下文,默认是当前目录。由于 Docker 构建过程是在 Docker 守护进程中进行的,守护进程只能访问构建上下文中的文件。因此,COPYADD等指令的源路径必须是构建上下文中的相对路径,这样 Docker 才能找到要复制的文件。