Docker Compose 完全指南 / 第 10 章 · 敏感信息管理:secrets、configs 与 Docker Secrets
第 10 章 · 敏感信息管理
10.1 敏感信息的安全挑战
不安全的做法
# ❌ 密码硬编码在 compose.yaml(会进入版本控制)
services:
db:
environment:
POSTGRES_PASSWORD: MyS3cretP@ss!
# ❌ 密码硬编码在 Dockerfile(会进入镜像层)
# FROM postgres:16
# ENV POSTGRES_PASSWORD=MyS3cretP@ss!
敏感信息泄露风险
| 风险 | 说明 |
|---|
| 版本控制泄露 | compose.yaml 提交到 Git |
| 镜像层泄露 | docker history 可查看 ENV |
| 进程列表泄露 | docker inspect 可查看环境变量 |
| 共享卷泄露 | 日志文件可能记录敏感信息 |
安全等级对比
| 方式 | 安全等级 | 说明 |
|---|
| 硬编码在 YAML | ❌ 最低 | 进入版本控制 |
.env 文件 | ⚠️ 较低 | 需 .gitignore,仍在磁盘明文 |
environment + shell 变量 | ⚠️ 中等 | 不在文件中,但在进程环境 |
| Docker Secrets | ✅ 高 | 文件挂载在 /run/secrets/,不进入镜像层 |
| 外部密钥管理 | ✅✅ 最高 | HashiCorp Vault、AWS Secrets Manager 等 |
10.2 Docker Secrets(Swarm 模式)
Docker Secrets 原本是 Swarm 模式的功能,但 Compose V2 在独立模式下也支持基于文件的 secrets。
基本用法
services:
db:
image: postgres:16-alpine
secrets:
- db_password
- db_user
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
POSTGRES_USER_FILE: /run/secrets/db_user
secrets:
db_password:
file: ./secrets/db_password.txt
db_user:
file: ./secrets/db_user.txt
# 创建密钥文件
mkdir -p secrets
echo "MyS3cretP@ss!" > secrets/db_password.txt
echo "admin" > secrets/db_user.txt
# 设置权限
chmod 600 secrets/*.txt
Secrets 在容器中的位置
# 容器内默认挂载在 /run/secrets/<secret_name>
docker compose exec db ls /run/secrets/
# db_password
# db_user
# 读取密钥
docker compose exec db cat /run/secrets/db_password
# MyS3cretP@ss!
密钥的生命周期
┌─────────────────────────────────────────────┐
│ Secret 生命周期 │
│ │
│ secrets/ │
│ ├── db_password.txt ← 宿主机文件 │
│ └── db_user.txt │
│ │ │
│ docker compose up │
│ │ │
│ ▼ │
│ ┌───────────────────┐ │
│ │ Docker 管理的 tmpfs │ ← 内存中,不落盘 │
│ │ /run/secrets/ │ │
│ ├── db_password │ │
│ └── db_user │ │
│ └───────────────────┘ │
│ │ │
│ docker compose down │
│ │ │
│ ▼ │
│ 密钥从容器中移除(内存释放) │
└─────────────────────────────────────────────┘
10.3 Secrets 高级配置
自定义挂载路径
services:
app:
image: myapp:latest
secrets:
- source: api_key
target: /app/config/api_key.txt
mode: 0400 # 只读权限
uid: "1000"
gid: "1000"
- source: tls_cert
target: /app/certs/server.crt
mode: 0444
secrets:
api_key:
file: ./secrets/api_key.txt
tls_cert:
file: ./secrets/tls.crt
环境变量方式引用密钥文件
services:
app:
image: myapp:latest
secrets:
- source: db_password
target: db_password
environment:
# 许多官方镜像支持 _FILE 后缀
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
# 支持 _FILE 后缀的常见官方镜像
# - PostgreSQL: POSTGRES_PASSWORD_FILE
# - MySQL: MYSQL_ROOT_PASSWORD_FILE, MYSQL_PASSWORD_FILE
# - Redis: REDIS_PASSWORD_FILE (需确认版本)
# - WordPress: WORDPRESS_DB_PASSWORD_FILE
在应用代码中读取密钥
# Python — 读取文件中的密钥
def get_secret(secret_name, default=None):
"""从 Docker Secrets 或环境变量读取密钥"""
secret_path = f"/run/secrets/{secret_name}"
try:
with open(secret_path, 'r') as f:
return f.read().strip()
except FileNotFoundError:
return os.environ.get(secret_name.upper(), default)
db_password = get_secret('db_password')
api_key = get_secret('api_key')
// Node.js
const fs = require('fs');
function getSecret(name) {
try {
return fs.readFileSync(`/run/secrets/${name}`, 'utf8').trim();
} catch {
return process.env[name.toUpperCase()];
}
}
const dbPassword = getSecret('db_password');
// Go
import "os"
func getSecret(name string) string {
data, err := os.ReadFile(fmt.Sprintf("/run/secrets/%s", name))
if err == nil {
return strings.TrimSpace(string(data))
}
return os.Getenv(strings.ToUpper(name))
}
10.4 Docker Configs(配置对象)
Configs 类似 Secrets,但用于非敏感配置数据。
基本用法
services:
nginx:
image: nginx:alpine
configs:
- source: nginx_config
target: /etc/nginx/nginx.conf
mode: 0444
app:
image: myapp:latest
configs:
- source: app_config
target: /app/config.yaml
configs:
nginx_config:
file: ./config/nginx.conf
app_config:
file: ./config/app.yaml
内联配置
configs:
nginx_config:
content: |
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://app:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /health {
return 200 'OK';
add_header Content-Type text/plain;
}
}
Secrets vs Configs
| 维度 | Secrets | Configs |
|---|
| 用途 | 敏感信息(密码、密钥) | 非敏感配置 |
| 存储方式 | tmpfs(内存) | 挂载文件 |
| 文件大小限制 | 500KB | 无限制 |
| Swarm 加密 | ✅ Raft 日志加密 | ❌ 明文 |
| 容器中路径 | /run/secrets/ | 自定义 |
| 更新行为 | 需要重启容器 | 可自动更新(Swarm) |
10.5 外部密钥管理
HashiCorp Vault 集成
services:
vault:
image: hashicorp/vault:latest
cap_add:
- IPC_LOCK
environment:
VAULT_DEV_ROOT_TOKEN_ID: "root-token"
VAULT_DEV_LISTEN_ADDRESS: "0.0.0.0:8200"
ports:
- "8200:8200"
app:
image: myapp:latest
environment:
VAULT_ADDR: "http://vault:8200"
VAULT_TOKEN_FILE: /run/secrets/vault_token
secrets:
- vault_token
depends_on:
- vault
secrets:
vault_token:
file: ./secrets/vault_token.txt
使用 AWS Secrets Manager
services:
app:
image: myapp:latest
environment:
AWS_REGION: us-east-1
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
# 应用代码中通过 AWS SDK 获取密钥
使用 SOPS 加密
# 安装 sops
# 使用 age 或 GPG 加密 .env 文件
sops -e .env.production > .env.production.enc
# 在 CI/CD 中解密
# sops -d .env.production.enc > .env.production
# docker compose --env-file .env.production up -d
10.6 TLS 证书管理
使用 Secrets 挂载证书
services:
nginx:
image: nginx:alpine
ports:
- "443:443"
secrets:
- source: tls_cert
target: /etc/nginx/certs/server.crt
mode: 0444
- source: tls_key
target: /etc/nginx/certs/server.key
mode: 0400
configs:
- source: nginx_ssl_config
target: /etc/nginx/conf.d/default.conf
secrets:
tls_cert:
file: ./certs/server.crt
tls_key:
file: ./certs/server.key
configs:
nginx_ssl_config:
content: |
server {
listen 443 ssl;
ssl_certificate /etc/nginx/certs/server.crt;
ssl_certificate_key /etc/nginx/certs/server.key;
location / {
proxy_pass http://app:3000;
}
}
10.7 完整的安全配置示例
项目结构
secure-app/
├── compose.yaml
├── .env # 仅非敏感配置
├── .env.example # 模板
├── secrets/
│ ├── db_password.txt # 数据库密码
│ ├── api_key.txt # API 密钥
│ ├── jwt_secret.txt # JWT 签名密钥
│ └── tls/
│ ├── server.crt # TLS 证书
│ └── server.key # TLS 私钥
├── config/
│ ├── nginx.conf
│ └── app.yaml
└── app/
└── Dockerfile
compose.yaml
services:
app:
build: ./app
environment:
NODE_ENV: ${NODE_ENV:-production}
LOG_LEVEL: ${LOG_LEVEL:-info}
DB_HOST: db
DB_NAME: myapp
DB_USER: postgres
# 敏感值通过文件传递
DB_PASSWORD_FILE: /run/secrets/db_password
API_KEY_FILE: /run/secrets/api_key
JWT_SECRET_FILE: /run/secrets/jwt_secret
secrets:
- db_password
- api_key
- jwt_secret
configs:
- source: app_config
target: /app/config.yaml
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
retries: 5
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "443:443"
secrets:
- source: tls_cert
target: /etc/nginx/certs/server.crt
mode: 0444
- source: tls_key
target: /etc/nginx/certs/server.key
mode: 0400
configs:
- source: nginx_config
target: /etc/nginx/conf.d/default.conf
depends_on:
- app
restart: unless-stopped
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
file: ./secrets/api_key.txt
jwt_secret:
file: ./secrets/jwt_secret.txt
tls_cert:
file: ./secrets/tls/server.crt
tls_key:
file: ./secrets/tls/server.key
configs:
app_config:
file: ./config/app.yaml
nginx_config:
file: ./config/nginx.conf
volumes:
pgdata:
.gitignore
# 敏感文件
secrets/
.env.production
.env.staging
# 保留模板
!secrets/.gitkeep
!.env.example
10.8 安全最佳实践
| 实践 | 说明 |
|---|
| 使用 Secrets 而非 environment | 密码不进入进程环境变量 |
使用 _FILE 后缀 | 配合官方镜像的文件读取机制 |
| 限制文件权限 | chmod 600 secrets/*.txt |
.gitignore 排除 | 密钥文件不进入版本控制 |
提供 .env.example | 模板文件供团队参考 |
| 轮换密钥 | 定期更新密码和密钥 |
| 外部密钥管理 | 生产环境使用 Vault/AWS SM |
| 加密备份 | 密钥文件的备份需要加密 |
| 最小权限 | 密钥文件的 mode 设置合理权限 |
10.9 常见问题
| 问题 | 原因 | 解决方案 |
|---|
| Secret 文件找不到 | 路径错误 | 确认 file: 路径相对于 compose.yaml |
| 权限拒绝 | UID/GID 不匹配 | 使用 uid/gid/mode 配置 |
| Secret 值包含换行 | 文件末尾有换行符 | 应用代码 strip() 去除 |
| 环境变量优先级覆盖 Secret | _FILE 不是标准机制 | 需要在应用代码中实现读取逻辑 |
| Secret 文件大小超限 | 超过 500KB | 使用 Configs 或挂载卷 |
10.10 小结
| 概念 | 说明 |
|---|
| Secrets | 敏感信息管理,挂载在 /run/secrets/,tmpfs 存储 |
| Configs | 非敏感配置,支持内联和文件两种方式 |
_FILE 模式 | 官方镜像的文件读取约定 |
| 外部密钥管理 | Vault、AWS Secrets Manager、SOPS |
| 安全原则 | 不硬编码、不进版本控制、最小权限、定期轮换 |
扩展阅读
上一章:第 9 章 · 多环境管理 ← | 下一章:第 11 章 · Swarm 部署 →