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つの課題

  1. イメージ肥大化:ベースイメージが大きすぎる、コンパイラツールの残留、冗長な依存でGB級イメージに
  2. ビルド速度低下:レイヤーキャッシュ無効化、依存の重複ダウンロード、直列ビルドでCI/CDが長時間化
  3. セキュリティ脆弱性:ベースイメージに多数のCVE、ランタイムに不要なシステムツールが攻撃者に悪用される
  4. 環境一貫性:dev/test/prodのイメージが不一致、「自分のマシンでは動く」問題
  5. ビルド保守性: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+仮想環境。キー原則:最小ベースイメージ→最小権限→最小攻撃面。


オンラインツール推奨

ブラウザローカルツールを無料で試す →

#Docker#多阶段构建#镜像优化#安全加固#BuildKit#2026#DevOps