docker-containerization について
コンテナを構築・保守する開発者向けに、本番用 Dockerfile の作成と改善方法をまとめたスキルです。マルチステージビルド、キャッシュ、セキュリティ、イメージサイズ、Docker Compose の開発パターンを扱います。
主な機能
- 01マルチステージ Dockerfile のパターン
- 02レイヤーキャッシュ戦略
- 03Docker Compose の開発環境
- 04イメージサイズの最適化
- 05セキュリティ強化とヘルスチェック
活用シーン
- 01Go、Rust、TypeScript、Python 用の Dockerfile 作成
- 02依存レイヤーのキャッシュによる再ビルド時間の短縮
- 03非 root ユーザーとヘルスチェックによるコンテナの強化
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ curiositech/ some_claude_skills --skill docker-containerizationインストール後、Claude Code で/docker-containerizationを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
.claude-plugin/plugin.jsonreferences/compose-patterns.mdmulti-stage-patterns.md詳細
Docker のコンテナ化
マルチステージビルド、セキュリティ強化、サイズ最適化を備えた、本番環境向けの高品質な Dockerfile を作成します。ローカル開発向けの docker-compose、イメージレイヤーのキャッシュ、ヘルスチェック、そして 2GB のイメージと 50MB のイメージを分けるパターンを扱います。
使用する場面
次の用途に使用します:
- Dockerfile を一から作成する、または既存のものを改善する
- コンパイル言語(Go、Rust、TypeScript)向けのマルチステージビルド
- ローカル開発環境向けの Docker Compose
- イメージサイズの最適化(ベースイメージの選択、レイヤーキャッシュ)
- Docker のセキュリティスキャンと強化
- 開発用と本番用の Dockerfile パターン
- コンテナのビルド失敗のデバッグ
- .dockerignore の最適化
次の用途には使用しません:
- Kubernetes のデプロイ/オーケストレーション(別の領域です)
- クラウド固有のコンテナサービス(ECS、Cloud Run、App Runner)
- CI/CD パイプラインの設定(
github-actions-pipeline-builderを使用してください) - docker-compose の範囲を超えるコンテナネットワーキング
- Docker Swarm
Dockerfile の判断ツリー
flowchart TD
Start[What are you building?] --> Lang{Language/runtime?}
Lang -->|Node.js/TypeScript| Node[Node pattern]
Lang -->|Python| Python[Python pattern]
Lang -->|Go| Go[Go pattern]
Lang -->|Rust| Rust[Rust pattern]
Lang -->|Static site| Static[Static pattern]
Node --> NQ{Need build step?}
NQ -->|Yes, TypeScript/bundler| MultiNode[Multi-stage: build + runtime]
NQ -->|No, plain JS| SingleNode[Single stage with slim base]
Python --> PQ{Package manager?}
PQ -->|pip| PipPattern[pip + venv pattern]
PQ -->|uv| UvPattern[uv pattern — fastest]
PQ -->|poetry| PoetryPattern[poetry export pattern]
Go --> GoMulti[Multi-stage: build + scratch/distroless]
Rust --> RustMulti[Multi-stage: build + debian-slim]
Static --> StaticMulti[Multi-stage: build + nginx/caddy]
言語別の本番環境向けパターン
Node.js / TypeScript(マルチステージ)
# Stage 1: Dependencies
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
# Stage 2: Build (TypeScript/bundler)
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 3: Production
FROM node:22-alpine AS production
WORKDIR /app
ENV NODE_ENV=production
# Security: non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY package.json ./
USER nextjs
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "dist/index.js"]
Python(uv — 最速)
FROM python:3.12-slim AS base
# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
WORKDIR /app
# Install dependencies (cached layer)
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev --no-editable
# Copy application code
COPY . .
# Non-root user
RUN useradd -r -s /bin/false appuser
USER appuser
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=3s \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
CMD ["uv", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Go(マルチステージ → Distroless)
# Build stage
FROM golang:1.22-alpine AS build
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /server ./cmd/server
# Production: distroless (no shell, no package manager, minimal attack surface)
FROM gcr.io/distroless/static-debian12
COPY --from=build /server /server
EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT ["/server"]
レイヤーキャッシュ戦略
flowchart TD
subgraph "Slow to change (cache hit)"
A[Base image] --> B[System packages]
B --> C[Language runtime deps]
end
subgraph "Medium change frequency"
C --> D[Application dependencies]
end
subgraph "Fast changing (cache miss OK)"
D --> E[Application code]
E --> F[Build step]
end
ルール:Dockerfile の命令は、変更頻度の低いものから高いものへ並べます。各命令でレイヤーが作成されます。レイヤーが変更されると、それ以降のすべてのレイヤーが再ビルドされます。
アンチパターン:依存関係より前に COPY する
初心者:
COPY . . # ← Busts cache on ANY file change
RUN npm install # ← Reinstalls everything every build
エキスパート:
COPY package.json package-lock.json ./ # ← Only busts on dependency changes
RUN npm ci # ← Cached when deps unchanged
COPY . . # ← Only app code changes trigger rebuild
背景:これは Docker のレイヤーキャッシュが導入されて以来のベストプラクティスですが、古いチュートリアルで学習した LLM は、今でも誤った順序を生成します。
開発向け Docker Compose
# docker-compose.yml
services:
app:
build:
context: .
dockerfile: Dockerfile
target: development # Use a dev-specific stage
ports:
- "${PORT:-3000}:3000"
volumes:
- .:/app # Hot reload via bind mount
- /app/node_modules # Anonymous volume: don't override node_modules
environment:
- NODE_ENV=development
- DATABASE_URL=postgresql://postgres:postgres@db:5432/myapp
depends_on:
db:
condition: service_healthy
develop:
watch: # Docker Compose Watch (2024+)
- action: sync
path: ./src
target: /app/src
- action: rebuild
path: package.json
db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: myapp
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
volumes:
pgdata:
アンチパターン:ヘルスチェックがない
初心者:depends_on だけに頼ります。しかし、これが待つのはコンテナの起動までであり、サービスの準備完了ではありません。
エキスパート:データベース/キャッシュサービスには必ず healthcheck を追加し、depends_on では condition: service_healthy を使用します。起動していても WAL のリカバリが終わっていない Postgres コンテナは、アプリをクラッシュさせることがあります。
イメージサイズの最適化
| ベースイメージ | サイズ | 使用する場面 |
|---|---|---|
node:22 | ~1.1 GB | 本番環境では使用しません |
node:22-slim | ~200 MB | apt パッケージが必要な場合 |
node:22-alpine | ~130 MB | デフォルトの選択肢 |
distroless | ~20 MB | Go/Rust のコンパイル済みバイナリ |
scratch | 0 MB | 完全な静的バイナリ |
chainguard/* | ~10-30 MB | セキュリティ強化された代替イメージ |
すぐにできる改善
# 1. Use --no-cache for apk/apt
RUN apk add --no-cache curl
# 2. Combine RUN commands to reduce layers
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/*
# 3. Use .dockerignore aggressively
# .dockerignore:
node_modules
.git
*.md
.env*
dist
coverage
.next
セキュリティ強化
# 1. Non-root user (MANDATORY)
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
USER appuser
# 2. Read-only filesystem (in compose)
# docker-compose.yml:
# read_only: true
# tmpfs:
# - /tmp
# 3. No new privileges
# docker run --security-opt no-new-privileges ...
# 4. Pin image digests for reproducibility
FROM node:22-alpine@sha256:abc123...
# 5. Scan for vulnerabilities
# docker scout quickview myimage:latest
# trivy image myimage:latest
アンチパターン:root で実行する
初心者:USER 命令を省略し、すべてを root として実行します。
エキスパート:コンテナ内で root として実行すると、コンテナから脱出された際に、攻撃者がホスト上でも root 権限を得ることになります。必ず root 以外のユーザーを作成して切り替えてください。root を使用するのは、ビルドステージでのパッケージインストール時に限ります。
検出方法:docker inspect --format='{{.Config.User}}' image:tag を実行します。値が空なら root です。
サービスの種類別ヘルスチェック戦略
原則:レディネスではなくライブネス
Docker HEALTHCHECK が答えるのは、「このプロセスは稼働していて、最低限の機能を果たせるか」という問いです。「すべての依存先に接続できるか」ではありません。後者はレディネス(Kubernetes の概念)です。この2つを混同すると、連鎖的な再起動が発生します。DB が停止すると、すべての API コンテナのヘルスチェックが「失敗」し、オーケストレーターがそれらをすべて再起動するため、DB の復旧時にアクセスが殺到します。
API サービス
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD wget -qO- http://localhost:${PORT}/health || exit 1
/health エンドポイントは、次の条件を満たす必要があります:
- プロセスが HTTP リクエストを処理できる場合に 200 を返す
- データベース接続を確認しない(それはレディネスの役割です)
- コストの高いクエリや計算を実行しない
- 100ms 未満で応答する(30秒ごとに実行されます)
// Minimal /health endpoint
app.get('/health', (req, res) => res.status(200).json({ status: 'ok' }));
監視ダッシュボード向けに、より詳細なヘルスチェック(DB の状態、キューの深さ、キャッシュのヒット率など)が必要な場合は、/health/detailed で公開し、Docker HEALTHCHECK には接続しないでください。
Compose での設定例:
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 30s
timeout: 3s
start_period: 10s
retries: 3
ワーカー/バックグラウンドジョブサービス
ワーカーは HTTP を提供しません。ハートビートファイルのパターンを使用します。
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD test $(find /tmp/worker-heartbeat -mmin -1 2>/dev/null | wc -l) -gt 0 || exit 1
ワーカーは、ジョブループの各反復が正常に完了するたびに、タイムスタンプファイルを書き込みます。
// Inside your worker loop
await processJob();
fs.writeFileSync('/tmp/worker-heartbeat', Date.now().toString());
ハートビートファイルが 1 分以上前のものであれば、ワーカーは停止しています。確認項目:プロセスが稼働していること、イベントループがブロックされていないこと、ジョブがキューから取り出されていること。
静的ファイルサーバー(nginx、Caddy)
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -qO- http://localhost:80/ || exit 1
起動期間は短く設定します。静的サーバーはすぐに起動するため、ページを提供できるか確認するだけで十分です。/health エンドポイントは必要ありません。
データベースコンテナ
ヘルスチェックには HTTP ではなく、データベース固有のクライアントを使用します。
# PostgreSQL
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
start_period: 30s # DBs are slow to start — generous grace period
retries: 5
# Redis
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
# MySQL
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
start_period: 30s
retries: 5
調整パラメーター
| パラメーター | 推奨事項 |
|---|---|
interval | アプリでは 30 秒、データベースでは 10 秒にします。短くすると、CPU 負荷とログのノイズが増えます。 |
timeout | 3~5 秒にします。ヘルスチェックにそれ以上かかる場合は、コストがかかりすぎています。 |
start_period | 最初のチェックを行うまでの時間です。静的サーバーでは 5 秒、API では 10 秒、データベースでは 30 秒、JVM アプリでは 60 秒以上にします。 |
retries | アプリでは 3 回、データベースでは 5 回にします。少なすぎると、一時的な障害で再起動します。 |
参考資料
references/multi-stage-patterns.md— 複雑なマルチステージビルドについては、BuildKit によるビルドキャッシュ、クロスコンパイル、モノレポの Dockerfile、Bun/Deno のパターンを参照してください。references/compose-patterns.md— 高度な docker-compose の使用方法については、プロファイル、extends、オーバーライドファイル、ネットワーク、シークレット管理、GPU パススルーを参照してください。