Docker: マルチステージビルド
最終更新:2026-08-26
イメージが 1.2 GB から 12 MB に縮小 — マルチステージビルドはイメージサイズ削減に最も効果的なテクニックです。
1. 学べること
- マルチステージビルドの原理と利点
- ビルドフェーズからのアーティファクトのコピー
- ランタイムに最適なベースイメージの選択
- イメージサイズ削減戦略
- キャッシュ最適化とデバッグテクニック
2. Go 開発者の実話
(1) 問題点: 1.2 GB のアプリケーショイメージ
Alice の Go アプリケーショイメージは 1.2 GB あります — golang:1.22 をベースイメージとして使用しているためで、完全な Go コンパイラ、ソースコード、ツールチェーンが含まれています。しかし本番環境に必要なのは 12 MB のコンパイル済みバイナリだけです。デプロイのたびにイメージのダウンロードに 5 分かかり、帯域幅 50 MB のクラウドサーバーはリリースのたびに遅くなります。
(2) マルチステージビルドのアプローチ
Bob がマルチステージビルドの使い方を教えました: 「最初のステージで golang:1.22 を使ってコンパイル; 2 番目のステージで Alpine からコンパイル出力をコピーする」
DOCKERFILE
# ステージ 1: ビルド
FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN go build -o server
# ステージ 2: 実行(バイナリのみ、Go コンパイラなし)
FROM alpine:3.19
COPY --from=builder /src/server /usr/local/bin/
CMD ["server"]
(3) メリット: イメージサイズが 1.2 GB から 12 MB に
イメージサイズが 100 分の 1 に縮小され、デプロイとダウンロードの時間が 5 分から 5 秒に短縮され、セキュリティ脆弱性も 200 以上から 10 へと減少しました(コンパイラが本番イメージに含まれないため)。
3. マルチステージビルドの原理
(1) シングルステージ vs マルチステージの比較
graph TB
subgraph Single["シングルステージビルド"]
S1["golang:1.22<br/>800 MB ベース"] --> S2["go build<br/>コンパイル出力"] --> S3["最終イメージ<br/>1.2 GB<br/>(コンパイラ + ソースコードを含む)"]
end
subgraph Multi["マルチステージビルド"]
M1["ステージ 1: golang:1.22<br/>800 MB(破棄)"] -->|"COPY --from"| M3["ステージ 2: alpine:3.19<br/>7 MB ベース"]
M1 --> M2["go build<br/>コンパイル出力"]
M2 -->|"バイナリデータのみコピー"| M3
M3 --> M4["最終イメージ<br/>12 MB<br/>(バイナリのみ)"]
end
(2) コアメカニズム
| ステージ | 目的 | 内容 | 最終イメージ |
|---|---|---|---|
| ビルダーステージ | コンパイル/ビルド | コンパイラ、ソースコード、ビルドツール | ❌ 破棄 |
| ランナーステージ | 実行 | 必要なアーティファクトのみコピー | ✅ 保持 |
📌 重要: マルチステージビルドはイメージの圧縮ではなく、不要な項目をイメージに含めないようにすることです。ビルダーステージからのすべてのレイヤーは破棄され、ランナーステージのレイヤーのみが最終イメージに含まれます。
4. マルチステージビルドのハンズオン
▶ サンプル: Go でのマルチステージビルド (難易度: ⭐⭐)
DOCKERFILE
# ============================================
# ステージ 1: Go バイナリのビルド
# ============================================
FROM golang:1.22 AS builder
WORKDIR /src
# 依存関係ファイルを最初にコピー(キャッシュ最適化)
COPY go.mod go.sum ./
RUN go mod download
# ソースコードをコピーしてビルド
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server
# ============================================
# ステージ 2: 最小限のランタイムイメージの作成
# ============================================
FROM alpine:3.19
# CA 証明書をインストール(HTTPS リクエスト用)
RUN apk --no-cache add ca-certificates
# コンパイル済みバイナリのみをビルダーからコピー
COPY --from=builder /src/server /usr/local/bin/server
# 非 root ユーザーで実行
RUN adduser -D -u 1000 appuser
USER appuser
EXPOSE 8080
CMD ["server"]
BASH
# マルチステージイメージをビルド
docker build -t go-app:1.0 .
# 最終イメージサイズを確認
docker images go-app:1.0
💻 出力:
TEXT
📖 参照専用
REPOSITORY TAG IMAGE ID SIZE
go-app 1.0 a1b2c3d4e5f6 12.5MB
▶ サンプル: Node.js でビルド → Nginx で実行 (難易度: ⭐⭐⭐)
React や Vue などのフロントエンドプロジェクト: ビルドフェーズで静的ファイルが生成され、ランタイムでは Nginx が HTTP リクエストを処理します。
DOCKERFILE
# ============================================
# ステージ 1: React アプリケーションのビルド
# ============================================
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ============================================
# ステージ 2: Nginx で配信
# ============================================
FROM nginx:1.25-alpine
# ビルド成果物をビルダーステージからコピー
COPY --from=builder /app/build /usr/share/nginx/html
# SPA ルーティング用のカスタム Nginx 設定
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
💡 ヒント: マルチステージビルドはフロントエンドプロジェクトで最も効果的です —
node_modules は 500 MB 以上になりがちですが、ビルド出力(HTML/CSS/JS)は通常わずか 5〜20 MB です。
▶ サンプル: Python venv のクローン (難易度: ⭐⭐)
Python プロジェクトもマルチステージでビルドできます: 最初のステージで仮想環境を作成し、2 番目のステージでは仮想環境とアプリケーションコードを単純にコピーします。
DOCKERFILE
# ステージ 1: 仮想環境のビルド
FROM python:3.12 AS builder
WORKDIR /app
COPY requirements.txt .
RUN python -m venv /opt/venv && \
/opt/venv/bin/pip install --no-cache-dir -r requirements.txt
# ステージ 2: venv のみで実行
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /opt/venv /opt/venv
COPY . .
ENV PATH="/opt/venv/bin:$PATH"
CMD ["python", "app.py"]
5. ランタイム用ベースイメージの選択
(1) 3 つのミニマルベースイメージの比較
| ベースイメージ | サイズ | シェル付属 | パッケージマネージャー付属 | libc 付属 | ユースケース |
|---|---|---|---|---|---|
scratch |
0 MB | ❌ | ❌ | ❌ | 静的コンパイルされた Go/Rust バイナリ |
distroless |
2〜20 MB | ❌ | ❌ | ✅ (glibc) | Google 製、極めてセキュア |
alpine |
7 MB | ✅ (ash) | ✅ (apk) | ✅ (musl) | デバッグが必要な場合 |
(2) Scratch イメージの注意点
DOCKERFILE
# scratch: イメージ内に何もない
FROM scratch
COPY --from=builder /src/server /server
CMD ["/server"]
⚠️ 注意:
scratch イメージにはシェル、ca-certificates、タイムゾーンデータが含まれていません。Go プログラムを scratch で動作させるには、静的コンパイル CGO_ENABLED=0 が必要です。HTTPS リクエストやタイムスタンプ付きログが必要な場合は、alpine を使用する方が安全です。
6. マルチステージビルドの高度なテクニック
▶ サンプル: ステージ命名と --target ビルド選択 (難易度: ⭐⭐)
DOCKERFILE
# 明確性のためのステージ命名
FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN go build -o server
# 代替: ホットリロードツール付きのデベロップメントステージ
FROM golang:1.22 AS dev
WORKDIR /src
RUN go install github.com/cosmtrek/air@latest
COPY . .
CMD ["air"]
# 本番: 最小限のランタイム
FROM alpine:3.19 AS prod
COPY --from=builder /src/server /usr/local/bin/
CMD ["server"]
BASH
# dev ステージのみビルド
docker build --target dev -t myapp:dev .
# prod ステージのみビルド
docker build --target prod -t myapp:prod .
▶ サンプル: --target で中間アーティファクトをデバッグ (難易度: ⭐⭐⭐)
BASH
# ビルダーステージまでビルドして確認
docker build --target builder -t myapp:builder .
# ビルダーイメージを実行してビルドの問題をデバッグ
docker run -it myapp:builder bash
# コンパイル済みバイナリを確認
docker run --rm myapp:builder ls -la /src/server
7. 言語別コンパイル出力サイズのリファレンス
| 言語 | ベースイメージ | ビルド出力 | 最終イメージ | 削減率 |
|---|---|---|---|---|
| Go | golang:1.22 (780 MB) | 静的バイナリ 12 MB | alpine: 12 MB | 65x |
| Rust | rust:1.75 (1.5 GB) | 静的バイナリ 8 MB | alpine: 15 MB | 100x |
| Node.js | node:20 (1.1 GB) | ビルド/ 5〜20 MB | nginx-alpine: 25 MB | 44x |
| Java | eclipse-temurin:21 (450 MB) | JAR 30 MB | jre-alpine: 170 MB | 2.6x |
| Python | python:3.12 (1 GB) | venv/ 50 MB | slim: 200 MB | 5x |
8. 完全なサンプル: Go Web アプリケーションのマルチステージビルド
DOCKERFILE
# ============================================
# Go Web アプリケーション用マルチステージ Dockerfile
# ステージ 1: 依存関係キャッシュ付きビルド
# ステージ 2: 最小限の Alpine ランタイム
# ============================================
# ---------- ステージ 1: ビルダー ----------
FROM golang:1.22-alpine AS builder
# git をインストール(プライベートリポジトリの go mod download 用)
RUN apk add --no-cache git
WORKDIR /src
# キャッシュ: 依存関係ファイルを最初にコピー
COPY go.mod go.sum ./
RUN go mod download
# ソースコードをコピー
COPY . .
# 静的バイナリをビルド(scratch/alpine 用に CGO_ENABLED=0)
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-w -s" -o /app/server
# ---------- ステージ 2: ランタイム ----------
FROM alpine:3.19
# ランタイム依存関係をインストール
RUN apk --no-cache add ca-certificates tzdata
# 非 root ユーザーを作成
RUN adduser -D -u 1000 appuser
WORKDIR /app
# バイナリのみをビルダーからコピー
COPY --from=builder /app/server .
# 所有権を設定
RUN chown -R appuser:appuser /app
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
CMD wget -qO- http://localhost:8080/health || exit 1
CMD ["/app/server"]
BASH
# ビルド
docker build -t go-web:1.0 .
# 実行
docker run -d -p 8080:8080 --name go-web go-web:1.0
# サイズとヘルスを確認
docker images go-web:1.0
docker inspect go-web --format='Size: {{.Size}}, Health: {{.State.Health.Status}}'
💻 出力:
TEXT
📖 参照専用
REPOSITORY TAG SIZE
go-web 1.0 15.2MB
Size: 15974400, Health: healthy
❓ よくある質問
Q マルチステージビルドはビルド速度に影響しますか?
A 大きな影響はありません。ビルダーステージのレイヤーは依然としてキャッシュされます — コードが変更されていなければ、
go build はキャッシュを直接使用します。最終イメージにビルダーレイヤーが含まれないだけです。ビルド速度はシングルステージビルドとほぼ同じですが、イメージサイズは大幅に縮小されます。Q マルチステージビルドをデバッグに使えますか?
A はい。
--target builder を使ってビルダーステージまでビルドし、docker run -it myapp:builder bash でデバッグ段階に入れます。Dockerfile に dev ステージを定義してデバッグツール(dlv や air など)を含めることもできます。Q scratch イメージでシェルがないのにどうやってデバッグしますか?
A scratch イメージにはシェルがないため、
docker exec で入ることはできません。デバッグ方法: ① --target builder を使ってデバッグイメージをビルド; ② 一時的に FROM scratch を FROM alpine に変更してデバッグ; ③ ホストで docker cp を使ってログファイルをコピーして取り出す。Q マルチステージビルドでシークレットを渡せますか?
A ビルダーステージにシークレットを含めないでください — ビルダーレイヤーは最終イメージに含まれませんが、
docker history で依然として表示されます。--mount=type=secret(BuildKit の機能)を使って安全にシークレットを渡します: RUN --mount=type=secret,id=ssh_key cp /run/secrets/ssh_key /root/.ssh/id_rsa。Q distroless イメージはセキュアですか?
A 非常にセキュアです。Google が管理する distroless イメージには、シェル、パッケージマネージャー、その他の不要なツールが含まれておらず、攻撃対象領域が極めて小さいです。ただし、デバッグが難しいため(シェルがないため)、開発には Alpine、本番には distroless を使用し、ログと監視による外部デバッグを組み合わせることを推奨します。
Q なぜ Go イメージは Scratch ではなく Alpine を使うのですか?
A Alpine は CA 証明書(HTTPS リクエストに必要)とタイムゾーンデータ(ログのタイムスタンプに必要)、緊急時のデバッグ用シェルを提供します。追加の 5 MB には十分な価値があります。Scratch は極端な最適化が必要なシナリオ(組み込みデバイスなど)でのみ使用されます。
📖 まとめ
- マルチステージビルド: ビルダーステージでコンパイルが行われ、ランナーステージではアーティファクトのみをコピーすることで、イメージサイズを GB から MB に縮小
FROM xxx AS builderでステージを命名、COPY --from=builderでステージ間コピー- ベースイメージの選択肢: scratch(0 MB) > distroless(2〜20 MB) > alpine(7 MB)
--targetで指定したステージまでビルドでき、デバッグや開発に役立つ- Go や Rust などのコンパイル言語が最も効果的(サイズ 100 倍削減)、Node.js フロントエンドプロジェクトが次ぐ(44 倍)
CGO_ENABLED=0+-ldflags="-w -s"は Go の静的コンパイルの標準パラメータ
📝 練習問題
- 基本問題 (難易度: ⭐): シングルステージビルドとマルチステージビルドで Go アプリケーショイメージのサイズを比較してください — まず
FROM golang:1.22でシングルステージビルドし、次にマルチステージビルドを行い、2 つのイメージのサイズ差を記録してください。 - 応用問題 (難易度: ⭐⭐): React アプリケーションをマルチステージでビルドします:
node:20-alpineでビルド →nginx:1.25-alpineで実行。Node.js ベースイメージと Nginx ベースイメージの最終イメージサイズを比較してください。 - 挑戦問題 (難易度: ⭐⭐⭐):
docker historyを使ってマルチステージイメージビルドの各レイヤーのサイズを分析し、ビルダーステージのレイヤーが最終イメージに含まれない理由を説明してください。