Dockerコンテナパターン集: マルチステージビルドからヘルスチェックまで
Dockerコンテナは現代のアプリケーション開発に欠かせません。しかし、シンプルなDockerfileを書くのと、本番環境で運用できる最適化されたコンテナを作るのは別物です。本記事では、2026年の最新ベストプラクティスに基づいた実践的なDockerパターンを解説します。
マルチステージビルドパターン
基本パターン: Node.js
# ビルドステージ
FROM node:20-alpine AS builder
WORKDIR /app
# 依存関係のインストール(キャッシュ活用)
COPY package*.json ./
RUN npm ci --only=production && \
npm cache clean --force
# ソースコードのコピーとビルド
COPY . .
RUN npm run build
# 本番ステージ
FROM node:20-alpine AS production
# セキュリティ: 非rootユーザーで実行
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
WORKDIR /app
# ビルド成果物のみコピー
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/package*.json ./
USER nodejs
EXPOSE 3000
# ヘルスチェック
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
CMD ["node", "dist/main.js"]
高度なパターン: Next.js
# 依存関係ステージ
FROM node:20-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# ビルダーステージ
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# 環境変数(ビルド時のみ必要なもの)
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build
# 本番依存関係ステージ
FROM node:20-alpine AS prod-deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production && \
npm cache clean --force
# ランナーステージ
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
# public, .next/static をコピー
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s \
CMD node -e "require('http').get('http://localhost:3000/api/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
CMD ["node", "server.js"]
Python Webアプリケーションパターン
FastAPI + Poetry
# ベースイメージ
FROM python:3.12-slim AS base
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 \
POETRY_VERSION=1.7.1 \
POETRY_HOME="/opt/poetry" \
POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_CREATE=false
ENV PATH="$POETRY_HOME/bin:$PATH"
# ビルダーステージ
FROM base AS builder
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl \
build-essential && \
curl -sSL https://install.python-poetry.org | python3 - && \
apt-get purge -y --auto-remove curl && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY pyproject.toml poetry.lock ./
RUN poetry install --only main --no-root
COPY . .
RUN poetry install --only main
# 本番ステージ
FROM base AS production
RUN apt-get update && \
apt-get install -y --no-install-recommends \
libpq5 && \
rm -rf /var/lib/apt/lists/* && \
useradd -m -u 1001 appuser
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
COPY --from=builder --chown=appuser:appuser /app /app
USER appuser
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD python -c "import requests; requests.get('http://localhost:8000/health').raise_for_status()"
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Go アプリケーションパターン
最小サイズイメージ
# ビルドステージ
FROM golang:1.22-alpine AS builder
RUN apk add --no-cache git ca-certificates tzdata
WORKDIR /build
# 依存関係のキャッシュ
COPY go.mod go.sum ./
RUN go mod download
# ビルド
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags='-w -s -extldflags "-static"' \
-a \
-o app \
./cmd/server
# 本番ステージ(scratch使用)
FROM scratch
# タイムゾーンデータとCA証明書をコピー
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /etc/group /etc/group
# バイナリのみコピー
COPY --from=builder /build/app /app
# 非rootユーザー(builder内で作成)
USER nobody:nobody
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD ["/app", "healthcheck"]
ENTRYPOINT ["/app"]
Distrolessパターン
FROM golang:1.22-alpine AS builder
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o app ./cmd/server
# Distroless(最小限のOS)
FROM gcr.io/distroless/static-debian12
COPY --from=builder /build/app /app
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/app"]
ヘルスチェックパターン
Node.js HTTPヘルスチェック
# Dockerfileで定義
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD node /healthcheck.js
// healthcheck.js
const http = require('http');
const options = {
host: 'localhost',
port: process.env.PORT || 3000,
path: '/health',
timeout: 2000,
};
const request = http.request(options, (res) => {
console.log(`Health check status: ${res.statusCode}`);
process.exit(res.statusCode === 200 ? 0 : 1);
});
request.on('error', (err) => {
console.error('Health check failed:', err);
process.exit(1);
});
request.end();
PostgreSQL接続チェック
HEALTHCHECK --interval=30s --timeout=3s \
CMD pg_isready -U $POSTGRES_USER -d $POSTGRES_DB || exit 1
Redis接続チェック
HEALTHCHECK --interval=30s --timeout=3s \
CMD redis-cli ping || exit 1
セキュリティパターン
最小権限の原則
FROM node:20-alpine
# 1. セキュリティアップデート
RUN apk upgrade --no-cache
# 2. 非rootユーザー作成
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
WORKDIR /app
# 3. ファイルの適切な権限設定
COPY --chown=nodejs:nodejs package*.json ./
RUN npm ci --only=production && \
npm cache clean --force
COPY --chown=nodejs:nodejs . .
# 4. 読み取り専用ファイルシステム(可能な場合)
RUN mkdir -p /app/tmp && chown nodejs:nodejs /app/tmp
USER nodejs
# 5. 不要なポート公開を避ける
EXPOSE 3000
CMD ["node", "server.js"]
シークレット管理
# ビルド時のシークレット使用(BuildKit)
# docker buildx build --secret id=npmrc,src=$HOME/.npmrc .
FROM node:20-alpine AS builder
WORKDIR /app
# シークレットを一時的にマウント
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm ci
COPY . .
RUN npm run build
# シークレットは本番イメージに含まれない
FROM node:20-alpine
COPY --from=builder /app/dist ./dist
CMD ["node", "dist/main.js"]
キャッシュ最適化パターン
レイヤーキャッシュの活用
FROM node:20-alpine
WORKDIR /app
# 1. 依存関係定義のみコピー(変更頻度が低い)
COPY package*.json ./
# 2. 依存関係インストール(キャッシュされる)
RUN npm ci --only=production
# 3. ソースコード(変更頻度が高い)
COPY . .
CMD ["node", "server.js"]
BuildKit キャッシュマウント
# syntax=docker/dockerfile:1.4
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
# npm キャッシュをマウント
RUN --mount=type=cache,target=/root/.npm \
npm ci --only=production
COPY . .
CMD ["node", "server.js"]
マルチアーキテクチャビルド
# syntax=docker/dockerfile:1.4
FROM --platform=$BUILDPLATFORM node:20-alpine AS builder
ARG TARGETPLATFORM
ARG BUILDPLATFORM
RUN echo "Building on $BUILDPLATFORM for $TARGETPLATFORM"
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/main.js"]
# ビルド
docker buildx build \
--platform linux/amd64,linux/arm64,linux/arm/v7 \
-t myapp:latest \
--push \
.
開発環境最適化
ホットリロード対応
# Dockerfile.dev
FROM node:20-alpine
WORKDIR /app
# グローバルツールのインストール
RUN npm install -g nodemon
# 依存関係
COPY package*.json ./
RUN npm install
# ソースはvolume マウント
CMD ["nodemon", "--watch", "src", "src/index.js"]
# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- ./src:/app/src
- /app/node_modules # node_modulesは除外
ports:
- "3000:3000"
environment:
- NODE_ENV=development
モノレポ対応パターン
# syntax=docker/dockerfile:1.4
FROM node:20-alpine AS base
WORKDIR /app
# ルートの依存関係
COPY package*.json ./
COPY turbo.json ./
COPY packages/shared/package*.json ./packages/shared/
# 特定アプリの依存関係
COPY apps/api/package*.json ./apps/api/
RUN npm install
# ビルド
FROM base AS builder
COPY . .
# 特定アプリのみビルド
RUN npx turbo run build --filter=api
# 本番
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/apps/api/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/main.js"]
デバッグパターン
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
# デバッグポート公開
EXPOSE 3000 9229
# デバッグモードで起動
CMD ["node", "--inspect=0.0.0.0:9229", "src/index.js"]
# docker-compose.debug.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
- "9229:9229" # デバッガー接続用
volumes:
- ./src:/app/src
まとめ
本記事で紹介したDockerパターンを適切に使うことで、以下が実現できます。
パフォーマンス
- マルチステージビルドでイメージサイズを削減
- レイヤーキャッシュで高速ビルド
- BuildKitで並列ビルド
セキュリティ
- 非rootユーザー実行
- 最小限のベースイメージ
- シークレット漏洩防止
運用性
- ヘルスチェックで自動復旧
- マルチアーキテクチャ対応
- 開発環境と本番環境の分離
2026年現在、これらのパターンは本番環境での運用に欠かせないベストプラクティスとなっています。
参考リンク