言語を切り替える
テーマを切り替える

GitHub Actions キャッシュ戦略:CI/CD パイプラインを 5 倍高速化

npm install に 3 分 15 秒。

これは去年引き継いだプロジェクトの CI ビルド時間です。コードをプッシュするたび、GitHub Actions のログがくるくる回るのを眺めて、緑のチェックマークが出るのを待っていました。正直なところ、その間は別のウィンドウに切り替えて休憩していました——どうせ待つ必要がありますから。

その後キャッシュを追加したら、同じビルドが 40 秒で完了しました。約 5 倍速くなったのです。

これは魔法ではありません。GitHub Actions のキャッシュ戦略を正しく設定しただけです。今回は、ハマった罠、テストしたデータ、そしてそのままコピーして使える設定テンプレートをまとめました。CI ビルドを待っている時間があるなら、この記事でかなりのコーヒータイムを節約できるかもしれません。

一、キャッシュメカニズムの核心概念

まず、キャッシュがどのように動作するかを理解しておかないと、設定時に罠にハマりやすくなります。

GitHub Actions のキャッシュメカニズムは実にシンプルです——たった 3 ステップ:検索 → 復元 → 保存key を定義すると、GitHub は全キャッシュから一致するものを探します。見つかれば、作業ディレクトリに直接復元されます。見つからなければ、タスク完了後に新しいキャッシュが保存されます。

ただし、いくつかの制限を知っておく必要があります:

制限項目数値
リポジトリごとのキャッシュ上限10 GB
単一キャッシュファイルの上限5 GB(実際は 1GB を超えると問題が起きやすい)
キャッシュ保持期間7 日間アクセスなしで削除
グローバル同時アップロード制限最大 5 つのキャッシュを同時アップロード

10GB の制限にハマった人を見たことがあります——プロジェクトの依存関係が多すぎて、キャッシュがどんどん大きくなり、最終的に新しいキャッシュが保存できず、古いものは削除され、毎回のビルドが「コールドスタート」になります。

もう一つ混同しやすい点:Cache と Artifact は別物です。Cache は CI 用で、高速化を追求します。Artifact は人間用で、ビルド成果物やテストレポートなど、長期保存が必要なものです。Cache には 10GB 制限がありますが、Artifact には上限がありません(ただしリポジトリのストレージを消費します)。

また、Docker Layer Cache というものもあります。これは Docker ビルド専用で、通常のキャッシュとはロジックが少し違います。後で別途説明します。

二、キャッシュキー設計戦略

キャッシュがヒットするかどうかは、すべて key の設計にかかっています。これがキャッシュ戦略の核心です。

hashFiles() とは

GitHub には hashFiles() という組み込み関数があり、ファイルのハッシュ値を計算できます。よく package-lock.jsonyarn.lock に使われます——依存関係が変わらなければハッシュも変わらず、キャッシュがヒットします。

key: npm-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}

これは npm-Linux-a1b2c3d4e5f6... のようなキーを生成します。package-lock.json が変わらなければ、このキーも変わりません。

restore-keys:バックアッププラン

しかし、依存関係はいつか更新されます。そこで restore-keys が必要です。これは「フォールバックマッチング」メカニズムです:

- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      npm-{{ runner.os }}-

完全な key に優先的にマッチします。マッチしなければ? npm-Linux- で始まる古いキャッシュを探します。完全にヒットしなくても、node_modules の大部分はすでにあるため、新しい依存関係をインクリメンタルにインストールするだけで済みます。

3 つのキー命名パターンの比較

テストした結果、この 3 つのパターンをおすすめします:

シンプルパターン(小規模プロジェクト向け):

key: {{ runner.os }}-node-{{ hashFiles('**/package-lock.json') }}

バージョンパターン(複数 Node バージョン向け):

key: {{ runner.os }}-node{{ matrix.node-version }}-{{ hashFiles('**/package-lock.json') }}

マルチパスパターン(monorepo 向け):

key: {{ runner.os }}-{{ hashFiles('**/package-lock.json', '**/yarn.lock') }}

キャッシュがヒットしたかどうかの確認

actions/cachecache-hit 変数を出力します:

