很多人以为 ARG 只在构建时可见、用完就消失,所以拿它传 token。这篇用一个故意传了假 token 的构建,把它留在了哪些地方一处处找出来,并验证 ARG 对构建缓存的影响。
构建退出码 0;BuildKit 的警告:#1 WARN: SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ARG "TOKEN") (line 2) | - SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ARG "TOKEN") (line 2)
运行时环境变量里有 APP_MODE=prod;TOKEN=[];VERSION=[]
构建时 TOKEN=abc123SECRET
版本 2.0
docker inspect 的 Config.Env = ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","APP_MODE=prod"]
docker history --no-trunc 里含有 "abc123SECRET" 的行数 = 3:
RUN |2 TOKEN=abc123SECRET VERSION=2.0 /bin/sh -c echo "版本 $VERSION" > /version.txt # buildkit
RUN |2 TOKEN=abc123SECRET VERSION=2.0 /bin/sh -c echo "构建时 TOKEN=$TOKEN" > /build.txt # buildkit
ARG TOKEN=abc123SECRET
docker save 导出的 tar 里,包含 "abc123SECRET" 的文件(含各层 blob 与配置 JSON)有 1 个运行时环境变量里确实没有 TOKEN,但镜像的构建历史把它写得清清楚楚:RUN |2 TOKEN=abc123SECRET VERSION=2.0 … 和 ARG TOKEN=abc123SECRET,而且 docker save 导出的配置里也有。拿到镜像的人不需要运行它。BuildKit 构建时自己就警告 SecretsUsedInArgOrEnv。需要在构建期使用密钥,用下面会讲的 --mount=type=secret。
[什么都不变] 3/4 'RUN echo "构建时 TOKEN=t1" > /build.txt'→CACHED;4/4 'RUN echo "版本 1" > /version.txt'→CACHED [只改 VERSION] 3/4 'RUN echo "构建时 TOKEN=t1" > /build.txt'→重新执行;4/4 'RUN echo "版本 2" > /version.txt'→重新执行 [只改 TOKEN] 3/4 'RUN echo "构建时 TOKEN=t2" > /build.txt'→重新执行;4/4 'RUN echo "版本 1" > /version.txt'→重新执行
ARG 声明之后的每个 RUN,缓存键里都带着所有 ARG 的值(history 里的 |2 TOKEN=… VERSION=… 前缀)。所以改任何一个 ARG,后面所有 RUN 都要重新执行,哪怕有的 RUN 根本没用到它。把经常变化的 ARG(比如构建号)放在 Dockerfile 的靠后位置。
FROM 之后直接用 BASE=[] | 重新声明 ARG BASE 之后=[alpine:3.20]
写在 FROM 之前的 ARG 只能用在 FROM 行本身,进入阶段后得到空值,要在阶段里再写一行 ARG BASE 才能用。
docker run -e APP_MODE=dev:APP_MODE=dev
ENV 会一直留在镜像配置里并成为容器环境变量,docker run -e 可以覆盖。放默认配置可以,放密钥不行(docker inspect 能直接看到,环境变量泄漏那一篇会专门验证)。