Skip to main content

第27章:セキュリティ最小セット(秘密・権限・マウント範囲)🔐🚧

この章は「やらかしを最小化」がゴールです😊 “ガチガチの要塞”じゃなくて、開発で現実的に効く3点セットだけ入れます👇✨

  1. 秘密(パスワード/トークン)を漏らさない🕵️‍♀️
  2. コンテナ権限を必要最小限にする🧤
  3. ホスト(Windows側)への影響範囲を狭める🪟➡️🐳

1) 「秘密」は .env じゃなく “Secrets” に寄せる🔑📦

.env は便利だけど、流出事故が起きやすいです😇 しかも環境変数はログやデバッグ出力で混ざる事故も起きがち…。なので Composeの secrets を使うのが安全寄りです。 Compose secrets は サービスごとに明示的に渡したものだけがアクセスできて、コンテナ内では /run/secrets/<secret_name> のファイルとして入ります📄✨ (Docker Documentation)

まずはファイル配置(おすすめ)📁

  • secrets/ フォルダを作る
  • 中に **“秘密そのもののテキスト”**を置く(1行でOK)

例:

  • secrets/postgres_password.txt
  • secrets/jwt_secret.txt

そして Git管理から除外します🙅‍♂️(超重要)

## 秘密はコミットしない!
secrets/
.env
.env.*

ポイント:.env.example(秘密なし)を作って共有すると、チームでも事故りにくいです🙂✨

Composeで secrets を使う(Postgres例)🐘🔐

services:
db:
image: postgres:18
environment:
POSTGRES_USER: app
POSTGRES_DB: appdb
# 文字列で直書きしない!_FILE を使う
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
secrets:
- postgres_password

secrets:
postgres_password:
file: ./secrets/postgres_password.txt

✅ これで DBパスワードは環境変数にベタ書きされず、ファイルとして注入されます。 (*_PASSWORD_FILE みたいな “FILE系” を用意してる公式イメージが多いので、まずそれを使うのが楽です🙂)


2) 権限は「最小」だけ入れる🧤🧯

ここは“積み上げ式”がコツです💡 効き目が大きいのに壊れにくい順にいきます👇

(A) 非rootで動かす🧑‍💻🚫

コンテナは、何もしないと root で動くことがあります。Composeでは user で実行ユーザーを指定できます。 (Docker Documentation)

services:
api:
image: node:22
user: node

Node公式イメージには node ユーザーがいるので、まずはこれが楽です😊

(B) 余計な権限(capabilities)を落とす✂️🛡️

Linuxには「rootっぽい能力の小分け」みたいな仕組みがあって、Composeでは cap_drop で削れます。 (Docker Documentation)

services:
api:
cap_drop:
- ALL

✅ APIサーバーはだいたいこれでOKになりやすいです(必要になったら戻せばOK🙆‍♂️)

(C) ルートファイルシステムを読み取り専用にする📖🔒

改ざん/書き込みを減らせるので、刺さることが多いです。Composeでは read_only。 (Docker Documentation)

services:
api:
read_only: true

ただし、アプリが /tmp などに書きたがることがあるので… そこで次👇

(D) “書いていい場所”だけ tmpfs を足す🧺✨

tmpfs はメモリ上の一時領域をコンテナ内にマウントできます。 (Docker Documentation)

services:
api:
read_only: true
tmpfs:
- /tmp
- /run

✅ 「基本は読み取り専用、必要な一時領域だけ書ける」になって強いです😎

(E) 特権昇格を抑止する(no-new-privileges)🧨🚫

Linux側の仕組みで「子プロセスが新しい特権を得ない」設定があります。Dockerでは --security-opt no-new-privileges で使えます。 (docs.docker.jp)

Composeでは security_opt文字列として渡すのが基本形です(Dockerの --security-opt と同じノリ)。 (Docker Documentation)

services:
api:
security_opt:
- no-new-privileges:true

ここまで入れると「万一コンテナ内に危ないSUIDが混ざってた」みたいなケースでも、被害を抑えやすいです🙂


3) マウント範囲は “最小” にする🗺️📌