- uses: actions/cache@v4
  id: cache-npm
  with:
    path: ~/.npm
    key: {{ runner.os }}-node-{{ hashFiles('**/package-lock.json') }}

- name: Check cache hit
  run: echo "Cache hit - {{ steps.cache-npm.outputs.cache-hit }}"

true は正確にヒット、false は部分的なヒットか完全なミスです。この変数を使って npm ci を実行するかどうかを判断できます:

- name: Install dependencies
  if: steps.cache-npm.outputs.cache-hit != 'true'
  run: npm ci

三、実践設定例

理論は終わり、コードを見ていきましょう。以下の設定はすべて実際にテスト済みで、そのままコピーして使用できます。

npm キャッシュ(setup-node の使用を推奨)

実は setup-node にはすでにキャッシュ機能が組み込まれており、手動で actions/cache を使うよりもシンプルです:

- uses: actions/setup-node@v4
  with:
    node-version: '20'
    cache: 'npm'  # または 'yarn'、'pnpm'

これだけで完了です。ただし、他のディレクトリ(例:node_modules)をキャッシュしたい場合は、やはり actions/cache を使う必要があります:

- uses: actions/cache@v4
  with:
    path: node_modules
    key: {{ runner.os }}-nm-{{ hashFiles('**/package-lock.json') }}
    restore-keys: {{ runner.os }}-nm-

おすすめ:特別な要件がない限り、setup-node の組み込みキャッシュを優先してください。

yarn と pnpm

yarn のキャッシュディレクトリは npm とは異なります:

- uses: actions/cache@v4
  with:
    path: |
      ~/.yarn/cache
      ~/.yarn/install-state.gz
    key: yarn-{{ runner.os }}-{{ hashFiles('**/yarn.lock') }}

pnpm はさらに特殊で、グローバルストアを使用します:

- uses: pnpm/action-setup@v4
  with:
    version: 9

- uses: actions/cache@v4
  with:
    path: ~/.pnpm-store
    key: pnpm-{{ runner.os }}-{{ hashFiles('**/pnpm-lock.yaml') }}

Python/pip キャッシュ

Python プロジェクトのキャッシュパス:

- uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: pip-{{ runner.os }}-{{ hashFiles('**/requirements.txt') }}
    restore-keys: pip-{{ runner.os }}-

Docker Layer Cache

Docker ビルドは最も時間がかかります。良いニュースは、BuildKit が GitHub Actions のキャッシュバックエンドをサポートしていることです:

- uses: docker/setup-buildx-action@v3

- uses: docker/build-push-action@v6
  with:
    context: .
    push: false
    cache-from: type=gha
    cache-to: type=gha,mode=max

type=gha は GitHub Actions のキャッシュサービスを使って Docker レイヤーを保存することを意味します。実際にテストしたところ、5 分かかっていたイメージビルドが約 1 分に短縮されました。

Go モジュールキャッシュ

- uses: actions/cache@v4
  with:
    path: |
      ~/go/pkg/mod
      ~/.cache/go-build
    key: go-{{ runner.os }}-{{ hashFiles('**/go.sum') }}

Rust Cargo キャッシュ

- uses: actions/cache@v4
  with:
    path: |
      ~/.cargo/registry
      ~/.cargo/git
      target
    key: cargo-{{ runner.os }}-{{ hashFiles('**/Cargo.lock') }}

Rust のコンパイルは遅いので、キャッシュでかなりの時間を節約できます。ただし、target ディレクトリがどんどん大きくなるので注意——定期的なクリーンアップをおすすめします。

四、パフォーマンス最適化とベストプラクティス

実際にテストしたデータとハマった罠をまとめました。皆さんが同じ道を通らずに済むように。

パフォーマンスベンチマークデータ

RunsOn のテストレポート(2026 年 1 月更新)によると、適切にキャッシュを設定すると:

操作キャッシュなしキャッシュあり向上
npm install3 分40 秒約 5 倍
yarn install2 分 30 秒35 秒約 4 倍
Docker build5 分1 分約 5 倍
pip install45 秒8 秒約 5 倍

キャッシュヒット率は 70-90% で、キー戦略の設計がどれだけ良いかによります。

よくある罠

node_modules を直接キャッシュしない

最初はこうやっていましたが、大きな罠にハマりました。

# こう書かないでください
path: node_modules

