docker buildは速く、docker imageは小さいほうが良い

🛠️ 開発サイクルの短縮

  • フィードバックループの高速化
  • 無駄な再ビルドの削減
  • ローカル環境構築の軽量化

☁️ クラウドリソースのコスト最適化

  • ストレージと転送量の削減
  • CI/CD実行時間の短縮
  • ビルド・デプロイの効率化
  • イメージ再利用による無駄なビルド・デプロイの回避

🚀 本番環境でのデプロイ・起動速度向上

  • pull速度とスケール性能の向上
  • コンテナ起動の高速化
  • 不要ファイル除外による軽量化
  • ビルド・デプロイのためのコストを削減

🔐 セキュリティと保守性の向上

  • 不要ファイル削除により安全性が向上
  • 脆弱性スキャンの高速化
  • シンプルな構成とデバッグ性

高速化・最適化 tips 一覧

Dockerイメージを速く・小さくするためにやること。

tips 効果 説明
.dockerignore の活用 イメージ最適化 .dockerignore でテスト・ドキュメント・node_modules などを除外
不要ファイルの除外・クリーンアップ イメージ最適化 RUN rm -rf /var/lib/apt/lists/* などで不要ファイルを削除
軽量なベースイメージの選定 イメージ最適化 Alpine や slim 版などの軽量イメージを利用し、必要なパッケージのみ追加
レイヤーキャッシュ ビルド高速化 Dockerfile の命令ごとにキャッシュが効くように設計し、依存インストールを前に配置する
BuildKit キャッシュ ビルド高速化 BuildKit の –mount=type=cache で pip/npm などのキャッシュを活用
GitHub Actions でのキャッシュ CI/CD高速化 actions/cache で依存パッケージやビルド成果物を永続化・共有
マルチステージビルド ビルド高速化 & イメージ最適化 ビルド用と実行用のステージを分け、最終イメージに必要なものだけを残す

キャッシュの種類と保存場所について

Docker image のビルドを高速化する際に利用できる、いろいろなキャッシュ。

キャッシュ種別 主な用途・特徴 保存場所・管理方法 備考
レイヤーキャッシュ Dockerfile 各命令の結果をキャッシュ。再ビルドを短縮 ローカル Docker デーモン COPY, RUN など
BuildKit キャッシュ ビルド時の依存や中間成果物を細かくキャッシュ ローカル/外部ストレージ(BuildKit) --mount=type=cache
パッケージマネージャキャッシュ docker使用時に限らず存在する、npm/pip など依存パッケージのダウンロードキャッシュ 各パッケージマネージャのキャッシュディレクトリ ~/.npm, ~/.cache/pip など
CI/CD キャッシュ CI/CD 環境でのビルド成果物や依存キャッシュの永続化 actions/cache など CI/CD のストレージ GitHub Actions でのキャッシュなど
マルチステージビルド ビルド用と実行用を分離し不要ファイルを含めない - COPY --from=build ... で成果物のみ

.dockerignore の活用

docker build 時に「ビルドコンテキスト」に含めたくないファイルやディレクトリを除外する。

  • 主に Dockerfile の COPY や ADD 命令で送らないファイルやディレクトリを定義する

    • ビルド時に不要なファイル(例: テストコード、ドキュメント)を除外する。
    • 大きなファイルやディレクトリを除外することで、ビルドのアップロードや転送も高速化される。
    • 例:
      # .dockerignore の例
      node_modules
      dist
      *.log
      
  • ビルドコンテキストのサイズが大きいとアップロードに時間がかかるため、適切に除外設定を行う。
  • 「ビルド時のコンテキスト送信」 つまり、docker build の際にホストから送られるファイルが制限されるだけ。
  • ボリュームマウント(docker run -v)や docker compose の volumes には影響しない。
    • COPY/ADD 命令で指定したファイルが .dockerignore で除外されている場合、ビルド時に「ファイルが見つからない」エラーになる。
    • ビルドコンテキストのサイズが大きいとアップロードに時間がかかるため、適切な除外設定をする。
  • 誤って必要なファイルまで除外しないようにする。

不要ファイルのクリーンアップ

ソースコードをビルドした後などに、不要となったファイルやキャッシュを削除してイメージを小さくする方法。

  • ビルド時に生成される一時ファイルやキャッシュを削除。
  • 不要なパッケージやファイルを含めないようにする。
  • 例:
    # 不要なファイルを削除する RUN 命令
    RUN apt-get update && \
        apt-get install -y --no-install-recommends curl && \
        rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
    

軽量なベースイメージの選定

imageサイズが小さいと様々なメリットがあるので、状況が許す限り軽量なベースイメージを選ぶ。

よくある軽量ベースイメージ

  • scratch
    もっとも小さい空(から)のイメージ。共有ライブラリや動的リンクライブラリを必要としない静的バイナリをそのまま実行したい場合などに使う。(究極の軽量化)
  • busybox
    • 特に「シェルスクリプトだけ動かしたい」「最小限のUNIXコマンドが必要」という用途で便利。
    • alpine よりさらに小さく、scratch よりは機能が多い中間的な選択肢。
    • glibc ではなく musl libc ベースなので、バイナリ互換性や一部のライブラリ動作に注意が必要。
  • alpine
    • 最小限のパッケージのみを含むLinuxディストリビューション。
    • 多くの公式イメージで -alpine タグが用意されている。
    • glibc ではなく musl libc ベースなので、バイナリ互換性や一部のライブラリ動作に注意が必要。
  • debian:slim
    • 標準の debian イメージから不要なパッケージやドキュメントを省いた軽量版。
    • 必要最低限のパッケージ構成で、サイズを抑えつつ glibc ベースの安定した環境が使える。
    • 多くの公式イメージで -slim タグが用意されている。
    • Alpine ほど小さくはないが、バイナリ互換性やパッケージの豊富さがメリット。
  • gcr.io/distroless/*
    • Googleが提供する「distroless」イメージ。
    • アプリの実行に必要な最小限のファイルのみを含み、シェルやパッケージマネージャも含まない。

使い方

  • Alpine や Slim 版のイメージでサイズダウン。
  • セキュリティリスクを考慮し、不要なパッケージは含めない。
  • 例:
    FROM node:20-alpine
    
  • 軽量イメージに必要なパッケージが含まれていない場合があるので動作確認は必須。
  • ベースイメージの選定ミスによる脆弱性リスクに注意。
  • 軽量イメージは、標準的なイメージに比べて一部の処理で若干パフォーマンスが劣る場合がある。

レイヤーキャッシュ

  • Dockerfile の命令ごとにキャッシュを利用し、再ビルドを省略できる。

使い方

  • レイヤーキャッシュは「Dockerfileの各命令( RUN , COPY , ADD など)ごと」に作られる。
  • 依存関係のインストールやビルドを先に行うことでキャッシュを最大限活用。
    • あまり変化しないファイルを配置する処理を Dockerfile の前半に書く。
    • 頻繁に変わるファイル(例: アプリのソースコード)を配置する処理は Dockerfile の後半に配置。
    • 例1: Node.js プロジェクトの依存キャッシュ
      # 依存ファイルだけを先にコピーしてレイヤーキャッシュを使う
      COPY package.json package-lock.json ./
      RUN npm ci
      # アプリ本体を後からコピーすることで、依存が変わらない限りキャッシュが効く
      COPY . ./
      RUN npm run build
      
    • 例2: Python プロジェクトの依存キャッシュ
      # 依存ファイルのみを先にコピー
      COPY requirements.txt ./
      RUN pip install -r requirements.txt
      # アプリ本体を後からコピー
      COPY . ./
      CMD ["python", "main.py"]
      
    • 例3: OSパッケージのキャッシュ
      # OSパッケージのインストールとクリーンアップを1つのRUN命令でまとめてキャッシュ
      RUN apt-get update && \
          apt-get install -y --no-install-recommends \
          build-essential curl && \
          rm -rf /var/lib/apt/lists/*
      # ...
      
  • キャッシュが効かない場合は COPY/ADD の順序や内容を見直す。

レイヤーキャッシュのヒット条件

  • その命令の入力(以下)が前回のビルドと完全に同じ場合。
    • コマンド内容
    • コピー元ファイルの内容
    • タイムスタンプ
  • RUN命令のコマンド内容や引数が変わってもキャッシュは無効になる。
  • 例えば COPY package.json . の直後に RUN npm install がある場合、package.json が1バイトでも変われば、その後の RUN npm install 以降のキャッシュはすべて無効になる。
  • 逆に、COPY . . で全ファイルを一度にコピーすると、どれか1ファイルが変化するだけで以降のキャッシュがすべて無効になる。

キャッシュの無効化・クリア方法

  • 全キャッシュ無効化してビルド: docker build --no-cache .
  • 不要なイメージ削除: docker image prune
  • ビルドキャッシュを削除: docker builder prune

注意点

  • CIでキャッシュを使いたい場合は、そのままではキャッシュが効かない(GitHub Actions でのキャッシュ を参照)

BuildKit キャッシュ

  • 依存や中間成果物のビルドを効率化する仕組み。
  • 特に依存パッケージのダウンロードやビルド成果物のキャッシュで、ビルド時間を大幅に短縮できる。

使い方

  • Docker Desktopや最近のDocker Engineではデフォルトで有効。
    • 古いバージョンのdockerでBuildKitを有効にしてビルドするには、環境変数を指定。
      DOCKER_BUILDKIT=1 docker build .
      
  • RUN命令で --mount=type=cache オプションを使うことで、特定ディレクトリをキャッシュ。
  • パッケージマネージャのキャッシュディレクトリをキャッシュ対象にする。
    • 例: pip のキャッシュを活用(Pythonプロジェクト)
      # syntax=docker/dockerfile:1.4
      FROM python:3.11-slim
      
      # pipのキャッシュを有効化
      RUN --mount=type=cache,target=/root/.cache/pip \
          pip install -r requirements.txt
      
    • 例: npm のキャッシュを活用(Node.jsプロジェクト)
      # syntax=docker/dockerfile:1.4
      FROM node:20-alpine
      
      RUN --mount=type=cache,target=/root/.npm \
          npm ci
      
    • 例: aptキャッシュを活用(Debian/Ubuntu系)
      # syntax=docker/dockerfile:1.4
      FROM debian:slim
      
      RUN --mount=type=cache,target=/var/cache/apt \
          apt-get update && apt-get install -y curl
      
  • キャッシュの保存先(target)はプロジェクトや言語ごとに適切に設定する必要がある。

キャッシュの無効化・クリア方法

  • 一時的にキャッシュを使わない: --no-cache オプション
  • キャッシュが肥大化した場合は docker builder prune で整理。

注意点

  • キャッシュが効かない場合は、RUN命令やtargetパス、BuildKitのバージョンを見直し。
  • CIでキャッシュを使いたい場合は、そのままではキャッシュが効かない(GitHub Actions でのキャッシュ を参照)

GitHub Actions (CI) でのキャッシュ

  • CI/CD 環境でビルドや依存のダウンロードを高速化できる。
  • レイヤーキャッシュやビルドキャッシュは、あくまで ローカルのdockerデーモンが管理するキャッシュ のため、CI環境のように毎回新しいVMやコンテナが起動してビルドが実行される場合は、キャッシュが毎回リセットされてしまう。
  • CIでキャッシュを活用する場合は、 キャッシュディレクトリを永続化・共有する必要がある。
  • GitHub Actions 以外のCI/CDでも 何かしら工夫が必要なはず。

使い方

  • actions/cache で npm, pip, poetry などのパッケージキャッシュや BuildKit キャッシュを永続化。
  • 例: npm キャッシュ

    - name: Cache npm
      uses: actions/cache@v4
      with:
        path: ~/.npm
        key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
        restore-keys: |
          ${{ runner.os }}-npm-
    
  • 例: pip キャッシュ

    - name: Cache pip
      uses: actions/cache@v4
      with:
        path: ~/.cache/pip
        key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
        restore-keys: |
          ${{ runner.os }}-pip-
    
  • BuildKit キャッシュの永続化例

    - name: Cache Docker BuildKit
      uses: actions/cache@v4
      with:
        path: /tmp/.buildx-cache
        key: ${{ runner.os }}-buildx-${{ github.sha }}
        restore-keys: |
          ${{ runner.os }}-buildx-
    

キャッシュの無効化・クリア方法

  • キャッシュキーを変更して新しいキャッシュを作成。
  • キャッシュの保存先ディレクトリを変更。
  • キャッシュディレクトリを削除。

注意点

  • キャッシュサイズ制限(GitHub Actions では 10GB)に注意。
  • requirements.txt や package-lock.json の変更でキャッシュが効かなくなる場合がある。
  • キーの衝突や restore-keys の設計ミスでキャッシュがヒットしない場合がある。
  • キャッシュディレクトリのパスミスに注意。

マルチステージビルド

  • ビルドと実行環境を分けることで最終イメージを軽量化することが可能。
  • 最終イメージを小さくしたい場合や不要なファイルを含めたくない場合に有効。
  • ビルド部分だけを別ステージに分離してキャッシュ可能
  • あるステージの成果物を、複数の別ステージから再利用可能

使い方

  • FROM busybox AS downloader のように、ステージに名前をつける。
  • 他のステージから、 COPY --from=downloader /aaa /bbb のように、成果物などのファイルをコピーできる。
  • 最終的なイメージに、不要なファイルや依存関係を含めずに済む。
  • ソースコードを捨てて、ビルド済みファイルだけを含める例:
    FROM node:20 AS build
    WORKDIR /app
    COPY . .
    RUN npm install && npm run build
    
    # 実行ステージ
    FROM node:20 AS production
    WORKDIR /app
    COPY --from=build /app/dist ./dist
    CMD ["node", "dist/index.js"]
    
  • 特に効果を発揮する例(Goの静的バイナリビルド)
    # ビルド用ステージ(Go公式イメージを利用)
    FROM golang:1.22 AS builder
    WORKDIR /src
    COPY . .
    RUN go build -o myapp main.go
    
    # 実行用ステージ(最小のscratchイメージ)
    FROM scratch
    COPY --from=builder /src/myapp /myapp
    ENTRYPOINT ["/myapp"]
    
  • ツールのインストールだけのために別イメージを使う例
    例えば、busybox など一時的なツールを使って外部から実際のバイナリ(例: wget コマンドで yq をダウンロード)を取得し、最終イメージに成果物だけをコピーすることもできる。

    # busybox で wget を使って yq バイナリをダウンロード
    FROM busybox AS downloader
    WORKDIR /tmp
    RUN wget -O yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 && chmod +x yq
    
    # 最終イメージには yq バイナリだけを含める
    FROM scratch
    COPY --from=downloader /tmp/yq /yq
    ENTRYPOINT ["/yq"]