Docker 基础体系 · 第 51/70 篇。示例以现代 Docker Engine、BuildKit 与 Compose 规范为基础,并明确 Linux 容器边界。

Docker 时区、Locale 与 CA 证书:最小镜像中的运行时基础

最小镜像通常只保留“运行程序所必需的文件”。问题在于,程序需要的并不只有可执行文件和动态库:时间格式化需要时区数据库,文本处理可能需要 Locale 数据,HTTPS 校验需要 CA 信任库。

因此,下面三个目录或配置经常决定一个程序在开发环境正常、进入容器后却出现异常:

/etc/localtime
/usr/share/zoneinfo/
/etc/ssl/certs/

它们分别与时区数据、时区选择和 CA 证书信任库有关。Locale 则通常由环境变量和 libc 提供的数据共同决定,例如:

LANG
LC_ALL
LC_TIME
LC_NUMERIC
LC_COLLATE

这三类运行时基础彼此独立:

  • 时区解决“同一个时间点如何显示”;
  • Locale 解决“文本、数字、日期如何按某种语言和区域规则解释或格式化”;
  • CA 证书解决“某个 TLS 证书链是否被本地信任”。

安装了 CA 证书,不会自动获得时区;设置了 TZ,也不会自动安装时区数据库;复制了时区文件,也不会改变 Locale。


1. Linux 容器到底提供了什么

Linux 容器不是一个拥有独立内核的虚拟机。容器中的进程通常共享宿主机 Linux 内核,但拥有相对隔离的:

  • 根文件系统;
  • 进程空间;
  • 网络命名空间;
  • 用户和权限视图;
  • 挂载视图。

时间是一个容易混淆的例子。容器中的 clock_gettime() 等系统调用由宿主机内核提供,因此容器通常读取的是同一个内核时间源;但“把 Unix 时间戳格式化成上海时间”所需的时区规则,来自容器自己的用户态文件系统。

可以把时间处理拆成三层:

内核时钟
  │
  │ 返回 Unix 时间戳,例如 1714521600
  ▼
用户态运行库或语言运行时
  │
  │ 读取 TZ、/etc/localtime、zoneinfo
  ▼
格式化结果,例如 2024-05-01 08:00:00 +0800

因此:

  • 容器不会因为缺少 tzdata 而“没有时间”;
  • 容器可能能获得正确的 Unix 时间戳,却无法正确解析 Asia/Shanghai
  • 容器的时区配置不会改变内核时钟,也不会改变数据库中保存的 UTC 时间。

1.1 时间点与本地时间不是一回事

Unix 时间戳表示一个绝对时间点。设时间点为 tt,时区规则为 ZZ,本地显示结果可以写成:

L=format(t,Z)L = \operatorname{format}(t, Z)

其中:

  • tt 是绝对时间;
  • ZZ 是时区规则,包括 UTC 偏移和夏令时变化;
  • LL 是展示给用户的本地日期时间。

同一个 tt,在两个时区下可能得到不同的日期:

UTC             2024-05-01 00:30:00
Asia/Shanghai   2024-05-01 08:30:00
America/New_York 2024-04-30 20:30:00

反过来,同一个本地时间不一定唯一对应一个时间点。夏令时切换时,某些本地时间可能不存在,或者重复出现。因此,日志和事件数据更适合保存 UTC 时间点,并在展示边界进行时区转换。


2. 时区:TZ/etc/localtime 与 zoneinfo

2.1 TZ 只是选择,不是数据

TZ 是用户态程序常见的时区选择方式:

TZ=Asia/Shanghai date

但这个命令能否成功,取决于运行库是否能找到对应的时区规则。常见查找路径包括:

/usr/share/zoneinfo/Asia/Shanghai
/etc/localtime

所以以下 Dockerfile 并不完整:

FROM alpine:3.20
ENV TZ=Asia/Shanghai
COPY app /app
CMD ["/app"]

ENV TZ=Asia/Shanghai 只设置了一个字符串。若镜像中没有 tzdata,程序可能出现:

找不到时区
无法加载 location
使用 UTC 回退
时间格式化结果与预期不一致

在 Alpine 中,通常这样安装:

FROM alpine:3.20

RUN apk add --no-cache tzdata

ENV TZ=Asia/Shanghai
COPY app /app