node_modules はプラットフォーム依存です——Linux でインストールしたパッケージは、Windows で問題が起きる可能性があります。正しい方法は、グローバルキャッシュディレクトリ(~/.npm)をキャッシュし、npm ci に自分で組み立てさせることです。

クロス OS キャッシュには GNU tar + zstd を使う

デフォルトの tar は macOS と Windows でフォーマットが異なり、キャッシュの復元が失敗します。この設定を追加してください:

- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}
    enableCrossOsArchive: true

キャッシュ汚染の問題

時々、キャッシュに問題のある依存関係が保存され、ビルドが常に失敗することがあります。解決方法:

  1. 手動でキャッシュを削除:GitHub リポジトリの Actions → Caches ページで、削除をクリック
  2. キーを強制更新:キーにプレフィックスやタイムスタンプを追加して、再生成させる
key: npm-v2-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}

ベストプラクティスチェックリスト

最後に要点をまとめます。設定前に確認してください:

  1. 公式 action の組み込みキャッシュを優先(setup-node、setup-python)
  2. key に hashFiles を含める。そうしないと、依存関係が更新されても古いバージョンのキャッシュが使われ続ける
  3. restore-keys を書く。フォールバックマッチングが命を救う
  4. node_modules をキャッシュしない。グローバルディレクトリをキャッシュする
  5. 期限切れキャッシュを定期的にクリーンアップ。10GB 制限を超えないように

五、よくある質問

Q1: キャッシュヒット率が低いのはなぜ?

最も一般的な原因は、key が頻繁に変わることです。例えば、キーにタイムスタンプやブランチ名を含めると、プッシュのたびに新しいキーが生成されます。解決策:runner.oshashFiles だけを使い、不要な変数を削除する。

もう一つの原因は、hashFiles が不適切なファイルにマッチしていること。例えば hashFiles('**/*.json') と書くと、設定ファイルを変更するだけでキャッシュが無効になります。package-lock.jsonyarn.lock だけにマッチするように変更してください。

Q2: キャッシュ容量が上限を超えたらどうなる?

10GB は大きく見えますが、monorepo や Docker キャッシュでは簡単に超過します。解決策:

  1. 定期的なクリーンアップ:GitHub Actions → Caches で、古いものを手動削除
  2. キャッシュを分ける:異なる依存関係に異なるキーを使い、一つのキャッシュにすべてを保存しない
  3. self-hosted runners を使う:10GB 制限がない

Q3: self-hosted runners には特別な設定が必要?

特別な設定は不要で、キャッシュメカニズムは同じように使えます。ただし、self-hosted runners には利点があります:キャッシュはローカルに保存されるため、ネットワーク転送遅延がなく、復元がより高速です。欠点は、キャッシュが自動的にクリーンアップされないため、自分でスクリプトを書いて定期的にクリーンアップする必要があります。

Q4: キャッシュを強制更新するには?

キーを変更します。プレフィックスにバージョン番号を追加:

key: npm-v3-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}

または、古いキャッシュを削除して、システムに再生成させる。

まとめ

いろいろ説明しましたが、要は一文:キャッシュをうまく使えば、CI は 5 倍速くなる。

計算してみましょう——毎回のビルドで 2 分節約、1 日 10 回実行すれば、1 ヶ月で 600 分、約 10 時間です。この時間があれば、記事を何本も書けます。

GitHub Actions に慣れていない場合は、まず setup-node の組み込みキャッシュから始めるのがおすすめです。設定 1 行で十分使えます。ボトルネックにぶつかったら、その時、より複雑なキー戦略や Docker Layer Cache を研究しに戻ってくればいいです。

そうそう、この記事は GitHub Actions 実践ガイドシリーズの第 3 弾です。以前に CI パイプライン構築とデプロイ戦略について書きました。興味があれば、過去の記事をチェックしてみてください。

次回コードをプッシュする時、ビルド時間を確認してみてください。3 分から 40 秒に短縮できるか、試してみればわかります。

GitHub Actions キャッシュを設定して CI/CD を高速化

GitHub Actions キャッシュを設定し、npm install ビルド時間を 3 分から 40 秒に短縮する方法

