2-6. 本番向け Compose - マルチステージ、override、リソース制限、ログ
所要時間: 50-60分(重い回。2セッションでも可) ゴール: 開発と本番で同じイメージを使いつつ設定を分離でき、resource limits とログ設計を本番運用視点で説明できる コミット内容:
~/learn/infra/day13/に Dockerfile(マルチステージ)、compose.yml、compose.override.yml、compose.prod.yml
このレッスンのゴール
- マルチステージビルドで本番イメージを最小化できる
-
compose.override.yml/compose.prod.ymlで環境差分を分離できる - resource limits(cpus / memory)と OOM の関係を理解した
-
read_only/ 非 root / cap_drop で攻撃面を絞れる - ログドライバ・ローテーション設計を本番視点で語れる
- Compose と Kubernetes の境界線を判断できる
なぜ学ぶか(実務悩みベース)
- 「dev のイメージそのまま本番に流す」事故を撲滅したい
- メモリリークで隣のコンテナまで道連れ OOM を防ぐ
- ログがディスクを食い潰してサーバーが詰まる事故を予防
- Compose 運用と k8s 移行の判断軸を持ちたい
前章とのつながり
2-5 までで「複数コンテナを安全に動かす」素材は揃った。今回はそれを 本番運用に耐える形 に仕上げる総合編。マルチステージ(1-5)、ネットワーク分離(2-3)、シークレット(2-4)、ヘルスチェック(2-5)の全部をミックスする。
大前提: 「開発で動いたから本番でも動く」は半分嘘
Docker の最大の売り文句は「環境差を吸収する」だが、それは イメージが同じ が前提。実務では:
- 開発: bind mount でホットリロード、root で動かす、ポート全開
- 本番: 読み取り専用、非 root、最小ポート、リソース制限
という 真逆の要件 がある。同じ compose.yml ではどちらも満たせない。
そして本番で起きる事故の多くは:
- 「dev のままのイメージで本番デプロイ → 開発ツールが入ったまま → 攻撃面が広い」
- 「リソース制限なしで動かして他コンテナを巻き込んで OOM」
- 「ログがコンテナ内に溜まり続けてディスク埋まる」
今日はこれらを 全部 compose の機能で解決 する。
本日の到達点
- マルチステージビルドでイメージサイズを 1/10 に
- compose override で開発と本番を分離(DRY)
- resource limits / read_only / non-root の本番設定
- ログドライバの選定と回転設計
- CI からのイメージビルド & レジストリ push の入口
- K8s / ECS との位置付け
セッション①: マルチステージビルドで本番イメージを絞る(25-30分)
0. 録画スタート&作業ディレクトリ
mkdir -p ~/log ~/learn/infra/day13
cd ~/learn/infra/day13
script ~/log/infra_day13.log1. なぜマルチステージか
# ----- 単一ステージ(NG) -----
FROM golang:1.23-alpine
WORKDIR /app
COPY . .
RUN go mod download
RUN go build -o /app/api .
CMD ["/app/api"]docker build すると、出来上がりのイメージサイズは 約 800MB。中身:
- Go コンパイラ
- ソースコード全部
- Git 履歴
- ビルド時の中間ファイル
これを本番に送るのは無駄が多い。
# ----- マルチステージ(推奨) -----
FROM golang:1.23-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /out/api .
FROM gcr.io/distroless/static-debian12
COPY --from=build /out/api /api
USER 65532:65532
EXPOSE 8080
ENTRYPOINT ["/api"]サイズ: 約 20MB(Go バイナリ + 最小ランタイム)。40分の1 になる。
マルチステージビルドの本質
1つの Dockerfile に複数の
FROMを書ける。各ステージは独立したイメージで、最終的に保持されるのは 最後のステージのみ。[build stage] ← Go コンパイラ、ソース、依存 | | COPY --from=build /out/api /api v [runtime stage] ← 最小ランタイム + 出来上がりバイナリ | v 最終イメージ(runtime stage のみ)メリット
- イメージサイズが激減: pull / push / デプロイが速い
- 攻撃面が小さい: コンパイラ・パッケージマネージャ・シェルが入ってない
- CI 環境を Dockerfile 内に閉じ込められる: 「ローカルに Go 1.23 が無い」とかにならない
各ベースイメージの選択肢
ベース サイズ 用途 golang:1.23800MB ビルド用 golang:1.23-alpine350MB ビルド用(小さめ) alpine:3.208MB ランタイム(busybox/sh 入り) gcr.io/distroless/static-debian122MB 静的バイナリ用ランタイム(推奨) scratch0MB 完全空(debugしづらい) distroless とは: Google が開発した「アプリだけ」入った最小イメージ。シェルすら入っていない。利点はセキュリティと小ささ、欠点はデバッグ時にシェルで入れないこと。
Go ビルドのお作法フラグ
CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /out/api .
CGO_ENABLED=0: C 言語連携を切る → 完全静的バイナリ。distroless / scratch で動くGOOS=linux: クロスコンパイル時に明示(Mac から Linux イメージを作る)-ldflags="-s -w": シンボル情報・デバッグ情報を削除してバイナリ縮小(30%減)
2. レイヤーキャッシュを効かせる順序
# ----- 悪い順序(毎回フルビルド) -----
FROM golang:1.23-alpine AS build
WORKDIR /src
COPY . . # ソース全部コピー
RUN go mod download
RUN go build -o /out/api .
# ----- 良い順序(依存だけキャッシュ) -----
FROM golang:1.23-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./ # 依存定義だけ先にコピー
RUN go mod download # 依存ダウンロード(変わらなければキャッシュ)
COPY . . # ソースコピー(変わるので毎回)
RUN go build -o /out/api .Docker のレイヤーキャッシュ
Docker は各
RUNCOPYをキャッシュ。前のレイヤーが変わっていなければ、それ以降を再利用する。「ソース変更 → 全部再ビルド」を避けるには、変わりにくいものを先に、変わりやすいものを後に 書く。
[依存定義 (go.mod)] ← 変わりにくい → キャッシュヒット率高 [依存DL] [ソースコード] ← 変わりやすい [ビルド]開発中の
docker buildが「毎回 10 分かかる」なら、レイヤー順を見直す。
3. .dockerignore で送信物を絞る
# .dockerignore
.git
node_modules
.env
.env.*
*.log
.vscode
.idea
README.md
docs/
test/fixtures/
*.test
coverage/.dockerignore の役割
docker buildは「context」をビルド時にデーモンに送る。.dockerignoreで除外したものは そもそも送られない。効果:
- ビルドが速い: 巨大な
.gitやnode_modulesを送らない- キャッシュが効きやすい: 不要なファイル変更でキャッシュが壊れない
- シークレット混入防止:
.envを.dockerignoreに入れて事故防止重要:
.gitignoreとは別物。.dockerignoreを必ず書く。
4. セキュリティ: 非 root ユーザー
FROM gcr.io/distroless/static-debian12
COPY --from=build /out/api /api
USER 65532:65532 # nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/api"]なぜ非 root か
デフォルトのコンテナは root で動く。これは:
- コンテナ侵害時の権限が広い: 内部での権限昇格が容易
- ホストへの脱獄リスク: コンテナエスケープ脆弱性があったとき被害が大きい
- マウントしたファイルを破壊できる: bind mount したホストファイルを root 権限で書き換え
正しい設計:
- Dockerfile で
USER <uid>:<gid>を指定- ファイル所有権を非 root にして配置
- 必要な作業ディレクトリは事前に chown
distroless の
65532:65532はnonrootユーザーの慣習的 UID/GID。
アンチパターン: root で動かす
FROM ubuntu:22.04 RUN apt-get update && apt-get install -y curl COPY app /app CMD ["/app"] # root で実行(USER 指定なし)なぜNGか:
- コンテナ内のあらゆる操作が root 権限
- LSM (AppArmor/SELinux) を回避できる可能性
- 「コンテナ脱出 → ホスト root」のリスク
対策:
RUN useradd -u 10001 -m app COPY --chown=app:app app /app USER app
セッション②: compose override と本番運用設定(25-30分)
5. compose override の階層化
~/learn/infra/day13/
├── compose.yml # 共通の最小定義
├── compose.override.yml # 開発用(docker compose up で自動マージ)
├── compose.prod.yml # 本番用(明示的に -f で指定)
└── .env
compose.yml(共通):
services:
api:
image: myregistry/myapi:${TAG:-latest}
environment:
LOG_LEVEL: ${LOG_LEVEL:-info}
DATABASE_URL: ${DATABASE_URL}
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "/api", "healthcheck"]
interval: 10s
timeout: 3s
retries: 3
start_period: 20s
db:
image: mysql:8.0
environment:
MYSQL_DATABASE: app
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-p$$MYSQL_ROOT_PASSWORD"]
interval: 5s
retries: 10
start_period: 20s
volumes:
db_data:compose.override.yml(開発、自動マージされる):
services:
api:
build: ./api # 開発はローカルビルド
image: myregistry/myapi:dev
volumes:
- ./api:/app # ホットリロード用
ports:
- "8080:8080"
environment:
LOG_LEVEL: debug
DATABASE_URL: "app:dev_password@tcp(db:3306)/app"
db:
environment:
MYSQL_ROOT_PASSWORD: dev_root_pass
MYSQL_USER: app
MYSQL_PASSWORD: dev_password
ports:
- "127.0.0.1:3306:3306" # 開発は GUI ツールから繋ぎたいcompose.prod.yml(本番、明示指定):
services:
api:
image: myregistry/myapi:${TAG} # CI でビルド済みイメージを使う
restart: always
# ホストからのポート公開はしない(front の nginx 経由)
ports: []
environment:
LOG_LEVEL: info
DATABASE_URL: ${DATABASE_URL} # CI Secrets から渡す
deploy:
resources:
limits:
memory: 512M
cpus: "1.0"
reservations:
memory: 256M
cpus: "0.5"
read_only: true
tmpfs:
- /tmp
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
security_opt:
- no-new-privileges:true
db:
image: mysql:8.0
restart: always
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
MYSQL_USER: app
MYSQL_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_root_password
- db_password
deploy:
resources:
limits:
memory: 2G
cpus: "2.0"
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"
secrets:
db_root_password:
file: /opt/secrets/db_root_password.txt
db_password:
file: /opt/secrets/db_password.txt起動コマンド:
# 開発(override が自動マージ)
docker compose up -d
# = docker compose -f compose.yml -f compose.override.yml up -d
# 本番(明示的に2ファイル指定、override はスキップ)
docker compose -f compose.yml -f compose.prod.yml up -doverride のマージルール
Compose は複数の YAML を マージ する。後ろに書いたファイルが優先。
- スカラー値(
image,restart): 上書き- リスト(
ports,volumes): 結合- マップ(
environment): マージ(同キーは上書き)
compose.override.ymlはdocker compose upで自動的に読まれる(ファイル名が固定)。本番用ファイルは別名にして-fで明示。設計指針:
compose.yml: 共通定義(image 名は変数、healthcheck、depends_on 等)compose.override.yml: 開発時のローカル差分(ホットリロード、debug ログ、ポート公開)compose.prod.yml: 本番設定(resource limits、read_only、ログ、secret)compose.staging.yml: ステージング用
6. Resource limits
services:
api:
image: myapi:1.0
# Compose v2 でも mem_limit / cpus は使える(古い書き方)
mem_limit: 512m
cpus: "1.0"
# 推奨は deploy.resources(Swarm 用だが Compose も読む)
deploy:
resources:
limits:
memory: 512M
cpus: "1.0"
reservations:
memory: 256M
cpus: "0.5"limits と reservations の違い
- limits(上限): ここを超えると OOM kill。CPU は throttle
- reservations(予約): 最低保証。スケジューラが他コンテナとの競合時に守る
実務での値:
- API(Go/Rust): 256-512M, 0.5-1 CPU
- API(JVM/Ruby/Node): 512M-2G, 1-2 CPU
- DB(MySQL/Postgres): 1-4G, 2-4 CPU
- Cache (Redis): 256-512M, 0.5 CPU
負荷試験で実測してから決める。最初は緩めに付けて、メモリ使用量を観察 → 徐々に絞る。
アンチパターン: limits なし
services: api: image: myapi:1.0 # 制限なしなぜNGか:
- メモリリークしたコンテナがホスト全体を食い潰す
- OOM Killer がランダムに他コンテナを kill する可能性
- 「特定 API が暴走したら全サービスが死ぬ」状態
必ず limits を付ける。「メモリ無制限が必要」なケースは存在しない。
OOM Kill が起きた時の確認
docker compose ps # STATUS: Exited (137) 5 minutes ago # ← exit code 137 = SIGKILL (128 + 9)、OOM の可能性大 docker inspect <container> | grep -i oom # "OOMKilled": true # Linux のログ dmesg | grep -i 'killed process' # Out of memory: Killed process 12345 (api)
7. read_only と tmpfs
services:
api:
image: myapi:1.0
read_only: true # ルートFS を読み取り専用に
tmpfs:
- /tmp # 書き込みが必要な場所だけ tmpfs
- /var/runread_only の効果
コンテナの ルートファイルシステムを書き込み禁止 にする。アプリが想定外の場所に書こうとしたら failure。
- マルウェアがバイナリを書き換えて永続化するのを防ぐ
- ログを
/var/log/に書く設計を強制的に「外に出す」(マウントか stdout)- 「コンテナはイミュータブル」の思想を実装で強制
書き込みが必要な場所は
tmpfsで別途許可。アプリ終了時にデータも消える(永続不要な一時ファイル向け)。
8. ログドライバ
services:
api:
image: myapi:1.0
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
compress: "true"主なログドライバ
ドライバ 用途 json-fileデフォルト。ファイルに JSON で書く localjson-file より効率的なバイナリ形式 journaldsystemd の journal に流す syslogsyslog サーバーへ fluentdFluentd / Fluent Bit へ awslogsCloudWatch Logs gcplogsCloud Logging noneログを破棄 json-file の罠: デフォルトでは ログがディスク上限まで溜まり続ける。
max-sizeとmax-fileを必ず指定。
アンチパターン: ログサイズ無制限
services: api: image: myapi:1.0 # logging 指定なし → 無制限に json-file が書かれるなぜ事故るか:
- 1日 100MB ログを吐くアプリ → 数ヶ月でホストのディスクを埋める
- ディスク枯渇で 全コンテナが死ぬ(書き込み不能で詰む)
- 復旧時にログファイルを消すまでサービス停止
必須設定:
logging: driver: json-file options: max-size: "10m" # 1ファイル 10MB max-file: "3" # 3ファイルまで保持(最大30MB)もしくは Docker daemon の
daemon.jsonでデフォルトを変える:{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }
12 Factor App: XI. Logs
ログをイベントストリームとして扱う アプリは出力ストリームのルーティングや保管を考えるべきでない。ログファイルを書くべきでない。代わりに各実行中のプロセスは、イベントストリームを stdout に書き出す
アプリは stdout/stderr に吐く のが正解。Docker / Compose / K8s がそれを拾って好きな場所に送る。
// 良い: 標準出力に書く log.Println("user created", "user_id", id) // 悪い: ファイルに直接書く f, _ := os.OpenFile("/var/log/app.log", os.O_APPEND, 0644) fmt.Fprintln(f, "user created")
9. イメージレジストリとビルドパイプライン
# ビルド & タグ付け
docker build -t myregistry/myapi:1.2.3 ./api
docker tag myregistry/myapi:1.2.3 myregistry/myapi:latest
# Login & Push
docker login myregistry.example.com
docker push myregistry/myapi:1.2.3
docker push myregistry/myapi:latestレジストリの選択肢
サービス 特徴 Docker Hub 最初の選択肢、無料枠あり(pull 制限あり) GitHub Container Registry (ghcr.io) GitHub Actions と相性◎、無料 AWS ECR AWS 環境、IAM 連携 GCP Artifact Registry GCP 環境 Quay.io Red Hat 系、脆弱性スキャン Harbor セルフホスト OSS
GitHub Actions でのビルド & push
# .github/workflows/release.yml name: Build and Push on: push: tags: ["v*"] jobs: build: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/build-push-action@v6 with: context: ./api push: true tags: | ghcr.io/${{ github.repository }}/api:${{ github.ref_name }} ghcr.io/${{ github.repository }}/api:latest cache-from: type=gha cache-to: type=gha,mode=maxポイント:
- タグ運用:
v1.2.3のような SemVer タグでイメージを固定- キャッシュ: GitHub Actions cache を Docker BuildKit のキャッシュとして使う
- マルチアーキ:
platforms: linux/amd64,linux/arm64で複数 CPU 対応
10. K8s / ECS との位置付け
Compose は本番に使えるか?
結論: 小規模なら使える、中規模以上は K8s/ECS が定石。
Compose で本番運用が許容されるケース
- 単一ホストで完結する(VPS 1台)
- サービス数が少ない(< 10)
- 高可用性が要らない(ダウンタイムが許容できる)
- チーム規模が小さい
K8s / ECS に移行すべきケース
- 複数ホスト跨ぎ
- オートスケーリングが必要
- 高度なロールアウト戦略(カナリア、ブルーグリーン)
- ステートフルなサービスの安全な運用
大事なのは Compose の知識が無駄にならないこと。
- K8s の Pod は Compose の service に近い
- Deployment は service + replicas
- Service(ネットワーク)は networks の発展形
- ConfigMap / Secret は environment / secrets の発展形
Compose で本番運用感覚を身につける → K8s への移行はスムーズ という学習パスは現代の王道。
アンチパターン: dev と本番で同一 yml
# 全環境で1ファイル(NG) services: api: build: . # 本番でローカルビルド? volumes: - .:/app # 本番でホットリロード? environment: LOG_LEVEL: debug # 本番で debug ログ? ports: - "8080:8080" # 本番でも 0.0.0.0:8080 公開なぜNGか:
- 開発依存(ソースマウント、debug ログ)が本番に漏れる
- 本番だけ違う設定にしたい時にコメントアウト合戦が始まる
- 設定の差分が見えにくく、レビューしづらい
正解: override ファイル階層で分離。本番設定は本番 yml に集約。
練習課題
mkdir -p ~/learn/infra/day13/api
cd ~/learn/infra/day13
script ~/log/infra_day13.log- マルチステージ Dockerfile を書き、
docker buildでサイズを比較(単一ステージ版と) docker history myapi:devでレイヤー構成を確認.dockerignoreを設定して、ビルド context のサイズをdocker build --progress=plainで観察- compose.yml / compose.override.yml / compose.prod.yml の3ファイルを作成
- 開発起動:
docker compose up -dで override が効いていることを確認 - 本番想定起動:
docker compose -f compose.yml -f compose.prod.yml configで 合成された設定を表示(実起動しなくてもよい) docker statsで resource limits が効いているか確認- ログサイズ確認:
docker inspect --format='{{.LogPath}}' <container>の出力をls -lhで確認 exitで script 終了
考察課題
- distroless ベースのイメージで、稼働中に exec で入ってデバッグしたい時どうする?
- resource limits の値を決めるには、本番でどんな計測をするべき?
- compose.prod.yml で
mem_limitではなくdeploy.resources.limits.memoryを使ったのはなぜ?
締め: git で証跡を残す
cd ~/learn/infra/day13
git init -q 2>/dev/null || true
git add api/Dockerfile api/.dockerignore compose.yml compose.override.yml compose.prod.yml
git commit -m "feat(infra): 本番向け compose(マルチステージ、override、limits、ログ)"チェックリスト
- マルチステージビルドで本番イメージサイズを 10分の1 にできた
- レイヤーキャッシュが効く順序で Dockerfile を書ける
-
.dockerignoreの意義と書き方が分かる -
USERで非 root 実行を設定できる - override / prod の3階層で設定分離できる
- resource limits を設定し、
docker statsで確認した - read_only + tmpfs の組み合わせを使える
- ログサイズ制限の必要性を語れる
- レジストリへの push / pull の流れが分かる
- Compose と K8s/ECS の位置付けを説明できる
詰まった時のチートシート
| やりたいこと | 書き方 |
|---|---|
| ステージ定義 | FROM ... AS build |
| ステージから持ってくる | COPY --from=build /src /dst |
| 非 root 実行 | USER 65532:65532 |
| context 除外 | .dockerignore |
| 本番起動 | docker compose -f compose.yml -f compose.prod.yml up -d |
| 設定合成プレビュー | docker compose -f ... config |
| メモリ制限 | deploy.resources.limits.memory: 512M |
| CPU 制限 | deploy.resources.limits.cpus: "1.0" |
| 読み取り専用 | read_only: true |
| 一時 FS | tmpfs: [/tmp] |
| ログサイズ制限 | logging.options.max-size: "10m" |
| イメージ push | docker push <registry>/<image>:<tag> |
| 動作確認 | docker stats |
やらかし事例: 本番Compose の地雷
事例1: dev のイメージそのまま本番デプロイ
ホットリロード用に
golang:1.22をベースにしたまま本番に乗せ、コンパイラ + 開発依存込みの 1.5GB イメージが本番に。マルチステージで分離する。
事例2: リソース制限無しで OOM 連鎖
mem_limit未設定 → 1コンテナのメモリリークでホストの swap 枯渇 → 他のコンテナまで道連れで死亡。本番では必ず制限。
事例3: ログ無限増殖でディスク満杯
default の json-file ドライバはローテーション無し。1年で50GB に。
max-size/max-fileを必ず設定。
事例4: root のまま本番起動
Dockerfile に
USER appuserを書かず、コンテナ内で root 動作。攻撃で破られた瞬間にホストへの足掛かりに。非 root 必須。
事例5: Compose で済むのに k8s 導入して運用破綻
10コンテナ未満・1台で済む規模なのに k8s クラスタを建てて、自分たちで Etcd / Control Plane の面倒を見る羽目に。Compose で十分なケースは多い。
対比表: Compose vs Kubernetes
| 観点 | docker compose | Kubernetes |
|---|---|---|
| スケール | 1台のホスト | 複数ノードクラスタ |
| 学習コスト | 低 | 高 |
| 運用負担 | 軽い | 重い(control plane) |
| 自動復旧 | restart policy 程度 | 強力(ReplicaSet, Pod 再スケジュール) |
| Rolling update | 弱い(Compose Watch) | 標準機能 |
| 適性 | 個人 / 小規模本番 / 開発 / CI | 中〜大規模本番、マイクロサービス |
| 移行コスト | YAML をほぼ書き直し | - |
物語型対比: Blue-Green vs Rolling Update
Blue-Green デプロイ
[Blue 環境(現本番 v1)] ←─ users
[Green 環境(v2 を起動 + 検証)]
↓ ロードバランサ切替
[Green 環境(v2 = 新本番)] ←─ users
[Blue 環境(v1 = 待機)] ← 失敗時に戻せる
- メリット: 切替が一瞬、ロールバックが安全
- デメリット: 2倍のリソース、DB スキーマ変更が同居期間を考慮要
Rolling Update
[v1 pod x 4] → 1個ずつ v2 に置換
v1: 4 → 3 → 2 → 1 → 0
v2: 0 → 1 → 2 → 3 → 4
- メリット: リソース小、k8s の標準
- デメリット: 一時的に v1 と v2 が同居、互換性必須
選び方
| 場面 | 選択 |
|---|---|
| 1サーバー Compose | Blue-Green を手動 |
| Kubernetes | Rolling が標準 |
| DB スキーマ破壊変更 | Blue-Green(同居期間のリスク回避) |
| 細かい段階リリース | Canary(Rolling の派生) |
「実務OK」基準
- 本番イメージが 50MB 未満で作れる: マルチステージを使いこなせる
docker compose -f ... -f ... up -dで環境を切り替えられる: 設定分離の感覚- resource limits の値を「なぜその数字か」根拠を持って言える: 観測 → 制限の習慣
- ログ運用の事故シナリオを語れる: ディスク枯渇、回転、外部送信
- Compose と K8s の境界線を語れる: いつ移行すべきか判断できる
自己評価チェックリスト
知識レベル
- Compose と k8s の境界線を3つの観点で語れる
- Blue-Green と Rolling の違いを場面別に判断できる
- resource limits / read_only / non-root の本番設定3点セットを暗唱できる
実行レベル
-
compose.override.ymlで開発設定を分離した - マルチステージで本番イメージを 50MB 未満にした
- ログドライバを
max-size付きで設定した
メタ認知
- 自分のチームのデプロイ戦略(Blue-Green or Rolling)の妥当性を見直した
- Compose で十分な規模なのに k8s 導入を考えていないか棚卸しした
さらに深掘るなら
- Docker docs: Multi-stage builds - 公式の網羅的解説
- Distroless - Google の最小イメージプロジェクト
- Docker docs: Compose specification - deploy セクション含む
- 12 Factor App: XI. Logs - ログをストリームとして扱う思想
- Aqua Trivy - イメージ脆弱性スキャナ。CI に組み込む
- Docker Bench for Security - 本番設定のセキュリティ監査
- 書籍: 「Docker Deep Dive」(Nigel Poulton) - 内部実装の参考書
- 書籍: 「Container Security」(Liz Rice) - 本番セキュリティの教科書
次のレッスン
レベル2 完了!次はレベル3 で Kubernetes 入門 → 本番デプロイの実践 に進む。Compose で学んだ概念(サービス、ネットワーク、ボリューム、ヘルスチェック、シークレット)が K8s の Pod/Service/Volume/Probe/Secret に直接対応する。今日までの理解が次の章で活きる。