bind mount(ホストのフォルダをコンテナに見せるやつ)は便利ですが、デフォルトで書き込み可能なので、コンテナからホストのファイルを壊せます💥 Docker公式も「ホストファイルシステムを変更できて影響が出る」ので注意し、必要なら ro を使えと言ってます。 (Docker Documentation)

“全部マウント”をやめる🙅‍♀️➡️🙆‍♀️

ありがちな事故:プロジェクト丸ごと .:/app をRWで渡す ↓ 秘密ファイルや設定ファイルまでコンテナが触れる😇

おすすめの考え方👇

  • ソースコードだけマウントする
  • 設定ファイルは 必要なものだけ
  • 可能なら 読み取り専用(read_only: true / :ro

例(長い構文で明示するタイプ):

services:
api:
volumes:
- type: bind
source: ./apps/api/src
target: /app/src
read_only: true
- type: bind
source: ./apps/api/package.json
target: /app/package.json
read_only: true

「え、ビルド成果物とか書き込みたい…」って場合は、書き込みが必要な場所だけ volume を別で作るのが安全です😊


4) ポート公開は “必要なものだけ”+“127.0.0.1縛り”🌐🚪

Composeの ports はホスト↔コンテナをつなぎます。 そして ホスト側IPを省略すると 0.0.0.0(全IF)にバインドされます。 (Docker Documentation)

開発中でも、DB/Redis をうっかり外に晒すのは避けたいので👇

(A) DB/Redisは基本 “公開しない”🙈

  • APIとWorkerから使うだけなら、ports不要でOKです(内部ネットワークでつながります)

(B) どうしても必要なら 127.0.0.1 に縛る🔒

例:ローカルのDBクライアントで覗きたいときだけ

services:
db:
ports:
- "127.0.0.1:5432:5432"

✅ これで「同じPCの中だけ」になりやすいです(LANに出にくい)🙂 ※ コンテナエンジンによっては host IP 指定がサポートされない場合があり、そのとき Compose が弾くことがあります。 (Docker Documentation)


5) “ビルド時”に秘密が必要なとき(必要な人だけ)🏗️🔐

「プライベート依存を落とすためにトークンがいる」みたいな場面、ありますよね🥲 そのとき Docker BuildKitの build secrets を使うと、秘密をイメージに焼き込まずに済みます。 (Docker Documentation)

概念だけ最小で👇

  • ビルド時に secret を渡す
  • Dockerfile の RUN --mount=type=secret で使う
  • ビルドが終わったらイメージには残らない

仕上げ:この章の “最小セット” テンプレ(API例)📌✨

services:
api:
image: node:22
user: node
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp
- /run
# マウントは最小・できればRO
volumes:
- type: bind
source: ./apps/api/src
target: /app/src
read_only: true
# 公開ポートも必要最小(例:APIだけ)
ports:
- "127.0.0.1:3000:3000"

ミニ演習(10〜15分)🧪🎮

  1. DBパスワードを environment: POSTGRES_PASSWORD: ... から secrets化して、*_PASSWORD_FILE に置き換える🐘🔐
  2. dbports を消して、APIが動くのを確認する(内部通信だけでいけるか)🕸️✅
  3. apiread_only: true にして、落ちたら tmpfs: /tmp を足して復活させる🩹✨
  4. cap_drop: [ALL] を入れて起動できるか確認(落ちたら「何が必要だった?」をログで観察)🔍🙂

よくある詰まりポイント(先に潰す)🧯😆

  • secretsファイルの改行:末尾改行があると、読み取り側でコケることがある → trim() 前提で読むと安心👌
  • “全部マウント”しちゃって secrets まで見えてたvolumes を絞ると一気に安全側へ👍
  • ポートを雑に公開してた127.0.0.1: を付けるだけで事故率がガクッと下がる✨ (Docker Documentation)

必要なら、この第27章の内容をベースにして、あなたの教材の既存サンプル(API/Worker/DB/Redis構成)に合わせた “完成版 compose.yaml” の差分パッチ(どこに何を足すか)もそのまま書けますよ😊🔧