⏱️ 目安時間: 10 分

  1. 1

    ステップ1: キャッシュ方式を選択

    プロジェクトのパッケージマネージャーに応じてキャッシュ方式を選択:

    • npm プロジェクト:setup-node の組み込みキャッシュを優先
    • yarn/pnpm プロジェクト:キャッシュパスを設定
    • Docker ビルド:BuildKit の gha バックエンドを使用
  2. 2

    ステップ2: キャッシュキーを設計

    hashFiles() を使ってロックファイルベースの安定したキーを生成:

    • 基本パターン:{{ runner.os }}-node-{{ hashFiles('**/package-lock.json') }}
    • バックアップマッチングとして restore-keys を追加
    • キーにタイムスタンプやブランチ名を含めない
  3. 3

    ステップ3: キャッシュ設定を追加

    ワークフローファイルにキャッシュステップを追加:

    • npm:actions/setup-node@v4 を使用、cache: 'npm' を設定
    • カスタムパス:actions/cache@v4 を使用
    • Docker:cache-from と cache-to を設定
  4. 4

    ステップ4: キャッシュ効果を検証

    キャッシュがヒットしたかどうかを確認:

    • cache-hit 出力変数を確認(true は正確なヒット)
    • ビルド時間を比較(4-5 倍短縮されるはず)
    • Actions → Caches ページでキャッシュが保存されたか確認
  5. 5

    ステップ5: キャッシュを定期的にメンテナンス

    キャッシュの問題を回避:

    • キャッシュ容量の使用状況を監視(上限 10GB)
    • 古いキャッシュを定期的にクリーンアップ
    • 汚染が発生したらキーのプレフィックスを更新して強制再構築

FAQ

キャッシュヒット率が 30% しかないのはなぜ?
通常はキー設計の問題です。キーに頻繁に変わる変数(タイムスタンプ、ブランチ名など)を含めていないか確認し、runner.os と hashFiles だけを使うように変更してください。また、hashFiles のパスがロックファイルを正確にマッチしているか確認し、ワイルドカードで多すぎるファイルをマッチさせないようにしましょう。
キャッシュが 10GB を超えるとどうなる?
GitHub は最も古いキャッシュを自動的に削除してスペースを確保します。推奨事項:

• 異なる種類の依存関係を別々にキャッシュ(npm、Docker、pip それぞれにキーを使用)
• Actions → Caches ページで不要なキャッシュを定期的に手動削除
• monorepo プロジェクトはリポジトリを分割するか self-hosted runners の使用を検討
異なるブランチ間でキャッシュを共有できる?
デフォルトでは、キャッシュは現在のブランチとデフォルトブランチ(main/master)間でのみ共有されます。ブランチをまたいで共有したい場合、キーからブランチ名を削除し、ファイルベースのハッシュだけを使用します。また、restore-keys は他のブランチのキャッシュをマッチするのに役立ちます。
self-hosted runner のキャッシュは何が違う?
キャッシュメカニズムは同じですが、2 つの違いがあります:利点はキャッシュがローカルに保存されるため、復元がより高速(ネットワーク遅延なし)です。欠点は 10GB 制限がない代わりに自動クリーンアップもされないため、自分でスクリプトを書いて定期的に古いキャッシュをクリーンアップする必要があります。
キャッシュの復元に失敗するとビルドが中断される?
いいえ。キャッシュはオプションの最適化であり、復元に失敗してもビルドには影響しません。GitHub Actions は後続のステップを実行し続け、今回のビルドでは依存関係を再ダウンロードします。ログに 'Cache not found for key: xxx' というヒントが表示され、その後、次回の使用に向けて新しいキャッシュが自動的に保存されます。
キャッシュを更新する必要があるかどうかの判断方法は?
3 つのケースでキャッシュの更新が必要:

• 依存関係のバージョン変更:hashFiles が自動的に処理、手動介入不要
• キャッシュ汚染:ビルドが突然失敗したら、古いキャッシュをクリアする必要あり
• 設定変更:Node バージョンのアップグレードなど、キーにバージョン番号を追加

ほとんどの場合、正しく設定すれば手動管理は不要です。

5 min read · 公開日: 2026年4月7日 · 更新日: 2026年4月8日

コメント

GitHubアカウントでログインしてコメントできます

関連記事