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 マルチステージの比較

100%
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 scratchFROM 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 は極端な最適化が必要なシナリオ(組み込みデバイスなど)でのみ使用されます。

📖 まとめ


📝 練習問題

  1. 基本問題 (難易度: ⭐): シングルステージビルドとマルチステージビルドで Go アプリケーショイメージのサイズを比較してください — まず FROM golang:1.22 でシングルステージビルドし、次にマルチステージビルドを行い、2 つのイメージのサイズ差を記録してください。
  2. 応用問題 (難易度: ⭐⭐): React アプリケーションをマルチステージでビルドします: node:20-alpine でビルド → nginx:1.25-alpine で実行。Node.js ベースイメージと Nginx ベースイメージの最終イメージサイズを比較してください。
  3. 挑戦問題 (難易度: ⭐⭐⭐): docker history を使ってマルチステージイメージビルドの各レイヤーのサイズを分析し、ビルダーステージのレイヤーが最終イメージに含まれない理由を説明してください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%