ENTRYPOINT ["/app"]

在 Debian 或 Ubuntu 中,通常这样安装:

FROM debian:bookworm-slim

RUN apt-get update \
    && apt-get install -y --no-install-recommends tzdata \
    && rm -rf /var/lib/apt/lists/*

ENV TZ=Asia/Shanghai
COPY app /app

ENTRYPOINT ["/app"]

这里有两个不同动作:

  1. 安装 tzdata,提供 /usr/share/zoneinfo 下的规则文件;
  2. 设置 TZ,告诉程序选择哪一份规则。

二者不能混为一谈。

2.2 /etc/localtime 是系统默认时区入口

很多 Linux 程序不直接读取 TZ,而是使用 /etc/localtime。它通常是一个指向 zoneinfo 文件的符号链接,或者是该文件的副本:

ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

完整 Alpine 示例:

FROM alpine:3.20

RUN apk add --no-cache tzdata \
    && ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    && echo Asia/Shanghai > /etc/timezone

COPY app /app
ENTRYPOINT ["/app"]

/etc/timezone 主要是某些发行版工具使用的记录文件,不是所有运行库都依赖它。真正决定时区行为的通常是 TZ/etc/localtime 以及 zoneinfo 数据。

如果程序明确使用:

time.LoadLocation("Asia/Shanghai")

它需要能找到名为 Asia/Shanghai 的时区数据;仅仅存在 /etc/localtime 不一定足够。

2.3 只复制一个时区文件

对于极小镜像,可以只复制需要的时区:

FROM alpine:3.20 AS tzdata

FROM scratch

COPY --from=tzdata /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai
COPY --from=tzdata /usr/share/zoneinfo/UTC /usr/share/zoneinfo/UTC

ENV TZ=Asia/Shanghai
COPY app /app

ENTRYPOINT ["/app"]

但这有明确边界:

  • 只能加载复制进去的区域;
  • 某些程序需要 /etc/localtime,此时还要复制或创建对应入口;
  • zoneinfo 规则会随系统数据更新,固定复制意味着需要重新构建镜像;
  • 如果程序依赖时区别名或其他区域,单文件复制可能不够。

在生产环境中,UTC 通常是最简单且最稳定的默认值:

ENV TZ=UTC

这并不表示用户界面不能展示本地时间,而是把转换推迟到明确的展示边界。


3. libc 决定了时区和 Locale 的实现边界

容器运行时不只有应用程序。动态链接程序还依赖 libc;即使是静态链接程序,语言运行时也可能自行读取系统文件。

常见组合如下:

基础环境 libc 典型特征
Debian、Ubuntu glibc Locale 能力完整,系统包较丰富
Alpine musl 体积小,Locale 与 glibc 行为不同
Distroless 取决于变体 通常缺少 shell 和包管理器
Scratch 无 libc、无用户态工具 只包含显式复制的文件

“镜像很小”不等于“程序只需要一个二进制文件”。需要分析程序的依赖方式:

应用程序
 ├─ 动态加载器和 libc
 ├─ 时区数据库
 ├─ Locale 数据
 ├─ CA 信任库
 └─ 用户、DNS、临时目录等运行时文件

静态链接只能消除某些动态库依赖,不能自动提供 CA、zoneinfo 或 Locale 数据。


4. Locale:不只是语言环境变量

Locale 是一组区域化规则,可能影响:

  • 字符分类,例如字母、数字、空白;
  • 大小写转换;
  • 字符串排序和比较;
  • 数字格式,例如小数点和千位分隔符;
  • 日期、时间和月份名称;
  • 错误消息语言;
  • 字符编码。

常见变量的优先关系可简化为:

LC_ALL
  覆盖所有 LC_* 类别
LC_TIME、LC_NUMERIC、LC_COLLATE ...
  覆盖对应类别
LANG
  提供未单独设置类别的默认值

例如:

LC_ALL=C
LANG=zh_CN.UTF-8

此时 LC_ALL=C 优先,整个进程通常使用 C/POSIX Locale,而不是中文 UTF-8 Locale。

4.1 LANG 不会凭空安装 Locale

下面的配置只设置了名称:

ENV LANG=zh_CN.UTF-8
ENV LC_ALL=zh_CN.UTF-8

如果镜像没有对应 Locale 数据,glibc 程序可能报告:

warning: setlocale: LC_ALL: cannot change locale

或者回退到 C Locale。程序仍可能运行,但排序、日期名称、数字格式和非 ASCII 文本处理可能改变。

在 Debian 中,可以使用 locales 生成目标 Locale:

FROM debian:bookworm-slim

RUN apt-get update \
    && apt-get install -y --no-install-recommends locales \
    && sed -i 's/^# *zh_CN.UTF-8 UTF-8/zh_CN.UTF-8 UTF-8/' /etc/locale.gen \
    && locale-gen \
    && rm -rf /var/lib/apt/lists/*

ENV LANG=zh_CN.UTF-8
ENV LC_ALL=zh_CN.UTF-8

这里的关键不是变量,而是 locale-gen 生成了 glibc 可读取的数据。

4.2 Alpine 的 Locale 不能按 glibc 习惯处理

Alpine 使用 musl。musl 的 Locale 支持模型与 glibc 不同,不能直接套用:

locale-gen
localedef

这类 glibc 工作流。很多 Alpine 程序主要依赖 UTF-8 字节处理,并不提供完整的 glibc Locale 数据库。需要特定排序、区域化数字或本地化日期的程序,应先验证实际运行库和应用行为,而不是仅复制 LANG

可执行验证示例:

FROM alpine:3.20

ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8

RUN printf '%s\n' "$LANG" "$LC_ALL"

这只能证明环境变量存在,不能证明每个程序都获得了完整的区域化功能。应使用目标语言运行时进行测试,例如测试:

  • Unicode 字符是否能正确编码和解码;
  • 排序是否符合业务要求;
  • 小数格式是否符合预期;
  • 日期月份名称是否可用。

4.3 C Locale 与 UTF-8 的工程取舍

C Locale 通常提供稳定、可预测的字节序或 ASCII 规则,适合协议字段、机器日志和可复现构建。UTF-8 则适合用户文本,但“使用 UTF-8 编码”和“启用完整中文 Locale”不是同一件事。

例如,一个程序可能正确处理 UTF-8 字符串,却仍然按字节排序;另一个程序可能根据 Locale 改变排序顺序。数据库排序、应用排序和 shell 排序也可能使用不同实现。

因此,Locale 选择必须对应业务语义:

  • 协议、配置、机器日志:倾向明确使用 UTF-8,并避免依赖隐式本地化;
  • 用户界面:在展示层显式设置语言和区域;
  • 需要稳定排序的程序:不要把 LC_ALL 交给宿主机或 Compose 环境随机继承;
  • 需要特定 glibc Locale 的程序:不要未经验证地迁移到 Alpine。

5. CA 证书:TLS 信任验证依赖的文件

CA 是 Certificate Authority,证书颁发机构。HTTPS 连接中,服务器通常发送自己的证书以及部分中间证书。客户端需要验证:

  1. 服务器证书的签名链能否连接到本地信任的根 CA;
  2. 证书的主机名是否匹配目标主机;
  3. 证书是否在有效期内;
  4. 密钥用途、签名算法等约束是否满足;
  5. 如果启用吊销检查,吊销状态是否满足要求。

可以把基本信任条件抽象为:

Trust(cert,host,roots,now)=true\operatorname{Trust}(cert, host, roots, now)=true

其中:

  • cert 是服务器证书链;
  • host 是请求的主机名;
  • roots 是客户端信任库;
  • now 是当前时间。

因此 CA 证书和时区虽然是两个独立问题,却都可能导致 TLS 失败:

  • 缺少 CA:无法建立信任链;
  • 系统时间错误:证书可能显示“尚未生效”或“已过期”;
  • 时区显示错误:会误导诊断人员,但证书有效期通常以绝对时间验证,不应简单归因于显示时区。

5.1 安装系统 CA 信任库

Alpine:

FROM alpine:3.20

RUN apk add --no-cache ca-certificates

COPY app /app
ENTRYPOINT ["/app"]

Debian:

FROM debian:bookworm-slim

RUN apt-get update \
    && apt-get install -y --no-install-recommends ca-certificates \
    && rm -rf /var/lib/apt/lists/*

COPY app /app
ENTRYPOINT ["/app"]

常见系统文件包括:

/etc/ssl/certs/ca-certificates.crt
/etc/ssl/certs/

具体路径取决于发行版和程序。不能只凭文件名假设所有运行时都使用同一位置。

5.2 应用程序可能有自己的 CA 查找规则

不同程序可能使用:

  • 系统 CA 目录;
  • 系统 CA bundle;
  • OpenSSL;
  • Go 的 crypto/x509
  • Java truststore;
  • Python 或 Node.js 的运行时规则;
  • 应用自己的证书配置;
  • SSL_CERT_FILESSL_CERT_DIR 等环境变量。

Go 程序通常会尝试加载系统根证书;如果运行环境没有系统 CA,可能需要显式指定 SSL_CERT_FILE,或者在构建阶段将 CA bundle 复制到最终镜像。具体行为还与 Go 版本、是否启用 cgo、目标操作系统以及环境变量有关,不能把“静态编译”当成“自带 CA”。

5.3 企业内部 CA 与公有 CA 不是一回事

如果服务使用企业内部 CA,安装公共 ca-certificates 仍然不够。应将内部根证书加入信任库:

FROM alpine:3.20

RUN apk add --no-cache ca-certificates

COPY company-root-ca.crt /usr/local/share/ca-certificates/company-root-ca.crt

RUN update-ca-certificates

COPY app /app
ENTRYPOINT ["/app"]

风险包括:

  • 把错误或过度授权的根 CA 放入所有服务;
  • 把私钥误复制进镜像;
  • 根 CA 更新后没有重建镜像;
  • 只修改了容器内 trust store,却没有验证目标语言运行时是否读取它。

根 CA 是高权限信任材料,镜像构建上下文、代码仓库和发布日志都不应泄露私钥。客户端证书私钥通常应通过 Secret 或运行时安全注入提供,而不是写入镜像层。


6. 最小镜像中的三类数据流

下面的关系比“安装几个包”更重要:

flowchart TD
    A[应用程序] --> B[语言运行时或 libc]
    B --> C[TZ / etc localtime]
    B --> D[zoneinfo 数据]
    B --> E[Locale 环境变量与数据]
    B --> F[CA 查找规则]
    F --> G[系统 CA Bundle]
    G --> H[TLS 服务器证书链]
    H --> I[主机名与有效期校验]
    C --> J[本地时间格式化]
    D --> J
    E --> K[排序/数字/日期/字符处理]

关键路径分别是:

  • 时区:时间点 → zoneinfo → 本地时间字符串
  • Locale:环境变量 → libc/运行时 Locale 数据 → 文本规则
  • CA:服务器证书链 → 本地信任根 → 主机名/有效期校验

三条路径中的任何一条缺失,程序都可能启动成功,但在特定功能第一次执行时失败。


7. 一个可验证的多阶段镜像

下面的 Dockerfile 以 Go 应用为例,展示 Debian 运行时中如何显式准备三类基础。它假设应用入口包位于当前目录,并且构建上下文中有 go.modgo.sum

# syntax=docker/dockerfile:1

FROM golang:1.23-bookworm AS build

WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
    go build -trimpath -ldflags="-s -w" -o /out/app .

FROM debian:bookworm-slim

RUN apt-get update \
    && apt-get install -y --no-install-recommends \
        ca-certificates \
        tzdata \
    && rm -rf /var/lib/apt/lists/*

ENV TZ=UTC
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8

COPY --from=build /out/app /usr/local/bin/app

USER 65532:65532
ENTRYPOINT ["/usr/local/bin/app"]

每一步的作用不同:

  • CGO_ENABLED=0 让该示例尽量避免动态 libc 依赖;
  • ca-certificates 提供 HTTPS 信任根;
  • tzdata 提供时区规则;
  • TZ=UTC 选择默认时区;
  • LANGLC_ALL 让 Locale 选择显式化;
  • USER 避免以 root 运行,但它不会自动创建主目录或写权限;
  • ENTRYPOINT 使用 JSON 形式,让应用直接成为容器主进程。

若程序需要 Asia/Shanghai

ENV TZ=Asia/Shanghai

同时必须保留 tzdata,并用应用测试 time.LoadLocation("Asia/Shanghai") 是否成功。

7.1 Scratch 版本需要显式复制全部依赖

FROM scratch 没有 shell、包管理器、证书、时区数据、/etc/passwd 和 CA bundle。一个可能的结构是:

FROM alpine:3.20 AS assets
RUN apk add --no-cache ca-certificates tzdata

FROM golang:1.23-bookworm AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
    go build -trimpath -ldflags="-s -w" -o /out/app .

FROM scratch

COPY --from=assets /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=assets /usr/share/zoneinfo/UTC /usr/share/zoneinfo/UTC
COPY --from=assets /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai

ENV TZ=UTC
COPY --from=build /out/app /app

USER 65532:65532
ENTRYPOINT ["/app"]

这个镜像仍有边界:

  • 没有 /bin/shdocker exec ... sh 会失败;
  • 没有 CA 更新工具,证书必须在构建时准备;
  • 没有完整 /etc/passwd,某些程序无法把 UID 映射为用户名;
  • 没有 DNS 配置文件时,名称解析行为可能不符合程序预期;
  • 没有时区文件时,命名时区加载会失败。

Scratch 适合依赖集合已经明确且验证充分的应用,不适合作为默认“更安全”的替代品。它减少了可见组件,也减少了现场诊断能力。


8. Compose 中的配置边界

Compose 的 environment 可以传递配置,但不能安装文件:

services:
  api:
    image: example/api:1.0
    environment:
      TZ: UTC
      LANG: C.UTF-8
      LC_ALL: C.UTF-8

这只改变进程环境。镜像仍然需要包含:

  • 对应的 zoneinfo;
  • 应用所需的 Locale 数据;
  • CA 信任库。

不要把宿主机的 /etc/localtime 当作跨平台、可复现的镜像依赖:

volumes:
  - /etc/localtime:/etc/localtime:ro

这种做法会让容器行为依赖宿主机文件,并可能在不同 Linux 发行版或非 Linux Docker 环境中表现不同。若业务要求固定时区,优先在镜像中安装并配置;若业务要求 UTC,直接在镜像和 Compose 中明确设置 TZ=UTC


9. 端到端验证:不要只检查环境变量

9.1 验证时区

在有工具的镜像中:

docker run --rm -e TZ=Asia/Shanghai alpine:3.20 sh -c '
  apk add --no-cache tzdata >/dev/null &&
  date &&
  date -u &&
  test -f /usr/share/zoneinfo/Asia/Shanghai
'

预期现象:

  • date 显示 TZ 指定的本地时间;
  • date -u 显示 UTC;
  • 目标 zoneinfo 文件存在。

若只执行:

docker run --rm -e TZ=Asia/Shanghai alpine:3.20 date

结果不应被当作完整验证,因为官方 Alpine 基础镜像未必预装 tzdata

9.2 验证 CA

docker run --rm alpine:3.20 sh -c '
  apk add --no-cache ca-certificates wget >/dev/null &&
  wget -qO- https://example.com >/dev/null &&
  echo TLS_OK
'

预期输出:

TLS_OK

失败时要区分:

  • certificate verify failed:优先检查 CA、系统时间、主机名和证书链;
  • network unreachable:不是 CA 问题;
  • DNS 解析失败:先检查 DNS;
  • 只有某个语言运行时失败:检查该运行时的 CA 查找路径。

不要通过关闭证书验证来“修复”问题:

InsecureSkipVerify=true
verify=False
curl -k

这会移除服务器身份验证,不能作为生产解决方案。

9.3 验证 Locale

docker run --rm \
  -e LANG=C.UTF-8 \
  -e LC_ALL=C.UTF-8 \
  debian:bookworm-slim \
  sh -c 'printf "LANG=%s\nLC_ALL=%s\n" "$LANG" "$LC_ALL"'

这只能检查变量。对需要完整 Locale 的程序,还应在目标镜像中执行真实测试,例如:

locale
locale -a

如果没有 locale 命令,不代表 Locale 一定不可用;它只说明镜像没有该诊断工具。最终证据应来自应用行为和运行库文档。


10. 常见失败路径与诊断顺序

10.1 日志时间偏移,但事件顺序正确

常见原因是:

  • 应用保存的是 UTC;
  • 日志查看器按宿主机时区展示;
  • 容器内 TZ 与观察端不同。

诊断时同时输出:

Unix 时间戳
UTC 格式
本地格式
时区名称或偏移

不要只比较日期字符串。优先确认绝对时间点是否一致。

10.2 程序启动正常,调用 HTTPS 时失败

这是 CA 缺失的典型延迟故障。启动阶段没有建立 TLS 连接,因此容器健康检查可能仍然通过。

检查:

ls -l /etc/ssl/certs/
test -f /etc/ssl/certs/ca-certificates.crt

然后使用与应用相同的运行时进行测试。curl 成功不能完全证明 Java、Node.js 或 Go 的 trust store 一定正确,因为它们可能使用不同实现。

10.3 TZ=Asia/Shanghai 已设置,但程序仍是 UTC

按以下顺序检查:

  1. 进程是否真正继承了 TZ
  2. /usr/share/zoneinfo/Asia/Shanghai 是否存在;
  3. 程序是否使用自己的时区数据库;
  4. 程序是否只读取 /etc/localtime
  5. 语言运行时是否在编译时嵌入了 tzdata;
  6. 是否存在应用级配置覆盖环境变量。

10.4 Locale 警告被忽略后,排序出现业务错误

setlocale 警告不一定导致进程退出,但可能使程序回退到 C Locale。对于依赖本地化排序、金额格式和日期名称的程序,这种回退可能是静默的数据展示错误,而不是明显崩溃。

应将 Locale 行为加入测试,而不是仅在 Dockerfile 中写:

ENV LANG=zh_CN.UTF-8

11. 版本、更新与供应链边界

时区规则和 CA 根证书都会变化:

  • 时区数据库会修正历史规则和未来政策;
  • CA 信任库会增加、删除或调整受信任根;
  • 基础镜像标签可能移动到新的补丁版本;
  • 构建缓存可能保留旧的包索引或旧的证书数据。

因此,镜像构建应使用发行版推荐的包管理方式,并在发布流程中定期重建和扫描。tzdataca-certificates 不是一次安装、永久不变的运行时常量。

同时要区分三个时间:

镜像构建时间
容器启动时的内核时间
证书校验时读取的当前时间

构建时复制的 CA 和 zoneinfo 是静态文件;运行时的时间点来自内核。更新 CA 或时区规则通常需要发布新镜像,而不是只重启现有容器。


12. 选择基础镜像时的实际取舍

Debian 或 Ubuntu

适合:

  • 依赖 glibc;
  • 需要完整 Locale;
  • 依赖系统工具进行诊断;
  • 运行时依赖较多且希望包管理简单。

代价是镜像和依赖集合通常更大。

Alpine

适合:

  • 应用已经验证兼容 musl;
  • 需要较小的常规 Linux 用户态;
  • 能接受与 glibc 不同的 Locale、DNS 和 ABI 行为。

不能只因为镜像小就迁移。需要特别验证动态链接、Locale、时间、TLS、DNS 和第三方原生库。

Distroless

适合:

  • 依赖已经固定;
  • 不需要 shell 和包管理器;
  • 通过外部工具或临时调试容器完成诊断。

必须确认对应变体中是否包含 CA、zoneinfo、动态库和非 root 所需文件。

Scratch

适合:

  • 静态链接或自带完整运行时;
  • 明确知道所有外部文件依赖;
  • 有成熟的日志、监控和调试方式。

它不是“没有依赖”,而是“不会替你提供依赖”。


一个最小但可用的 Linux 容器运行时,至少应回答四个问题:

  1. 应用保存和展示的时间分别采用什么语义,命名时区数据在哪里;
  2. 应用使用的 libc 或语言运行时是否支持目标 Locale;
  3. TLS 客户端从哪里读取公共或企业内部 CA;
  4. 镜像更新后如何验证时区规则、Locale 行为和证书信任仍然正确。

只设置 TZLANGLC_ALL,并不能完成这些配置;只复制二进制文件,也不能假设 CA、zoneinfo 和 Locale 会自动存在。最小镜像的正确做法不是盲目删除文件,而是先识别运行时数据流,再逐项保留、验证和更新真正的依赖。


系列导航与关联阅读

官方资料

本文依据 Docker、OCI 与 CNCF 官方文档重新梳理;正文与生产检查清单由 WR BLOG 编写。