
Config 可以公開,secrets 不能公開。聽起來很簡單對吧?但我保證你的團隊裡,至少有一個 repo 的 git history 裡藏著一組密碼。可能是 DB password、可能是 JWT secret、可能是某個 API key——它現在還在那裡,而你可能不知道。
先講結論
判斷標準只有一個:外洩後能被用來存取系統的,就是 secret。Config 可以進 Git,secrets 只能從 Secret Store 注入。所有存取都有紀錄,定期輪替,外洩時有應急流程。
Config vs Secrets:先分清楚
Config(可公開):PORT、LOG_LEVEL、API_BASE_URL、FEATURE_FLAG
Secrets(不可公開):DB_PASSWORD、JWT_SECRET、OAuth Client Secret、TLS Private Key
搞不清楚的時候問自己:「這個值如果出現在 GitHub 的 public repo,會怎樣?」如果答案是「沒差」,那是 config。如果答案包含任何程度的恐慌,那是 secret。
.env 管理:三條鐵則
.env必須在.gitignore- 提供
.env.example(只有 key,沒有 value) - prod 和 dev 的
.env絕對不能混用
.env
.env.*
*.pem
*.key
credentials.json
!.env.example# .env.example
APP_ENV=
APP_PORT=8000
LOG_LEVEL=info
DB_HOST=
DB_PASSWORD=
JWT_SECRET=Secrets 注入:從 Secret Store 來,不是從 repo 來
CI Variables 是最簡單的起步:在 GitLab / GitHub 設定,勾選 Protected + Masked,pipeline 裡注入。
deploy:
stage: deploy
script:
- echo "DB_PASSWORD=$DB_PASSWORD" >> .env
- docker compose --env-file .env up -d
- rm .env # 用完就刪
only:
- tagsdocker-compose 可以用 env_file 注入,或者用 Docker Secrets(Swarm 模式)把密碼掛在 /run/secrets/ 底下。重點是:密碼不要寫死在 docker-compose.yml 裡。docker inspect 會直接看到,而且你沒辦法輪替。
輪替:不是「想起來就做」,是固定週期
- DB 密碼:90 天
- API key:180 天
- TLS 憑證:到期前 30 天
輪替流程必須有「重疊期」——新舊 secret 都可用,等確認新的沒問題再廢舊的。
#!/bin/bash
set -euo pipefail
NEW_PASSWORD=$(openssl rand -base64 32)
# 改 DB 密碼
PGPASSWORD="$OLD_DB_PASSWORD" psql -h "$DB_HOST" -U postgres -c \
"ALTER USER $DB_USER WITH PASSWORD '$NEW_PASSWORD';"
# 更新 .env
sed -i "s/^DB_PASSWORD=.*/DB_PASSWORD=$NEW_PASSWORD/" /opt/app/.env
# 重啟服務
docker compose restart api worker
# 驗證
curl -sf http://localhost:8000/health外洩了怎麼辦?
- 立刻 rotate(不是明天,是現在)
- 從 Git history 移除
- 通知相關人員
- 檢查 audit log 有沒有被利用
# 用 git-filter-repo 清除 Git history
pip install git-filter-repo
git filter-repo --path .env --invert-paths
git push --force --all最常見的三個錯誤
所有人共用同一組密碼:離職後無法撤銷。你不知道是誰在用那組密碼做了什麼。
把 secrets 印進 log:CI log 裡面大辣辣地印出 DB_PASSWORD=xxx。然後有人截圖貼到 ticket 裡。在那個瞬間你就需要啟動輪替流程了。
secrets 寫在 docker-compose.yml:docker inspect 一看就看到,而且每次改密碼都要改 compose file 然後重新部署。
密碼就像牙刷:不要跟別人共用,而且要定期換。