Dock Stay
イメージが肥大化している
動かない時

イメージが肥大化している

数 GB に膨らんだイメージを `dive` と `docker history` で分析し、不要なレイヤーとキャッシュを剥がして劇的に縮小する手順。

イメージが肥大化している diagram

症状

  • 簡単な Node.js / Python アプリなのにイメージが 2GB を超える
  • CI からのプッシュ・プルが毎回遅く、デプロイのボトルネックになっている
  • レジストリのストレージ料金が膨らんでいる
  • コンテナ脆弱性スキャン(Trivy など)の検出数が異常に多い(不要ツールが大量に入っている兆候)

原因

イメージが肥大化する典型パターン。

  • ベースイメージがフル版: node:20(Debian フル)は 1GB 超。node:20-slimnode:20-alpine で大幅に減る
  • ビルド依存を削ぎ落としていない: gcc / make / python-dev などネイティブモジュールビルドにだけ必要なパッケージが最終イメージに残っている
  • キャッシュを消していない: apt-get install した後の /var/lib/apt/lists/ が 100MB 以上、pip のキャッシュが ~/.cache/pip に数百 MB 残っている
  • レイヤー分離の失敗: 機密やログを含む巨大ファイルを一度 ADD してから次のレイヤーで rm している → 削除しても前のレイヤーには残るためサイズは減らない
  • マルチステージビルドを使っていない: ビルド用ツールチェインと実行用ランタイムが同じイメージに同居している
  • node_modules / target / pycache などを丸ごとコピー: .dockerignore が甘い

確認コマンド

現状把握が大事です。

どのレイヤーで何 MB 食っているかが分かれば、8 割は解決したようなもの。

bash
# サイズ全体
docker image ls myapp

# レイヤーごとのサイズと生成コマンド
docker history myapp:latest --no-trunc --format 'table {{.CreatedBy}}\t{{.Size}}'

# 対話的に全レイヤーの中身を探索(wagoodman/dive)
docker run --rm -it \
  -v /var/run/docker.sock:/var/run/docker.sock \
  wagoodman/dive:latest myapp:latest

# イメージをエクスポートして巨大ファイルを特定
docker save myapp:latest -o myapp.tar
tar -tvf myapp.tar | awk '{print $3, $6}' | sort -n | tail -30

# 間接的: コンテナ起動後にサイズの大きいファイルトップ 30 を探す
docker run --rm myapp:latest sh -c \
  'find / -xdev -type f -printf "%s %p\n" 2>/dev/null | sort -n | tail -30'

解決策

  • ベースを -slim / -alpine / distroless に変える: 200〜800MB が 50〜150MB まで縮む。distroless は最小だがデバッグ用シェルが無いので運用知識が必要
  • マルチステージビルドを使う: ビルド用ステージとランタイムステージを分け、最終イメージにはビルド済み成果物だけをコピーする
    FROM node:20 AS build
    WORKDIR /app
    COPY package*.json ./
    RUN npm ci
    COPY . .
    RUN npm run build
    
    FROM node:20-slim
    WORKDIR /app
    COPY --from=build /app/dist ./dist
    COPY --from=build /app/node_modules ./node_modules
    CMD ["node", "dist/server.js"]
    
  • apt のキャッシュを同じ RUN で削除する: 別 RUN だと削除されないので 同一レイヤー内 で消す
    RUN apt-get update \
        && apt-get install -y --no-install-recommends curl ca-certificates \
        && rm -rf /var/lib/apt/lists/*
    
  • pip install --no-cache-dir: Python のキャッシュを最初から作らない
  • .dockerignore を厳しくする: node_modules, .git, dist, coverage, *.log, .env* などは最低限除外
  • dive で CI に回帰防止を入れる: --ci --highestUserWastedPercent 0.1 で無駄なレイヤーがしきい値を超えたら CI を落とせる
  • **docker build --squash(実験的)や buildx--output type=image,rewrite-timestamp=true などでレイヤー数を抑える選択肢もあるが、まずは上の王道から