第21章:Compose secrets実践:秘密はファイルで渡す(/run/secrets)📄🔐
この章のゴールはシンプル!😄
「パスワードやAPIキーを“環境変数に直書きしない”で、Composeのsecretsとして“ファイルで渡す” を手で覚えます🧠✨
secretsは コンテナ内で /run/secrets/<secret_name> の“読み取り専用ファイル”として見える のが基本です📌 (Docker Documentation)
1) まず“何が嬉しいの?”を一言で💡🫶
- 環境変数で秘密を渡すと、意図せず露出しやすい(デバッグログに出たり、追跡が難しかったり)😇💥
- secretsなら 「そのサービスにだけ見せる」 をComposeで明示できます✅ (サービス単位で許可しない限りアクセスできない)(Docker Documentation)
2) Compose secretsの“基本ルール”3つ📚🔒
-
top-level
secrets:で“秘密の中身の作り方”を定義file:(ファイル内容をsecretに)environment:(ホスト側の環境変数の値をsecretに)(Docker Documentation)
-
各サービス側
services.<name>.secrets:で“この秘密を見せる”と明示(ここが超重要)(Docker Documentation) -
コンテナ内では
/run/secrets/<secret_name>の読み取り専用ファイル 🧊📄 (Docker Documentation)
3) ハンズオン:Node/TypeScriptが /run/secrets を読む🧑💻✨
3-1) こんな構成で作るよ📁
compose.yamlapp/(Node/TSアプリ)secrets/(秘密ファイル置き場:**Gitに入れない!**🙅♂️)
3-2) secretファイルを作る(PowerShell例)🪟🔑
※例としてDBパスワードを作るよ(中身はダミーでOK)🙂
mkdir secrets -Force
## 改行が末尾に入ると困るケースがあるので -NoNewline 推奨
Set-Content -Path secrets\db_password.txt -Value "super-secret-password" -NoNewline
Gitに絶対入れない(ここ、事故ポイント!)😱🧯
## secrets置き場はまるごと除外
secrets/
## よくある秘密ファイルもついでに
.env
3-3) compose.yaml(最小形)🧩🔐
myapp だけが db_password を読める設定にします✅
services:
myapp:
build: ./app
secrets:
- db_password
environment:
# 値そのものではなく「ファイルの場所」だけ渡す(安全寄り)👍
DB_PASSWORD_FILE: /run/secrets/db_password
secrets:
db_password:
file: ./secrets/db_password.txt
ポイント👇
secrets:(トップ)で 中身の出どころ を決めるservices.myapp.secrets:で 見せる相手 を決める- アプリは
DB_PASSWORD_FILEを見てそこから読む(値をENVに直置きしない)👍
3-4) TypeScript側:secretファイルを読む📖✨
例:app/src/secret.ts
import { readFile } from "node:fs/promises";
export async function readSecretFromFile(
envKey: string,
defaultPath: string,
): Promise<string> {
const path = process.env[envKey] ?? defaultPath;
// 末尾改行が混ざっても困らないよう trimEnd() が安定👍
const value = (await readFile(path, "utf8")).trimEnd();
if (!value) throw new Error(`Secret is empty: ${path}`);
return value;
}
例:app/src/index.ts
import { readSecretFromFile } from "./secret.js";
async function main() {
const dbPassword = await readSecretFromFile(
"DB_PASSWORD_FILE",
"/run/secrets/db_password",
);
// 🚫 絶対に console.log(dbPassword) しない(漏れる)😇💥
console.log("✅ secret loaded (length only):", dbPassword.length);
}
main().catch((e) => {
// ここも注意:例外に秘密が混ざらない形にする
console.error("❌ failed to start:", e instanceof Error ? e.message : e);
process.exit(1);
});
3-5) 起動&確認コマンド🚀
docker compose up --build
コンテナ内に secret が来てるか“中身を出さずに”確認👀🔍 (例:ファイル一覧とパーミッションだけ見る)
docker compose exec myapp sh -lc "ls -l /run/secrets"
db_password が /run/secrets に見えてたら成功🎉 (Docker Documentation)
4) ちょい応用:long syntaxでファイル名を変える🪄📄
「コンテナ内では server.cert って名前がいい」みたいな時に target を使います✅ (Docker Documentation)
services:
myapp:
build: ./app
secrets:
- source: db_password
target: db-password.txt
secrets:
db_password:
file: ./secrets/db_password.txt
5) 重要な落とし穴集😇🧨(ここで事故る)
/run/secretsは基本“読み取り専用”:アプリが書き換えようとするとコケます🧊📄 (Docker Documentation)uid/gid/modeを指定しても、file:由来のsecretだとDocker Composeでは効かない(実装上、裏側がbind mountなので)🫠 → つまり「権限を細かく変えたい」は別の作戦が必要です(次の章以降でカバーしやすい)(Docker Documentation)- 改行問題:secretファイル末尾の改行でログイン失敗…あるある😵
→
trimEnd()が保険🛟 - ENVに“秘密そのもの”を入れない:Docker公式も、ENV注入は露出しやすい点を注意しています⚠️ (Docker Documentation)
6) AI拡張(Copilot/Codex)時代の“秘密の守り方”🤖🧱🔐
AIが悪いというより、うっかり流れが一番怖いです😇💥
-
秘密ファイルはワークスペースから隔離(最強)🚪
- 例:
secrets/をプロジェクト外に置く、など
- 例:
-
少なくとも
.gitignoreで秘密を追跡対象から外す✅ -
さらに GitHub のCopilotには“content exclusion”という仕組みがあり、除外したファイルは提案/チャットの文脈に使われない方向で設計されています🧯 (GitHub Docs)
-
OpenAI のCodexも、基本はワークスペース範囲+承認ポリシー等の安全設計が説明されています(とはいえ“見せない”が最強)🛡️ (OpenAI Developers)
✅合言葉:「秘密は“AIに貼らない・ログに出さない・Gitに入れない”】【最重要】📛
7) ミニ演習(3つ)✍️🔥
-
secretをローテーションしてみよう🔁
secrets/db_password.txtを書き換えるdocker compose up -d --buildordocker compose restart- アプリが新しい長さを読み直すのを確認(中身は出さない)✅
-
targetを使ってファイル名を変える🪄/run/secrets/db-password.txtにして読み取りパスを更新
-
“ENV直書き”をわざとやって、危なさを体感😈(検証用ダミーで!)
environment: DB_PASSWORD=...としてみて- 「どこに露出し得るか」を想像してみる(ログ/デバッグ/共有/スクショ)🫣
8) まとめ🎯✨
- Compose secretsは 「秘密を“ファイルとして渡す”」 仕組み📄🔐 (Docker Documentation)
- サービス単位でアクセスを絞れるのが強み✂️🛡️ (Docker Documentation)
- 実運用の事故はだいたい Git / ログ / コピペ / AIチャット から起きるので、そこを先回りして塞ぐのが勝ち🏆😄
次の章(第22章)では、この secrets を ログ・例外・デバッグで“漏らさない設計” に落とし込んでいきます🧯🫣