在 Dockerfile 中,如何安全地传入敏感信息(如 API Key、密码等)而不在最终镜像的历史记录中泄露?
在 Docker 中,安全地传入敏感信息(如 API Key、密码等)且不在镜像历史记录(docker history)中泄露,目前最推荐、最安全的方法是使用 Docker BuildKit 的 mount=type=secret 功能。
以下是详细的解决方案,按推荐程度从高到低排列:
方法一:使用 BuildKit Secrets(最安全,强烈推荐)
这是 Docker 官方引入的现代方法。它允许你在构建过程中挂载敏感文件,这些文件只存在于构建时的临时内存中,绝对不会被写入最终的镜像层。
1. 编写 Dockerfile
在需要使用敏感信息的 RUN 命令中,使用 --mount=type=secret 挂载密钥。
plaintext
# syntax=docker/dockerfile:1
FROM alpine
# 挂载 secret 到默认路径 /run/secrets/my_api_key
RUN --mount=type=secret,id=my_api_key \
# 在这里使用密钥,例如通过 curl 发送请求
API_KEY=$(cat /run/secrets/my_api_key) && \
echo "正在使用密钥进行安全构建..." && \
curl -H "Authorization: Bearer $API_KEY" https://api.example.com/data
# 最终镜像中不会包含任何关于 my_api_key 的内容
2. 执行构建命令
在构建时,通过 --secret 参数将宿主机上的文件或环境变量传入:
从文件传入:
bash# 假设你的 key 保存在宿主机的 ~/.api_key 中 docker build --secret id=my_api_key,src=$HOME/.api_key -t my-image .从环境变量传入(无需写成文件):
bash# 临时设置环境变量并传入 export MY_ENV_KEY="super-secret-token" docker build --secret id=my_api_key,env=MY_ENV_KEY -t my-image .
为什么安全?
- 密钥挂载在内存文件系统(
tmpfs)中。 - 密钥不会被提交到任何镜像层(Layer)。
docker history my-image中只会看到RUN --mount=type=secret...,看不到具体内容。
方法二:多阶段构建(Multi-stage Builds)
如果你使用的是旧版 Docker(不支持 BuildKit),或者需要下载私有依赖包,可以使用多阶段构建。
在“构建阶段”引入敏感信息,在“运行阶段”只复制构建产物。
示例:
plaintext
# 第一阶段:构建阶段 (Builder)
FROM golang:1.16 AS builder
# 传入敏感信息(注意:这个阶段的镜像层仍然含有敏感信息)
ARG GITHUB_TOKEN
RUN git config --global url."https://${GITHUB_TOKEN}:x-oauth-basic@github.com/".insteadOf "https://github.com/"
WORKDIR /app
COPY . .
# 编译出二进制文件
RUN go build -o myapp
# 第二阶段:运行阶段 (最终镜像)
FROM alpine:latest
WORKDIR /app
# 仅仅复制编译好的二进制文件,不复制任何 Git 配置或 TOKEN
COPY --from=builder /app/myapp .
CMD ["./myapp"]
为什么安全?
虽然在 builder 阶段存在敏感信息,但你最终推送到镜像仓库(Docker Hub/Registry)的只是第二个阶段的镜像。只要你不在本地保留中间镜像,敏感信息就不会泄露。
方法三:在容器启动时传入(避免在构建时传入)
最佳的安全实践是:如果可以,不要在构建(Build)时传入敏感信息,而是在容器启动(Run)时传入。
1. 编写 Dockerfile
plaintext
FROM alpine
# 镜像中不包含任何 Key,只写逻辑
CMD ["sh", "-c", "curl -H \"Authorization: Bearer $API_KEY\" https://api.example.com"]
2. 运行容器时传入
- 通过环境变量:bash
docker run -e API_KEY="your_actual_api_key" my-image - 通过 Docker Compose / Kubernetes Secrets:
在部署时将密钥挂载为文件或环境变量。
⚠️ 绝对不要使用的方法(反面教材)
1. 使用普通的 ARG 或 ENV
plaintext
# 极其危险!
ARG API_KEY=123456
ENV SECRET_TOKEN=abcdef
- 原因: 即使你在后面的步骤中删除了这些变量,它们也会被永久记录在镜像的历史元数据中。任何人只需运行
docker history <image>或docker inspect <image>就能直接看到明文。
2. COPY 密钥文件后在同一 Dockerfile 中 RM
plaintext
# 极其危险!
COPY ./secret.txt /app/secret.txt
RUN ./use_secret.sh /app/secret.txt
RUN rm /app/secret.txt
- 原因: Docker 的镜像是分层的。在第一步
COPY时,密钥已经写入了那一个 Layer。即使你在下一步rm了它,它仍然存在于前一个 Layer 的历史记录中,可以通过提取 Layer 轻松恢复。
总结建议
- 首选方案:使用 BuildKit Secrets (
--mount=type=secret)。这是目前 Docker 官方标准且最安全的构建期传参方式。 - 如果敏感信息仅在运行时需要:通过
docker run -e或 K8s Secret 在启动时注入,不要写进 Dockerfile。