bigywater.tech
Docker · 13

构建期密钥:--mount=type=secret 与直接 COPY 的区别

构建时要用私有仓库的凭据,怎么传才不会留在镜像里?这篇把三种做法(COPY 后删除、--build-arg、--mount=type=secret)的镜像都拆开,在配置、历史和每一层里搜索密钥字符串,并验证 secret mount 的缓存行为和失败方式。

0
RUN --mount=type=secret 构建的镜像里,在所有层和配置中找到密钥的处数
仍 CACHED
换成完全不同的密钥内容后,用到密钥的那一步仍命中缓存
层内文件 app.conf
命令把 /run/secrets/tok 复制到 /app.conf 后,密钥出现在镜像的层里

三种办法的对比

实测 · 本机执行输出
  [A:COPY 进去用完再 rm] 退出码 0;在镜像里找到密钥的位置:['层内文件 tmp/secret.txt']
  [B:--build-arg] 退出码 0;在镜像里找到密钥的位置:['blobs/sha256/830e139dbb7f2422a(原始 blob/配置)'];docker history 里含密钥的行数 2
  [C:RUN --mount=type=secret] 退出码 0;构建输出里:构建期读到了 18 字节
      在镜像里找到密钥的位置:(没找到);docker history 里含密钥的行数 0
      从镜像启动后 ls /run/secrets:ls: /run/secrets: No such file or directory

COPY 进去再 rm,密钥留在较早的层里(层内文件 tmp/secret.txt,前面讲 whiteout 的那一篇说过,删除救不了);--build-arg 把值写进配置和 docker history;--mount=type=secret 只在构建的那一步里把密钥挂载成临时文件 /run/secrets/tok,镜像的所有层和配置里都搜不到它。

也可以用环境变量

实测 · 本机执行输出
  退出码 0;环境变量 TOKVAR 长度 17;镜像里的密钥位置:(没找到)

--secret id=tok,env=MYTOK 配 RUN --mount=type=secret,id=tok,env=TOKVAR,构建期的 TOKVAR 就是宿主机环境变量里的值(长度 17),同样不进镜像。

缓存

实测 · 本机执行输出
  换了密钥内容(10 字节 → 35 字节)再构建:第 3 步 CACHED(命中缓存)

密钥内容不参与缓存键:换成完全不同的密钥,用到它的步骤仍然 CACHED。好处是密钥轮换不会让整条构建链重做;坏处是密钥变了你得自己想办法让它重建。

缺失与默认

实测 · 本机执行输出
  退出码 1;#5 ERROR: secret nope: not found | ERROR: failed to build: failed to solve: secret nope: not found
  没写 required(默认可选):构建退出码 0;ls: /run/secrets/nope: No such file or directory | 退出码 1

默认挂载是可选的:没提供密钥,构建照样成功,只是文件不存在;写了 required=true 才会以 secret nope: not found 失败。命令依赖密钥时记得写 required。

mount 管不住你的命令

实测 · 本机执行输出
  RUN --mount=type=secret … cp /run/secrets/tok /app.conf:退出码 0;镜像里找到密钥的位置:['层内文件 app.conf']

cp /run/secrets/tok /app.conf 把内容写进了镜像的层里,密钥又泄漏了。secret mount 保证的是挂载本身不进镜像,不是命令的输出。

分步演示

构建期密钥
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Docker Desktop(Engine 29.8.2,容器跑在 Docker Desktop 自带的 Linux 虚拟机里,内核与宿主机的 macOS 不同);镜像为 linux/arm64;需要看虚拟机内部时用特权容器 + nsenter;计时用 time / date +%s%N,数字每次运行会有小幅抖动。密钥是一个带时间戳的假字符串;在镜像里搜索的方法:docker save 导出 tar,在配置 JSON、每个 blob 以及解压后的每个层文件里查找该字符串;缓存判断来自构建输出里步骤的 CACHED(每次运行前加了带时间戳的 salt 层);每种情形一次;实验后已删除所有镜像
    • 没有验证:ssh 挂载(--mount=type=ssh)、docker compose 的 secrets、多阶段构建中 secret 是否进入中间阶段的缓存导出、--cache-to 导出缓存里是否含密钥、BuildKit 日志(--progress)是否可能打印密钥
    京ICP备2026067502号-1京公网安备11010502063487号