Dockerマルチステージビルド最適化:イメージスリム化からセキュリティ強化まで7つのキー戦略
DevOps
Dockerイメージが1.2GB、デプロイに5分かかる
Hello WorldのGoサービスを書いたら、Dockerイメージが800MB。Python依存を追加したら2GBに急増。CI/CDのビルドに8分、そのうち6分は依存のダウンロード。2026年、Dockerマルチステージビルド とBuildKit、distrolessイメージ、セキュリティスキャンを組み合わせれば、イメージをGB級からMB級に、ビルド時間を分単位から秒単位に削減できます。
本記事はマルチステージビルド原理から出発し、7つのキー最適化戦略を、イメージスリム化からセキュリティ強化まで完全ガイドします。
Dockerマルチステージビルドコア概念
| 概念 | 説明 |
|---|---|
| マルチステージビルド | 1つのDockerfileで複数のFROMステージを定義、最終成果物のみランタイムイメージにコピー |
| ビルドステージ | コンパイラツールチェーンを含むステージ、バイナリやビルド成果物を生成 |
| ランタイムステージ | ランタイム依存のみを含む最小イメージ |
| レイヤーキャッシュ | Dockerイメージの各レイヤーはキャッシュ可能、変更のないレイヤーはキャッシュを再利用 |
| BuildKit | Docker次世代ビルドエンジン、並列ビルド、キャッシュインポート/エクスポート対応 |
| distroless | Googleのディストリビューションレスベースイメージ、アプリランタイムのみ含む |
| COPY --from | 指定ステージからファイルをコピーする命令、マルチステージビルドのコア |
マルチステージビルドフロー
従来のビルド:
ソース → コンパイラインストール → コンパイル → ランタイムインストール → パッケージ → イメージ(2GB+)
全コンパイラツールと中間成果物がイメージに残る
マルチステージビルド:
Stage 1 (builder): ソース → コンパイラインストール → コンパイル → バイナリ生成
Stage 2 (runtime): Stage 1からバイナリをコピー → ランタイム依存のみ → イメージ(20MB)
コンパイラツールと中間成果物は破棄
問題分析:Dockerイメージ最適化の5つの課題
- イメージ肥大化:ベースイメージが大きすぎる、コンパイラツールの残留、冗長な依存でGB級イメージに
- ビルド速度低下:レイヤーキャッシュ無効化、依存の重複ダウンロード、直列ビルドでCI/CDが長時間化
- セキュリティ脆弱性:ベースイメージに多数のCVE、ランタイムに不要なシステムツールが攻撃者に悪用される
- 環境一貫性:dev/test/prodのイメージが不一致、「自分のマシンでは動く」問題
- ビルド保守性:Dockerfileが長すぎる、ロジックが混乱、理解と保守が困難
ステップバイステップ:7つのキー最適化戦略
戦略1:マルチステージビルド基礎——Goアプリケーション
FROM golang:1.22-bookworm AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-s -w" -o /app/server ./cmd/server
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /app/server /server
EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT ["/server"]
戦略2:マルチステージビルド——Node.jsアプリケーション
FROM node:20-bookworm-slim AS base
WORKDIR /app
RUN corepack enable
FROM base AS deps
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
FROM base AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm build
FROM base AS runner
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 appuser
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
USER appuser
EXPOSE 3000
CMD ["node", "dist/index.js"]
戦略3:マルチステージビルド——Pythonアプリケーション
FROM python:3.12-bookworm AS builder
WORKDIR /app
RUN pip install --no-cache-dir poetry
COPY pyproject.toml poetry.lock ./
RUN poetry config virtualenvs.in-project true && \
poetry install --no-dev --no-interaction --no-ansi
FROM python:3.12-slim-bookworm AS runtime
RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /app
COPY --from=builder /app/.venv .venv
COPY . .
ENV PATH="/app/.venv/bin:$PATH"
ENV PYTHONUNBUFFERED=1
USER appuser
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
戦略4:レイヤーキャッシュ最適化
FROM node:20-bookworm-slim AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src/ ./src/
RUN npm run build
FROM node:20-bookworm-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/index.js"]
戦略5:BuildKitキャッシュマウント
# syntax=docker/dockerfile:1
FROM golang:1.22-bookworm AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-s -w" -o /app/server ./cmd/server
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /app/server /server
EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT ["/server"]
戦略6:セキュリティ強化——Trivyスキャン + 最小権限
FROM python:3.12-slim-bookworm AS builder
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir --user -r requirements.txt
FROM python:3.12-slim-bookworm
RUN apt-get update && \
apt-get install -y --no-install-recommends dumb-init && \
apt-get clean && rm -rf /var/lib/apt/lists/*
RUN groupadd -r appuser && useradd -r -g appuser -s /sbin/nologin appuser
WORKDIR /app
COPY --from=builder /root/.local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY . .
RUN chmod -R 555 /app && \
chmod -R 444 /app/*.py
USER appuser
EXPOSE 8000
ENTRYPOINT ["dumb-init", "--"]
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
戦略7:distroless + マルチアーキテクチャビルド
FROM --platform=$BUILDPLATFORM golang:1.22-bookworm AS builder
ARG TARGETPLATFORM
ARG BUILDPLATFORM
ARG TARGETOS
ARG TARGETARCH
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
go build -ldflags="-s -w" -o /app/server ./cmd/server
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /app/server /server
EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT ["/server"]
落とし穴ガイド
落とし穴1:全ファイルをビルドステージにCOPY
# ❌ 誤り:COPY . .をRUNの前に配置、ファイル変更でキャッシュが無効化
COPY . .
RUN npm ci
# ✅ 正しい:依存ファイルを先にコピー、その後ソースコード
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
落とし穴2:latestタグをベースイメージに使用
# ❌ 誤り:latestタグは再現不可能、異なる時間のビルド結果が異なる可能性
FROM node:latest
# ✅ 正しい:明示的なバージョン番号とディストリビューションを使用
FROM node:20.11.0-bookworm-slim
落とし穴3:マルチステージでCOPY --fromを忘れる
# ❌ 誤り:ランタイムステージでコンパイラツールを再インストール
FROM python:3.12-slim
RUN pip install build-tools
COPY . .
RUN python -m build
# ✅ 正しい:ビルドステージでコンパイル、ランタイムステージは成果物のみコピー
FROM python:3.12 AS builder
COPY . .
RUN pip install --user -r requirements.txt
FROM python:3.12-slim
COPY --from=builder /root/.local /root/.local
落とし穴4:rootユーザーでコンテナを実行
# ❌ 誤り:デフォルトでroot実行、コンテナ脱出で攻撃者がroot権限を取得
FROM node:20-slim
COPY . .
CMD ["node", "index.js"]
# ✅ 正しい:非rootユーザーを作成して切り替え
FROM node:20-slim
RUN groupadd -r appuser && useradd -r -g appuser appuser
COPY . .
USER appuser
CMD ["node", "index.js"]
落とし穴5:apt/pipキャッシュのクリーンアップ忘れ
# ❌ 誤り:キャッシュがイメージレイヤーに残り、後で削除してもサイズが増加
RUN apt-get update && apt-get install -y curl
RUN pip install requests
# ✅ 正しい:同じRUN命令でインストールとクリーンアップ
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
apt-get clean && rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir requests
エラートラブルシューティング
| # | エラーメッセージ | 原因 | 解決方法 |
|---|---|---|---|
| 1 | COPY failed: file not found in build context |
COPYパスエラーまたはファイルが存在しない | .dockerignoreとファイルパスを確認 |
| 2 | executor failed running [/bin/sh -c ...] |
RUNコマンド実行失敗 | 依存バージョン、ネットワーク、コマンド構文を確認 |
| 3 | no matching manifest for linux/arm64 |
ベースイメージがターゲットアーキテクチャ非対応 | --platformを使用またはマルチアーキベースイメージを選択 |
| 4 | OOM killed during build |
ビルド中のメモリ不足 | Dockerメモリ制限を増加、ビルドステップを最適化 |
| 5 | denied: requested access to the resource is denied |
レジストリへのプッシュ権限なし | docker loginとレジストリ権限を確認 |
| 6 | max depth exceeded |
Dockerfile命令レベルが深すぎる | Dockerfileを簡素化、ネストを減らす |
| 7 | failed to solve: failed to compute cache key |
BuildKitキャッシュ計算失敗 | --mount=type=cacheのターゲットパスを確認 |
| 8 | user not found |
distrolessイメージにshellやユーザー管理ツールがない | 前のステージでユーザーを作成、またはnonrootバリアントを使用 |
| 9 | signal: killed |
ビルドプロセスがシステムOOM Killerで終了 | スワップまたはDockerメモリ制限を増加 |
| 10 | multiple platforms feature is currently not supported |
builderインスタンスがマルチアーキ非対応 | docker buildx createでマルチアーキ対応builderを作成 |
高度な最適化
1. イメージサイズ比較スクリプト
#!/bin/bash
echo "=== Docker Image Size Comparison ==="
images=(
"myapp:before-optimization"
"myapp:after-multistage"
"myapp:after-distroless"
)
for img in "${images[@]}"; do
if docker image inspect "$img" > /dev/null 2>&1; then
size=$(docker image inspect "$img" --format='{{.Size}}')
size_mb=$(echo "scale=2; $size / 1024 / 1024" | bc)
layers=$(docker image inspect "$img" --format='{{len .RootFS.Layers}}')
echo "$img: ${size_mb}MB (${layers} layers)"
else
echo "$img: not found"
fi
done
echo ""
echo "=== Layer Details (smallest image) ==="
docker history myapp:after-distroless --format "table {{.CreatedBy}}\t{{.Size}}" --no-trunc
2. Docker Composeマルチステージ開発/本番設定
services:
app-dev:
build:
context: .
target: builder
dockerfile: Dockerfile
volumes:
- .:/app
- /app/node_modules
ports:
- "3000:3000"
environment:
- NODE_ENV=development
command: npm run dev
app-prod:
build:
context: .
target: runner
dockerfile: Dockerfile
cache_from:
- myregistry/myapp:cache
ports:
- "3000:3000"
environment:
- NODE_ENV=production
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
3. CI/CDイメージビルド最適化パイプライン
name: Build and Push
on:
push:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix=
type=ref,event=branch
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
build-args: |
BUILD_DATE=${{ github.event.head_commit.timestamp }}
VCS_REF=${{ github.sha }}
比較分析
| 次元 | シングルステージ | マルチステージ | マルチステージ+distroless | マルチステージ+Alpine | Scratch |
|---|---|---|---|---|---|
| Goイメージサイズ | ~800MB | ~20MB | ~2MB | ~8MB | ~1.5MB |
| Nodeイメージサイズ | ~1.2GB | ~200MB | N/A | ~120MB | N/A |
| Pythonイメージサイズ | ~1.5GB | ~150MB | ~50MB | ~80MB | N/A |
| セキュリティCVE数 | 高(200+) | 中(50+) | 低(<5) | 中(30+) | 最低(0) |
| デバッグ能力 | ✅フル | ✅良い | ❌シェルなし | ⚠️制限あり | ❌なし |
| ビルド複雑さ | 低 | 中 | 中 | 中 | 高 |
| 互換性 | ✅ | ✅ | ⚠️CGO制限 | ⚠️musl互換 | ❌静的コンパイルのみ |
まとめ:Dockerマルチステージビルドはイメージ最適化の基盤——コンパイルとランタイムを分離し、最終成果物のみを保持。7つのキー戦略は段階的に構築:1)マルチステージ基礎、2)言語固有最適化、3)Python仮想環境、4)レイヤーキャッシュ順序、5)BuildKitキャッシュマウント、6)セキュリティ強化+Trivyスキャン、7)distroless+マルチアーキ。本番推奨:Goはdistroless/static、Nodeはslim+非root、Pythonはslim+仮想環境。キー原則:最小ベースイメージ→最小権限→最小攻撃面。
オンラインツール推奨
- Base64エンコード/デコード:/ja/encode/base64
- Hash計算:/ja/encode/hash
- JSONフォーマッター:/ja/json/format
ブラウザローカルツールを無料で試す →
#Docker#多阶段构建#镜像优化#安全加固#BuildKit#2026#DevOps