LESSON 25分

ビルダーパターン

ストーリー

「マルチステージビルドの基本はわかったか?」

「はい。ビルド用と本番用を分ければいいんですよね」

村上先輩が付け加えた。

「それだけじゃない。言語やフレームワークによって、最適なパターンがある。 Go のようなコンパイル言語なら、最終イメージにランタイムすら不要だ。 フロントエンドなら、ビルド成果物を Nginx で配信するパターンが定番だ。 いくつかのパターンを覚えておこう」


パターン1: Go アプリケーション

Go はコンパイル言語なので、バイナリだけを最終イメージにコピーできます。

dockerfile
# ビルドステージ
FROM golang:1.22-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .

# 静的リンクされたバイナリを作成
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server ./cmd/server

# 本番ステージ(scratch = 空のイメージ)
FROM scratch

COPY --from=builder /app/server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

EXPOSE 8080
ENTRYPOINT ["/server"]
ビルドステージ: ~500MB (Go ツールチェーン + ソースコード)
    ↓
本番イメージ: ~10MB (バイナリのみ)

distroless イメージ

scratch は最小限ですが、デバッグが難しいため Google の distroless イメージを使う選択肢もあります。

dockerfile
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]

パターン2: フロントエンド(React/Vue)

フロントエンドアプリをビルドし、静的ファイルを Nginx で配信するパターンです。

dockerfile
# ステージ1: ビルド
FROM node:20-alpine AS builder

WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

# ステージ2: Nginx で配信
FROM nginx:1.25-alpine

# カスタム Nginx 設定
COPY nginx.conf /etc/nginx/conf.d/default.conf

# ビルド成果物をコピー
COPY --from=builder /app/dist /usr/share/nginx/html

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
ビルドステージ: ~500MB (Node.js + 依存パッケージ)
    ↓
本番イメージ: ~25MB (Nginx + 静的ファイル)

nginx.conf の例

nginx
server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }

    location /api {
        proxy_pass http://api:3000;
    }
}

パターン3: Python(FastAPI)

dockerfile
# ステージ1: 依存パッケージのインストール
FROM python:3.12-slim AS builder

WORKDIR /app
RUN pip install --no-cache-dir poetry
COPY pyproject.toml poetry.lock ./
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes

# ステージ2: 本番イメージ
FROM python:3.12-slim

WORKDIR /app

COPY --from=builder /app/requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY src/ ./src/

RUN useradd -m appuser
USER appuser

EXPOSE 8000
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]

パターン4: テストを含むパイプライン

ビルドの中にテスト実行を含めることで、テストが通らなければイメージが作成されません。

dockerfile
# ステージ1: 依存パッケージ
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

# ステージ2: テスト
FROM deps AS test
COPY . .
RUN npm run lint
RUN npm run test

# ステージ3: ビルド
FROM deps AS builder
COPY . .
RUN npm run build

# ステージ4: 本番
FROM node:20-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
USER node
CMD ["node", "dist/server.js"]
bash
# テストまで実行
docker build --target test -t my-app:test .

# 本番イメージのビルド(テストが通った後のビルド成果物を使用)
docker build --target production -t my-app:prod .

各パターンのサイズ比較

パターンビルドステージ本番イメージ削減率
Go + scratch~500MB~10MB98%
React + Nginx~500MB~25MB95%
Node.js + Alpine~800MB~150MB81%
Python + slim~400MB~200MB50%

まとめ

ポイント内容
Go パターンコンパイルしてバイナリだけをコピー。scratch も使える
フロントエンドビルドして静的ファイルを Nginx で配信
Pythonpoetry/pip でエクスポートして slim イメージで実行
テスト統合ビルドパイプライン内にテストステージを含める

チェックリスト

  • Go アプリのマルチステージビルドパターンを理解した
  • フロントエンドの Nginx 配信パターンを理解した
  • テストをビルドパイプラインに統合する方法を理解した
  • 言語ごとの最適なパターンを選択できる

次のステップへ

次のセクションでは、イメージサイズのさらなる最適化テクニックを学びます。


推定読了時間: 25分