用 Bitwarden Secrets Manager 给 Docker Compose 部署注入密钥:数据库密码、SMTP 密码存放在 Secrets Manager 项目里,服务器上运行
bws run -- docker compose up -d,compose 文件里只有变量名。附带一个检查脚本,核对 compose 需要的变量是否都已在项目里,并发现写死的密码。
先看清成品
本机安装了 bws 2.1.0。检查脚本 3 个测试通过;
docker compose config在注入变量时校验通过、未注入时按预期报错。用无效令牌运行bws返回Doesn't contain a decryption key。没有用真实机器账号读取密钥,也没有真正启动服务。
注意:计划和价格以 Bitwarden 官方价格页为准。不要共享个人账号或主密码,团队共享请用组织和集合。只导入、导出你自己有权管理的凭据。
做完之后你会得到什么?
| 成果 | 说明 |
|---|---|
| Secrets Manager 项目 | shop-prod,放 DB_PASSWORD、SMTP_PASSWORD |
| 机器账号 + 访问令牌 | 只能读这个项目 |
| compose 文件 | 变量缺失时拒绝启动 |
| 检查脚本 | 缺失变量、写死密码 |
需要准备哪些环境?
| 项目 | 要求 | 说明 |
|---|---|---|
| Bitwarden 组织 | 开通 Secrets Manager | 计划以官方价格页为准 |
| bws CLI | 本文用 2.1.0 | 从 GitHub sdk-sm 发布页下载 |
| Docker Compose | v2 以上 | 本文用 v5.6.0 |
| Python | 3.9 以上 | 运行检查脚本 |
文件结构
24-bitwarden-A-src/
├── compose.yaml
├── check_secrets.py
├── sample_list.json # bws secret list 输出示例(值已省略)
└── test_check_secrets.py
第 1 步:建项目和机器账号
- Secrets Manager 网页版新建项目
shop-prod,添加密钥:键名DB_PASSWORD、SMTP_PASSWORD(键名要符合环境变量命名规则)。 - 新建机器账号
shop-server,在“项目”页签只授予shop-prod的读取权限。 - 在“访问令牌”页签创建令牌,设置过期时间。令牌只显示一次。
第 2 步:compose 文件
compose.yaml:
services:
db:
image: postgres:17
environment:
POSTGRES_USER: shop
POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD 未注入,请用 bws run 启动}
volumes:
- pgdata:/var/lib/postgresql/data
app:
image: ghcr.io/example/shop:latest
environment:
DATABASE_URL: postgresql://shop:${DB_PASSWORD:?}@db:5432/shop
SMTP_PASSWORD: ${SMTP_PASSWORD:?SMTP_PASSWORD 未注入}
depends_on: [db]
volumes:
pgdata:
${VAR:?提示} 表示变量为空或未设置时直接报错,不会用空密码启动数据库。
第 3 步:检查脚本
check_secrets.py:
"""核对 compose 需要的变量是否都在 Secrets Manager 项目里,并确认 compose 文件没有写死密码。
用法:bws secret list <项目ID> --output json | python check_secrets.py compose.yaml
"""
import json, re, sys
from pathlib import Path
VAR = re.compile(r"\$\{([A-Z_][A-Z0-9_]*)(?::?[?-][^}]*)?\}")
LITERAL = re.compile(r"^\s*\w*(PASSWORD|SECRET|TOKEN)\w*\s*:\s*(?P<v>\S.*)$", re.I)
def check(compose_text, secrets):
keys = {s["key"] for s in secrets}
need = set(VAR.findall(compose_text))
problems = [f"Secrets Manager 中缺少 {k}" for k in sorted(need - keys)]
for n, line in enumerate(compose_text.splitlines(), 1):
m = LITERAL.match(line)
if m and "${" not in m["v"]: # 值里没有变量引用,才算写死
problems.append(f"compose 第{n}行疑似写死了密钥:{line.strip()}")
return need, problems
if __name__ == "__main__":
secrets = json.load(sys.stdin)
need, problems = check(Path(sys.argv[1]).read_text(encoding="utf-8"), secrets)
print("需要的变量:", ", ".join(sorted(need)))
print("\n".join("✗ " + p for p in problems) or "检查通过")
sys.exit(1 if problems else 0)
export BWS_ACCESS_TOKEN=(机器账号令牌,只在当前 shell 中设置)
bws secret list 项目ID --output json | python check_secrets.py compose.yaml
用示例数据的本机实际输出:
需要的变量: DB_PASSWORD, SMTP_PASSWORD
✗ Secrets Manager 中缺少 SMTP_PASSWORD
测试:python -m pytest -q → 3 passed。
第 4 步:注入并启动(本文未执行)
bws run --project-id 项目ID -- 'docker compose up -d'
bws run 把项目里的密钥按键名设为环境变量,只传给这条命令。
预期结果:容器正常启动;docker compose config 不带 bws 运行时报错:
error while interpolating services.app.environment.DATABASE_URL: required variable DB_PASSWORD is missing a value
(这段是本机实际输出。)
令牌不要写进 shell 历史:可以放在只有 root 可读的文件里,由 systemd 的 EnvironmentFile= 加载。
调试示例
第一版“写死密码”的正则是 (PASSWORD|SECRET|TOKEN)\s*:\s*(?!\$\{)\S+,实际运行把三行正常的变量引用都误报了:
✗ compose 第6行疑似写死了密钥:POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD 未注入,请用 bws run 启动}
✗ compose 第12行疑似写死了密钥:DATABASE_URL: postgresql://shop:${DB_PASSWORD:?}@db:5432/shop
原因:\s* 可以匹配 0 个空格,否定前瞻就落在空格上而失效;另外 ${DB_PASSWORD:?} 里也含 PASSWORD:。改成“键名含 PASSWORD/SECRET/TOKEN,且值里不含 ${”后误报消失,并加了 test_literal 测试。
出错了怎么办?
| 现象 | 原因 | 解决 |
|---|---|---|
Doesn't contain a decryption key |
令牌不完整或错误 | 重新复制完整令牌 |
| 401 / 令牌失效 | 令牌过期或被撤销 | 新建令牌 |
| 变量没注入 | 键名不符合环境变量规则(有空格、横杠) | 改键名,或用 --uuids-as-keynames |
| 读到别的项目的密钥 | 没加 --project-id 且机器账号能访问多个项目 |
加 --project-id,并收紧权限 |
| 自建 Bitwarden 连不上 | 默认连官方云 | 用 --server-url 指向你的服务器 |
模板规则
- compose 里只写
${VAR:?},不写值。 - 一个环境一个项目、一个只读机器账号。
- 令牌设过期时间,定期轮换。
项目资源
| 用途 | 地址 |
|---|---|
| Secrets Manager 概览 | https://bitwarden.com/help/secrets-manager-overview/ |
| Secrets Manager CLI | https://bitwarden.com/help/secrets-manager-cli/ |
| 访问令牌 | https://bitwarden.com/help/access-tokens/ |
| bws 下载(sdk-sm) | https://github.com/bitwarden/sdk-sm |
| Compose 变量插值 | https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/ |
| Bitwarden 价格 | https://bitwarden.com/pricing/ |
服务器上不再有 .env 明文文件,密钥集中在 Secrets Manager。换密码时只改一处,重新 bws run 部署即可;令牌泄露了,撤销它就切断访问。