Contents
- 1 すぐ試せる短手順と期待効果(即効で確認するための最小フロー)
- 2 なぜ Docker イメージを最適化するか — 効果と計測項目
- 3 イメージの仕組み:レイヤーとOCI(正確な用語と再利用条件)
- 4 ベースイメージの選び方と実務的トレードオフ
- 5 マルチステージビルドの概念と言語別の実践例
- 6 RUN 命令の最適化・パッケージ別クリーンアップ・.dockerignore とレイヤー戦略
- 7 BuildKit / buildx、CI運用、キャッシュ管理と KPI 自動収集
- 8 セキュリティ運用上の注意点(企業運用向け)
- 9 推奨ツールと参考リンク(検証可能な一次情報)
- 10 まとめ(実務で優先すべき手順の要点)
すぐ試せる短手順と期待効果(即効で確認するための最小フロー)
まずは代表サービス1つを対象に短時間で効果を確認する手順と期待効果を示します。ここで示す手順はCIに組み込めるように最小限にまとめてあります。
短い実行フロー(チェックリスト)
まずは下記の順で1回ずつ実行して差分を確認してください。KPIは後段で自動収集例を示します。
- ベースライン計測(ビルド時間、圧縮転送サイズ、展開後サイズ、プル時間、脆弱性数)
- .dockerignore と COPY順を最適化して再ビルド
- マルチステージ化やRUN統合で再計測
- buildx のリモートキャッシュをCIに導入して再計測
- 差分を保存して継続監視(JSON/Artifacts)
期待される改善率(目安)
環境や言語による差は大きい点に注意してください。以下は現場でよく観測される目安です。
- イメージ圧縮サイズ: 10~70% 減(マルチステージ+不要ファイル除去で大きく改善)
- Cold ビルド時間: 20~60% 短縮(リモートキャッシュ有効時)
- Hot ビルド(キャッシュあり): 50~95% 短縮
- プル時間: イメージサイズに比例して低下(ネットワーク帯域次第)
重要コマンドと推奨フラグ
ここで挙げるコマンドは即効性が高く、まずはこれらでベースラインを取得してください。各コマンドは後段で用途別に詳述します。
DOCKER_BUILDKIT=1 docker build -f Dockerfile -t myapp:local .docker buildx create --name mybuilder --usedocker buildx build --platform linux/amd64,linux/arm64 --push --cache-to=type=registry,... --cache-from=type=registry,... -t registry/...:latest .- 圧縮サイズ概算:
docker save myapp:tag | gzip | wc -c - レジストリ側サイズ取得:
skopeo inspect --raw docker://registry/... | jq '...layers[].size...'
なぜ Docker イメージを最適化するか — 効果と計測項目
イメージ最適化は転送時間・ストレージ・セキュリティの改善に直結します。定義したKPIで比較し、改善の効果を定量的に示すことが重要です。
改善が運用に与える影響
イメージを小さくすると次の領域で効果が出ます。
- デプロイ/スケール時間の短縮(起動や更新の迅速化)
- CIランナー稼働時間削減とコスト低減
- レジストリ保存容量の低減(ストレージ/保持コスト)
- 攻撃面の縮小(不要パッケージを減らすと脆弱性が減る可能性)
追うべきKPI(定義と目安)
追跡すべき最低限の指標と簡単な目安を示します。
- 圧縮転送サイズ(registry にプッシュされる blob の合計、バイト/MB)
- 展開後サイズ(ランタイムでのディスク占有、バイト/MB)
- ビルド時間(Cold/Hot、秒)
- プル/プッシュ時間(秒)
- レイヤー数とキャッシュヒット率(%)
- 検出された脆弱性数(Trivy 等のツールで検出)
圧縮転送サイズと展開後サイズの具体的な計測コマンド
下記は現場で使える具体的なコマンド例です。各コードブロックの直前に何を達成するかを示します。
以下はローカルでイメージの圧縮転送量を概算する例です(gzip でさらに圧縮して概算)。
|
1 2 3 |
# ローカルイメージの圧縮サイズ概算(バイト) docker save myorg/myapp:tag | gzip | wc -c |
以下はレジストリのマニフェストから実際にストアされているレイヤー(圧縮)サイズを合計する例です。マルチアーキテクチャ(image index / manifest list)の場合は対象プラットフォームのマニフェストを選択してください。
|
1 2 3 4 5 6 7 8 |
# マニフェストリストから amd64 の manifest digest を取得(例) skopeo inspect --raw docker://registry.example.com/myorg/myapp:tag | \ jq -r '.manifests[] | select(.platform.architecture=="amd64" and .platform.os=="linux") | .digest' # 上で得た digest を使い、該当 manifest の layers[].size を合計 skopeo inspect --raw docker://registry.example.com/myorg/myapp@sha256:<digest> | \ jq '.layers | map(.size) | add' |
以下はローカル Docker デーモンが返す inspect 情報の主なフィールドの意味(簡潔)。
|
1 2 |
docker image inspect myorg/myapp:tag |
- .Id: イメージ ID(sha256:...)
- .RepoTags: タグ一覧
- .Size: デーモンが報告するイメージのバイト数(ストレージドライバに依存するため厳密さに注意)
- .VirtualSize: ベースイメージを含めた理論上のサイズ
- .RootFS.DiffIDs: コンテントの“非圧縮”ダイジェスト(各レイヤーの uncompressed digest)
注意点として、レジストリ側で実際に送受信されるのは manifest.layers[].digest(通常は圧縮された blob の digest)であり、サイズも layers[].size によって示されます。正確な「転送サイズ」はレジストリの manifest を合算するのが最も確実です。
イメージの仕組み:レイヤーとOCI(正確な用語と再利用条件)
イメージ内部の仕組みを正しく理解すると、最適化の効果をより確実に得られます。ここでは OCI 用語とレイヤー再利用の条件を正確に説明します。
レイヤー再利用の条件(ダイジェストの一致)
レイヤーが他イメージと共有されるのは、そのレイヤー blob のダイジェストが一致する場合のみです。ダイジェストは保存される blob の内容に基づくため、1バイトでも差分があれば異なる blob になります。
- レジストリで共有されるのは manifest.layers[].digest が一致する場合
- コンフィグ内の rootfs.diff_ids は非圧縮のダイジェストで、用途が異なる
- 圧縮形式や gzip レベルの違いで digest が変わるため、ビルド手順を揃えると再利用が増えます
image index / manifest list(マルチプラットフォーム配布)
OCI では「image index」が用語として使われます。Docker の文脈では「manifest list」と呼ばれることが多い点に注意してください。
- image index(manifest list)は複数プラットフォーム向けの manifest を束ねます
- プラットフォーム固有の manifest は個別に layers を持ち、共有は digest が一致するレイヤーのみ
- よってマルチアーキ構成ではプラットフォーム依存バイナリが多いとレイヤー共有が減ります
参考: OCI Image Specification(image-index / manifest の定義)を参照してください(下段の参考リンク参照)。
ベースイメージの選び方と実務的トレードオフ
ベースイメージは互換性とサイズ・保守性のトレードオフがあります。要件に応じて選定してください。
alpine
軽量で小さい反面、musl libc を使います。glibc 前提のネイティブモジュールで問題が出る場合があります。ネイティブバイナリが多いサービスでは注意が必要です。
slim(Debian/Ubuntu系)
glibc が必要なアプリケーションやネイティブモジュールが多い現場では安定して動作します。サイズは alpine より大きめですが互換性が高いです。
distroless
ランタイムに最小限のファイルしか含まれず攻撃面が小さくなります。シェルやパッケージ管理がなくデバッグが難しいため、デバッグ用イメージを別途用意する運用が望ましいです。
scratch
完全に空のイメージで、静的にリンクされたバイナリ向けです。必要なライブラリはすべてバイナリに含める必要があります。
選定チェックリスト(要点)
- ネイティブ依存の有無を確認する
- デバッグの要否(シェルやパッケージ管理)を検討する
- ベースの保守・更新頻度とセキュリティポリシーを確認する
企業向け注意:Docker Desktop と代替
Docker Desktop はローカル開発で便利ですが、企業利用のライセンス変更や有償化があるため、利用規約を確認してください。CI/サーバ環境やライセンス制約のある環境では次の代替を検討します。
- Podman / Buildah(rootless、OCI 準拠): https://podman.io/
- skopeo(レジストリ操作): https://github.com/containers/skopeo
- Kaniko / buildpacks(クラウド CI 向けの非デーモンビルド): https://github.com/GoogleContainerTools/kaniko
マルチステージビルドの概念と言語別の実践例
ビルド用とランタイム用を分離し、成果物のみを最終イメージに含めるのが基本です。以下に言語別の短い実例と注意点を示します。
Node.js
以下はビルド済み成果物と node_modules の最小化を行う一例です。ネイティブモジュールや musl 対応に注意してください(alpine でのビルドは glibc 前提のモジュールで問題が出ることがあります)。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# 以下は Node.js のマルチステージビルド例(ビルド→ランタイム) FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build && npm prune --production FROM node:18-alpine AS runtime WORKDIR /app ENV NODE_ENV=production COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules CMD ["node", "dist/index.js"] |
注: npm ci は package-lock.json 並びに整合性を前提とします。pnpm の場合は symlink や store の扱いに注意してください。
Python
ビルド依存を builder ステージに閉じ込め、wheel を最終イメージに持ち込むパターンです。C 拡張のあるパッケージはビルド環境で wheel 化してください。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# Python: ビルド依存で wheel を作成し最終イメージでインストール FROM python:3.11-slim AS builder WORKDIR /app COPY requirements.txt . RUN apt-get update && \ apt-get install -y --no-install-recommends build-essential gcc && \ pip wheel --no-cache-dir -r requirements.txt -w /wheels && \ apt-get purge -y --auto-remove build-essential gcc && \ rm -rf /var/lib/apt/lists/* FROM python:3.11-slim WORKDIR /app COPY --from=builder /wheels /wheels RUN pip install --no-cache-dir /wheels/* COPY . . CMD ["gunicorn", "app:app"] |
注: wheel を使うことで最終ステージにビルドツールを残しません。多くのライブラリで推奨される手法です。
Go
CGO を使わない静的ビルドで scratch に載せると非常に小さくできます。CGO を使う場合は glibc などの依存に注意してください。
|
1 2 3 4 5 6 7 8 9 10 11 |
# Go: 静的リンクで scratch に配置 FROM golang:1.20 AS builder WORKDIR /src COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /server ./cmd/server FROM scratch COPY --from=builder /server /server EXPOSE 8080 CMD ["/server"] |
注: CGO を無効にすると外部 C ライブラリが使えなくなります。必要なら distroless 等を検討してください。
Rust
release ビルドを行い、最終イメージにコピーします。musl ターゲットで静的化するとさらに小さくできますがクロスコンパイルの設定が必要です。
|
1 2 3 4 5 6 7 8 9 10 |
# Rust: release ビルドを用いて最終バイナリをコピー FROM rust:1.70 AS builder WORKDIR /usr/src/app COPY . . RUN cargo build --release FROM debian:bookworm-slim COPY --from=builder /usr/src/app/target/release/myapp /usr/local/bin/myapp CMD ["/usr/local/bin/myapp"] |
注: musl ターゲットで静的ビルドする場合はターゲットツールチェインの追加が必要です。
RUN 命令の最適化・パッケージ別クリーンアップ・.dockerignore とレイヤー戦略
RUN 命令の組み方と .dockerignore はビルドキャッシュと転送量に直結します。ここでは実務で使えるパターンを示します。
RUN 最適化の基本パターン
RUN の中でインストールとクリーンアップを一括することで不要レイヤーを残しません。set -eux を使って失敗を早期に検出します。
以下は apt ベースの最適化例です(ビルド内で wheel を作る等の処理を同一 RUN に入れています)。
|
1 2 3 4 5 6 7 8 |
# apt を使用する場合の RUN の例(同一レイヤー内でインストール→クリーンアップ) RUN set -eux; \ apt-get update; \ apt-get install -y --no-install-recommends build-essential libpq-dev; \ pip wheel --no-cache-dir -r requirements.txt -w /wheels; \ apt-get purge -y --auto-remove build-essential; \ rm -rf /var/lib/apt/lists/* |
以下は Alpine(apk)の例です。
|
1 2 3 4 5 6 |
# apk を使用する場合の RUN の例(virtual パッケージで一括削除) RUN set -eux; \ apk add --no-cache --virtual .build-deps build-base; \ pip wheel --no-cache-dir -r requirements.txt -w /wheels; \ apk del .build-deps |
パッケージマネージャ別クリーンアップ(要点)
各パッケージマネージャの注意点を簡潔に示します。
- apt:
--no-install-recommends、処理後に/var/lib/apt/lists/*を削除 - apk:
--no-cacheと virtual パッケージで一括削除 - pip:
pip install --no-cache-dir、ビルドは wheel 化して最終に wheel のみを持ち込む - npm:
npm ci(lockfile 必須)、最終にはnpm prune --productionを検討 - yarn:
yarn install --frozen-lockfile、production フラグの動作に注意 - pnpm:
pnpm install --frozen-lockfile --prod、pnpm の store と symlink に注意 - maven / gradle: ビルダーで jar を作成して最終イメージには成果物のみを持ち込む
.dockerignore と COPY の順序(要点)
.dockerignore はビルドコンテキスト転送を減らす最優先項目です。依存ファイル(package.json / requirements.txt 等)を先に COPY し、依存解決を行ってからソースを COPY することでキャッシュ効率を高めます。
例(.dockerignore の簡易例):
|
1 2 3 4 5 6 7 8 9 |
node_modules .git .env *.log tests/ docs/ *.md Dockerfile* |
ADD は自動解凍やリモート URL 取得の副作用があるため、理由がない限り COPY を使ってください。
BuildKit / buildx、CI運用、キャッシュ管理と KPI 自動収集
BuildKit と buildx はキャッシュ効率とマルチプラットフォームビルドで有効です。CI に組み込み、成果を自動で保存する運用パターンを示します。
buildx のキャッシュオプションと注意点
buildx の --cache-to / --cache-from / inline は強力ですが、動作は builder の種類や buildx バージョン、レジストリの対応に依存します。以下に注意点を示します。
- registry キャッシュ:
--cache-to=type=registryを使うとレジストリにキャッシュを保存できるが、push 権限が必要 - inline キャッシュ: イメージにキャッシュメタデータを埋め込みますが、古い buildx では multi-platform ビルドと互換性が低い場合がある
- 事前確認:
docker buildx version、docker buildx inspect --bootstrapで builder の機能を確認してください - CI での認証: キャッシュ push 用の権限は CI シークレットか OIDC を使って安全に付与すること
GitHub Actions の実践サンプル(buildx + registry キャッシュ + KPI 収集)
以下は BuildKit / buildx を使って image をビルド・プッシュし、ビルド時間と圧縮サイズを JSON に出力してアーティファクト保存する簡易ワークフローの抜粋例です(実行には secrets の設定と skopeo/jq の利用可否の確認が必要です)。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# 何を達成する例か: buildx でマルチアーキのイメージをビルドして registry キャッシュを使い、 # ビルド時間と圧縮サイズを metrics.json として保存してアーティファクト化します。 name: ci-build on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: set up buildx uses: docker/setup-buildx-action@v2 - name: login to registry uses: docker/login-action@v2 with: registry: registry.example.com username: ${{ secrets.REGISTRY_USERNAME }} password: ${{ secrets.REGISTRY_PASSWORD }} - name: build and push (with cache) id: build run: | set -eux start=$(date +%s) docker buildx build \ --platform linux/amd64,linux/arm64 \ --push \ --cache-to=type=registry,ref=registry.example.com/myorg/myapp:buildcache,mode=max \ --cache-from=type=registry,ref=registry.example.com/myorg/myapp:buildcache \ -t registry.example.com/myorg/myapp:${{ github.sha }} . end=$(date +%s) echo "BUILD_SECONDS=$((end-start))" >> $GITHUB_ENV - name: install introspection tools run: | sudo apt-get update sudo apt-get install -y skopeo jq - name: collect compressed size id: metrics run: | # multi-arch の場合は対象プラットフォームの manifest を選ぶ必要があります。 compressed_size=$(skopeo inspect --raw docker://registry.example.com/myorg/myapp:${{ github.sha }} | jq '.layers | map(.size) | add') echo "COMPRESSED_SIZE=$compressed_size" >> $GITHUB_ENV jq -n --arg sha "${{ github.sha }}" --argjson secs $BUILD_SECONDS --argjson size $compressed_size '{sha: $sha, build_seconds: $secs, compressed_size: $size}' > metrics.json - name: upload metrics uses: actions/upload-artifact@v3 with: name: image-metrics path: metrics.json |
注: 上記スクリプトは例であり、実際の CI 環境では skopeo のインストール手段や auth 情報の指定が環境に依存します。GitHub Actions の場合は docker/login-action でログインを行い、registry に対してキャッシュ push の権限があることを確認してください。
よくある失敗と対処
- COPY順が悪くキャッシュが効かない → 依存ファイル(lockfile等)を先に COPY
- dev 依存を残す → builder ステージで dev 依存を削除または production インストールを行う
- .dockerignore で重要ファイルを誤除外 → 変更時に差分を確認する運用を組む
- シークレットをイメージに埋める → BuildKit の
--secretを使い、ビルド後にシークレットが残らない設計にする
セキュリティ運用上の注意点(企業運用向け)
最小特権・非 root 実行・定期スキャンを運用ルールとして組み込みます。ツールと手順を自動化して運用負荷を下げます。
ベストプラクティス(要点)
- 実行ユーザーを可能な限り非 root にする
- ベースイメージは定期的に更新し、脆弱性スキャン(Trivy 等)を自動化する
- シークレットはビルド時に
--secret等で注入し、イメージ内に残さない - レジストリのアクセス権限は最小化し、キャッシュ保存用タグにも適切な権限を設定する
推奨ツールと参考リンク(検証可能な一次情報)
主要ツールと公式ドキュメントへのリンク、および運用上の目安バージョン(目安)を示します。実稼働前は各プロジェクトの最新版リリースノートを確認してください。
- hadolint(Dockerfile lint): https://github.com/hadolint/hadolint — 推奨: v2.x 系の最新安定版
- dive(レイヤー解析): https://github.com/wagoodman/dive — 推奨: v0.10.x 以上を目安
- Trivy(脆弱性スキャン): https://github.com/aquasecurity/trivy — 推奨: v0.40.x 以上を目安
- Docker Buildx(BuildKit): https://docs.docker.com/buildx/ — Docker CLI と BuildKit の組み合わせ(Docker 20.10+ を推奨)
- skopeo(レジストリ操作): https://github.com/containers/skopeo
- Podman / Buildah(Docker Desktop 代替): https://podman.io/
- Kaniko(非デーモンビルド): https://github.com/GoogleContainerTools/kaniko
- OCI Image Spec(image index / manifest の仕様): https://github.com/opencontainers/image-spec
まとめ(実務で優先すべき手順の要点)
短く実務的に優先順を示します。まずは代表サービス一つでベースラインを取り、順次適用・比較してください。
- まず計測: ビルド時間(Cold/Hot)、圧縮転送サイズ、プル時間、脆弱性数を取得する
- 早期改善: .dockerignore、COPY 順、RUN の統合、マルチステージ化を適用
- CI 化: buildx のリモートキャッシュを導入し、ビルド時間とキャッシュヒット率を定量的に監視する
- セキュリティ: 非 root 実行、定期スキャン、シークレットの安全な取り扱いを組み込む
- 継続改善: KPI を CI で記録(JSON や Prometheus へ出力)して効果を継続的に評価する
参考リンクは上記セクションを参照してください。