GitHub Actions のキャッシュがヒットしない原因 10 と対処レシピ
CI/CD パイプラインを高速化するために GitHub Actions のキャッシュ機能を導入したものの、思ったようにキャッシュがヒットせず、ビルド時間が短縮されないという経験はありませんか。キャッシュは正しく設定すれば劇的な効果を発揮しますが、わずかな設定ミスで全く機能しなくなることもあります。
本記事では、GitHub Actions のキャッシュがヒットしない主な原因 10 個を具体的なエラーコードや症状とともに解説し、それぞれに対する実践的な対処レシピをご紹介します。これらを理解することで、確実にキャッシュを活用できるようになるでしょう。
背景
GitHub Actions のキャッシュ機能は、依存関係やビルド成果物を保存して再利用することで、ワークフローの実行時間を大幅に短縮できる強力な機能です。
mermaidflowchart LR
workflow["ワークフロー開始"] -->|キャッシュキー確認| check{キャッシュ<br/>存在?}
check -->|ヒット| restore["キャッシュ復元"]
check -->|ミス| install["依存関係<br/>インストール"]
restore --> build["ビルド実行"]
install --> build
build --> save["新規キャッシュ<br/>保存"]
save --> workflow_end["ワークフロー完了"]
上図は GitHub Actions のキャッシュの基本フローを示しています。キャッシュキーが一致する既存キャッシュがあれば復元され、なければ新規にインストールして保存されます。
GitHub Actions キャッシュの仕組み
GitHub Actions では actions/cache アクションを使ってキャッシュを管理します。キャッシュは以下の要素で構成されています。
| # | 要素 | 説明 |
|---|---|---|
| 1 | キャッシュキー | キャッシュを一意に識別する文字列 |
| 2 | パス | キャッシュする対象のファイルやディレクトリ |
| 3 | リストアキー | キャッシュキーが完全一致しない場合の代替キー |
| 4 | 保存期間 | 最大 7 日間(アクセスがない場合は削除) |
| 5 | 容量制限 | リポジトリあたり 10GB まで |
キャッシュの仕組みは一見シンプルですが、実際には様々な要因でキャッシュがヒットしないことがあります。次の章では、よくある課題について見ていきましょう。
課題
GitHub Actions のキャッシュがヒットしない問題は、ワークフローのログで確認できます。以下のような症状が現れたら、キャッシュに問題がある可能性が高いです。
キャッシュミスの症状
mermaidflowchart TD
start["ワークフロー実行"] --> log_check["ログ確認"]
log_check --> symptom1["Cache not found<br/>for input keys"]
log_check --> symptom2["Post job cleanup<br/>Cache saved"]
log_check --> symptom3["毎回フル<br/>インストール実行"]
symptom1 --> issue["キャッシュ<br/>ヒット失敗"]
symptom2 --> issue
symptom3 --> issue
issue --> impact1["ビルド時間<br/>増加"]
issue --> impact2["API 制限<br/>リスク"]
issue --> impact3["開発効率<br/>低下"]
キャッシュがヒットしない場合、ワークフローログに「Cache not found for input keys」というメッセージが表示されます。また、毎回依存関係の完全インストールが実行され、ビルド時間が一向に短縮されません。
主な問題パターン
実際のプロジェクトで遭遇するキャッシュ問題は、大きく以下のカテゴリに分類できます。
| # | カテゴリ | 影響度 | 発生頻度 |
|---|---|---|---|
| 1 | キャッシュキーの設計ミス | ★★★ | 高 |
| 2 | パス指定の誤り | ★★★ | 高 |
| 3 | ブランチやワークフロー間の分離 | ★★☆ | 中 |
| 4 | キャッシュ容量の超過 | ★★☆ | 中 |
| 5 | タイミングや並列実行の問題 | ★☆☆ | 低 |
これらの問題を理解し、適切に対処することで、キャッシュの効果を最大限に引き出すことができます。次章では、具体的な原因と解決策を詳しく見ていきましょう。
解決策
ここからは、GitHub Actions のキャッシュがヒットしない主な原因 10 個と、それぞれの対処レシピを詳しく解説していきます。
原因 1: キャッシュキーのハッシュ値が一致しない
最も頻繁に発生する原因は、依存関係ファイルのハッシュ値がキャッシュキーに正確に反映されていないことです。
エラーメッセージ
cssCache not found for input keys: node-modules-d41d8cd98f00b204e9800998ecf8427e
発生条件
package-lock.jsonやyarn.lockなどのロックファイルが更新された- ハッシュ関数の対象ファイルが間違っている
- 複数のロックファイルが存在するが一部しか参照していない
解決方法
hashFiles() 関数を使って、正しい依存関係ファイルのハッシュ値をキャッシュキーに含めます。
修正前の例
yaml- name: Cache dependencies
uses: actions/cache@v4
with:
path: ~/.npm
key: npm-cache-${{ runner.os }}
上記の設定では OS のみがキー要素となっており、依存関係が更新されてもキャッシュキーが変わりません。これでは古いキャッシュが使い続けられてしまいます。
修正後の例
yaml- name: Cache dependencies
uses: actions/cache@v4
with:
path: ~/.npm
key: npm-cache-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
npm-cache-${{ runner.os }}-
hashFiles() 関数は指定されたパターンに一致するファイルの内容から SHA-256 ハッシュを生成します。依存関係が変わればハッシュ値も変わるため、適切にキャッシュが更新されます。
複数のパッケージマネージャーに対応する例
yaml- name: Cache dependencies
uses: actions/cache@v4
with:
path: |
~/.npm
~/.yarn
node_modules
key: deps-${{ runner.os }}-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}
restore-keys: |
deps-${{ runner.os }}-
モノレポ構成や複数のパッケージマネージャーを使用している場合は、すべてのロックファイルをハッシュ対象に含めることが重要です。
原因 2: restore-keys の優先順位が不適切
restore-keys は完全一致するキャッシュが見つからない場合のフォールバックとして機能しますが、設定順序が不適切だとキャッシュヒット率が低下します。
エラーメッセージ
vbnetCache restored from key: npm-cache-Linux
Post job cleanup: Cache saved with key: npm-cache-Linux-a1b2c3d4e5f6
上記のログは、部分一致のキャッシュが復元され、最終的に新しいキャッシュが保存されたことを示しています。これ自体は正常な動作ですが、より適切な設定で効率を上げられます。
発生条件
restore-keysの指定順序が最適化されていない- フォールバック戦略が明確でない
- 古いキャッシュが優先的に使われている
解決方法
キャッシュキーは具体的なものから汎用的なものへと段階的に設定します。
推奨設定パターン
yaml- name: Cache Node modules
uses: actions/cache@v4
with:
path: node_modules
key: node-modules-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
node-modules-${{ runner.os }}-
node-modules-
このように設定することで、以下の優先順位でキャッシュが検索されます。
| # | 検索キー | 説明 | マッチ精度 |
|---|---|---|---|
| 1 | node-modules-Linux-a1b2c3d4 | 完全一致(OS + ハッシュ) | ★★★ |
| 2 | node-modules-Linux- | OS のみ一致 | ★★☆ |
| 3 | node-modules- | プレフィックスのみ | ★☆☆ |
バージョン管理を含む例
yamlkey: cache-${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
cache-${{ runner.os }}-node-${{ matrix.node-version }}-
cache-${{ runner.os }}-node-
cache-${{ runner.os }}-
Node.js のバージョンごとにキャッシュを分離したい場合は、マトリックス変数も含めます。これにより、異なるバージョン間での互換性問題を回避できます。
原因 3: キャッシュ対象パスが存在しない
キャッシュの保存や復元時に、指定されたパスが存在しないとキャッシュが機能しません。
エラーメッセージ
javascriptWarning: Path Validation Error: Path(s) specified in the action do not exist:
/home/runner/.npm
発生条件
- キャッシュパスのディレクトリがまだ作成されていない
- パスの指定が間違っている(typo や環境変数の展開ミス)
- OS ごとにパスが異なるのに固定値を指定している
解決方法
環境変数や条件分岐を使って、正しいパスを動的に指定します。
パッケージマネージャーのキャッシュディレクトリを取得する例
yaml- name: Get npm cache directory
id: npm-cache-dir
shell: bash
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
まず、npm のキャッシュディレクトリの実際のパスを取得します。npm config get cache コマンドは環境に応じた正しいパスを返してくれます。
yaml- name: Cache npm dependencies
uses: actions/cache@v4
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
npm-${{ runner.os }}-
取得したパスを使ってキャッシュを設定することで、環境に依存しない確実なキャッシュが実現できます。
Yarn の場合
yaml- name: Get Yarn cache directory
id: yarn-cache-dir
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- name: Cache Yarn dependencies
uses: actions/cache@v4
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: yarn-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
Yarn も同様に、yarn cache dir コマンドで実際のキャッシュディレクトリを取得できます。
複数パスを指定する例
yaml- name: Cache dependencies and build
uses: actions/cache@v4
with:
path: |
${{ steps.npm-cache-dir.outputs.dir }}
node_modules
.next/cache
key: build-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
Next.js など、複数のディレクトリをキャッシュする場合は、パイプ記号(|)で複数行に分けて記述します。
原因 4: ブランチ間でキャッシュが共有されない
GitHub Actions のキャッシュには、ブランチごとのスコープ制限があります。
エラーメッセージ
cssCache not found for input keys: deps-Linux-a1b2c3d4e5f6
(feature ブランチでの実行時)
発生条件
- デフォルトブランチ(main/master)以外でワークフローを実行している
- feature ブランチで作成されたキャッシュは同ブランチでしか使えない
- キャッシュのスコープルールを理解していない
キャッシュのスコープルール
GitHub Actions のキャッシュは以下のルールで共有されます。
mermaidflowchart TB
main["main ブランチ<br/>キャッシュ"] --> feature1["feature/A<br/>ブランチ"]
main --> feature2["feature/B<br/>ブランチ"]
main --> develop["develop<br/>ブランチ"]
feature1 -.->|参照不可| feature2
feature2 -.->|参照不可| feature1
develop --> feature3["feature/C<br/>(develop から派生)"]
style main fill:#90EE90
style feature1 fill:#FFB6C1
style feature2 fill:#FFB6C1
style develop fill:#87CEEB
style feature3 fill:#DDA0DD
上図は、キャッシュがブランチ間でどのように共有されるかを示しています。デフォルトブランチのキャッシュはすべてのブランチから参照できますが、feature ブランチ同士では共有されません。
解決方法
方法 1: デフォルトブランチでキャッシュを事前作成
yamlname: Warmup Cache
on:
push:
branches:
- main
- develop
jobs:
cache:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Cache dependencies
uses: actions/cache@v4
with:
path: node_modules
key: deps-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
run: npm ci
デフォルトブランチでキャッシュを作成するワークフローを用意することで、すべての feature ブランチから利用可能なキャッシュが準備できます。
方法 2: キャッシュキーからブランチ固有の要素を除外
yaml- name: Cache dependencies
uses: actions/cache@v4
with:
path: node_modules
# ❌ ブランチ名を含めない
# key: deps-${{ github.ref_name }}-${{ hashFiles('**/package-lock.json') }}
# ✅ ブランチ非依存のキー
key: deps-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
ブランチ名をキャッシュキーに含めると、ブランチごとに別のキャッシュが作成されてしまいます。ブランチ非依存のキーを使うことで共有しやすくなります。
原因 5: ワークフロー間でキャッシュが共有されない
同じリポジトリ内でも、異なるワークフローファイル間ではデフォルトでキャッシュが共有されません。
エラーメッセージ
cssCache not found for input keys: build-cache-a1b2c3d4
(test.yml ワークフローでの実行時)
発生条件
- CI と CD で別々のワークフローファイルを使用している
- ワークフローごとに異なるキャッシュキーを使用している
- キャッシュの命名規則が統一されていない
解決方法
ワークフロー間でキャッシュを共有するには、同じキャッシュキーを使用します。
共有キャッシュキーの定義例(.github/workflows/ci.yml)
yamlname: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cache dependencies
uses: actions/cache@v4
with:
path: node_modules
# 共通のキャッシュキー
key: shared-deps-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
CI ワークフローでキャッシュを作成します。キー名に "shared-" プレフィックスを付けることで、共有目的であることが明確になります。
同じキーで復元する例(.github/workflows/deploy.yml)
yamlname: Deploy
on:
workflow_run:
workflows: ['CI']
types:
- completed
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Restore cached dependencies
uses: actions/cache@v4
with:
path: node_modules
# CI と同じキャッシュキー
key: shared-deps-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
デプロイワークフローでも同じキャッシュキーを使うことで、CI で作成したキャッシュを再利用できます。
再利用可能なワークフローを使う例
yaml# .github/workflows/reusable-cache.yml
name: Reusable Cache Setup
on:
workflow_call:
outputs:
cache-hit:
description: 'Whether cache was hit'
value: ${{ jobs.cache.outputs.cache-hit }}
jobs:
cache:
runs-on: ubuntu-latest
outputs:
cache-hit: ${{ steps.cache.outputs.cache-hit }}
steps:
- uses: actions/checkout@v4
- name: Cache dependencies
id: cache
uses: actions/cache@v4
with:
path: node_modules
key: deps-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
再利用可能なワークフローとしてキャッシュ設定を定義すれば、複数のワークフローから一貫した方法でキャッシュを利用できます。
原因 6: キャッシュサイズの上限超過
GitHub Actions のキャッシュには、リポジトリごとに 10GB の容量制限があります。
エラーメッセージ
vbnetError: Cache size of ~10000 MB (10485760000 B) is over the 10GB limit
Cache save failed.
発生条件
node_modules全体など、大容量のディレクトリをキャッシュしている- 複数のキャッシュキーが蓄積して容量を圧迫している
- 不要なファイルもキャッシュ対象に含まれている
キャッシュ容量の確認方法
リポジトリの「Actions」タブから「Management」→「Caches」で現在のキャッシュ使用状況を確認できます。
解決方法
方法 1: パッケージマネージャーのキャッシュのみを保存
yaml# ❌ 容量が大きい
- name: Cache node_modules
uses: actions/cache@v4
with:
path: node_modules
key: ${{ hashFiles('**/package-lock.json') }}
node_modules ディレクトリは非常に大きくなりがちで、キャッシュの容量制限を圧迫します。
yaml# ✅ パッケージマネージャーのキャッシュのみ
- name: Get npm cache directory
id: npm-cache-dir
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
- name: Cache npm
uses: actions/cache@v4
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
run: npm ci
npm のグローバルキャッシュディレクトリのみをキャッシュし、npm ci で高速にインストールする方が効率的です。
方法 2: 不要なファイルを除外
yaml- name: Cache build output
uses: actions/cache@v4
with:
path: |
.next/cache
!.next/cache/webpack
!.next/cache/**/*.map
key: nextjs-${{ hashFiles('**/package-lock.json') }}
キャッシュパスの指定で ! を使うと、特定のファイルやディレクトリを除外できます。ソースマップなど、再現性があり容量の大きいファイルは除外しましょう。
方法 3: キャッシュの有効期限を短縮
yaml- name: Cache with shorter retention
uses: actions/cache@v4
with:
path: dist
key: build-${{ github.sha }}
# 7日間アクセスがないと自動削除(デフォルト動作)
GitHub Actions のキャッシュは、7 日間アクセスされないと自動的に削除されます。この仕組みを活用して、古いキャッシュを自然に削除できます。
方法 4: キャッシュキーに日付を含める
yaml- name: Cache with date rotation
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-${{ hashFiles('**/requirements.txt') }}-${{ github.run_id }}
restore-keys: |
pip-${{ runner.os }}-${{ hashFiles('**/requirements.txt') }}-
pip-${{ runner.os }}-
github.run_id を含めることで、実行ごとに新しいキャッシュが作成されますが、restore-keys で以前のキャッシュも利用できます。
原因 7: 並列ジョブでのキャッシュ競合
複数のジョブが同時に実行される場合、キャッシュの保存と復元でタイミング問題が発生することがあります。
エラーメッセージ
vbnetError: Unable to reserve cache with key deps-Linux-a1b2c3d4, another job may be creating this cache.
発生条件
- matrix strategy で複数のジョブが並列実行されている
- 複数のジョブが同じキャッシュキーを使用している
- 同時に複数のジョブがキャッシュを保存しようとしている
解決方法
方法 1: マトリックス変数をキーに含める
yamlstrategy:
matrix:
node-version: [18, 20, 22]
os: [ubuntu-latest, windows-latest]
steps:
- name: Cache dependencies
uses: actions/cache@v4
with:
path: node_modules
# マトリックス変数を含めて一意にする
key: deps-${{ matrix.os }}-node${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
マトリックス変数をキャッシュキーに含めることで、各ジョブが独自のキャッシュを持つようになります。
方法 2: 依存関係グラフで順序制御
yamljobs:
cache:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cache dependencies
uses: actions/cache@v4
with:
path: node_modules
key: deps-${{ hashFiles('**/package-lock.json') }}
- run: npm ci
test:
needs: cache
strategy:
matrix:
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- name: Restore cache
uses: actions/cache@v4
with:
path: node_modules
key: deps-${{ hashFiles('**/package-lock.json') }}
needs を使って依存関係を定義し、キャッシュ作成ジョブを先に実行させることで競合を回避できます。
方法 3: cache-dependency-path を活用
yaml- name: Setup Node with caching
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
actions/setup-node のビルトインキャッシュ機能を使えば、内部で競合が適切に処理されます。
原因 8: キャッシュキーに可変値を含めている
実行ごとに変わる値をキャッシュキーに含めると、キャッシュが再利用されません。
エラーメッセージ
cssCache not found for input keys: cache-2024-11-01-14-30-45-a1b2c3d4
(毎回異なるタイムスタンプで新規キャッシュが作成される)
発生条件
- タイムスタンプや
github.run_numberなどの可変値をキャッシュキーに含めている - 環境変数の値が実行ごとに変わる
- ランダムな値を生成してキーに含めている
解決方法
❌ 避けるべき例
yaml- name: Cache with timestamp
uses: actions/cache@v4
with:
path: build
# タイムスタンプは実行ごとに変わる
key: build-${{ github.run_number }}-${{ github.run_attempt }}
github.run_number や github.run_attempt は実行ごとに変わるため、キャッシュが再利用されません。
✅ 推奨例
yaml- name: Cache build output
uses: actions/cache@v4
with:
path: build
# ソースコードの内容ベースでキャッシュ
key: build-${{ runner.os }}-${{ hashFiles('src/**/*.ts', 'package.json') }}
restore-keys: |
build-${{ runner.os }}-
ソースコードや設定ファイルのハッシュ値など、内容ベースのキーを使うことで、実際に変更があった場合のみキャッシュが更新されます。
条件付きで可変値を使う例
yaml- name: Cache with optional invalidation
uses: actions/cache@v4
with:
path: dist
key: dist-${{ hashFiles('src/**') }}-${{ github.event_name == 'workflow_dispatch' && github.run_id || '' }}
手動実行(workflow_dispatch)の場合のみ強制的に新しいキャッシュを作成し、通常の push や PR では再利用する、といった使い分けも可能です。
原因 9: OS やランタイムバージョンの不一致
異なる OS やランタイムバージョン間でキャッシュを共有しようとすると、互換性の問題が発生します。
エラーメッセージ
javascriptError: The process '/usr/bin/node' failed with exit code 127
(Linux でキャッシュされた node_modules を Windows で使おうとした場合)
発生条件
- マトリックスビルドで異なる OS を使用している
- Windows と Linux でバイナリの形式が異なる
- Node.js のバージョンによってネイティブモジュールの互換性がない
解決方法
OS とバージョンをキーに含める
yamlstrategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [18, 20, 22]
steps:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- name: Cache dependencies
uses: actions/cache@v4
with:
path: node_modules
# OS とバージョンの両方を含める
key: deps-${{ matrix.os }}-node${{ matrix.node }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
deps-${{ matrix.os }}-node${{ matrix.node }}-
OS とランタイムバージョンをキャッシュキーに含めることで、環境ごとに適切なキャッシュが作成・利用されます。
ネイティブモジュールを含む場合の追加対策
yaml- name: Cache for native modules
uses: actions/cache@v4
with:
path: node_modules
key: deps-${{ runner.os }}-${{ runner.arch }}-node${{ matrix.node }}-${{ hashFiles('**/package-lock.json') }}
runner.arch(CPU アーキテクチャ)も含めることで、ARM や x86 などのアーキテクチャ違いにも対応できます。
パッケージマネージャーキャッシュのみを使う場合
yaml- name: Get npm cache directory
id: npm-cache-dir
shell: bash
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
- name: Cache npm (OS independent)
uses: actions/cache@v4
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
# パッケージマネージャーのキャッシュは OS 間で共有可能
key: npm-cache-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
npm-cache-
npm のグローバルキャッシュは OS 間で共有できるため、node_modules ではなくパッケージマネージャーのキャッシュを使えば OS 固有の問題を回避できます。
原因 10: アクセス権限やセキュリティ制約
プライベートリポジトリや Fork からの PR では、キャッシュアクセスに制限がかかることがあります。
エラーメッセージ
vbnetError: Unable to download cache: Forbidden
Warning: Failed to restore cache: Cache not found
発生条件
- Fork されたリポジトリからの Pull Request
GITHUB_TOKENの権限が不足している- プライベートリポジトリでのキャッシュアクセス制限
制限の仕組み
mermaidflowchart TB
origin["オリジナル<br/>リポジトリ"] -->|キャッシュ作成| cache1["キャッシュ保存"]
fork["Fork<br/>リポジトリ"] -->|PR 作成| pr["Pull Request"]
pr -->|読み取り| cache1
pr -.->|書き込み不可| cache1
pr -->|独自キャッシュ| cache2["Fork 用<br/>キャッシュ"]
style origin fill:#90EE90
style fork fill:#FFB6C1
style cache1 fill:#87CEEB
style cache2 fill:#DDA0DD
Fork からの PR は、セキュリティ上の理由からオリジナルリポジトリのキャッシュを読み取ることはできますが、書き込むことはできません。
解決方法
方法 1: pull_request_target を使用(注意が必要)
yamlname: CI for Forks
on:
pull_request_target:
types: [opened, synchronize]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Cache dependencies
uses: actions/cache@v4
with:
path: node_modules
key: deps-${{ hashFiles('**/package-lock.json') }}
pull_request_target を使うと、ベースリポジトリのコンテキストで実行されるため、キャッシュへの書き込み権限が得られます。ただし、セキュリティリスクがあるため、信頼できないコードを実行しないよう注意が必要です。
方法 2: キャッシュミスを許容する設計
yaml- name: Restore cache
id: cache
uses: actions/cache@v4
with:
path: node_modules
key: deps-${{ hashFiles('**/package-lock.json') }}
continue-on-error: true
- name: Install dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: npm ci
continue-on-error: true を設定し、キャッシュがヒットしなくても処理を継続できるようにします。Fork からの PR では毎回インストールが実行されますが、エラーにはなりません。
方法 3: 明示的な権限設定
yamlname: Build
on: [push, pull_request]
permissions:
actions: write
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cache dependencies
uses: actions/cache@v4
with:
path: node_modules
key: deps-${{ hashFiles('**/package-lock.json') }}
ワークフローレベルで permissions を明示的に設定することで、必要な権限を確保します。
方法 4: キャッシュの失敗を検出して通知
yaml- name: Cache dependencies
id: cache
uses: actions/cache@v4
with:
path: node_modules
key: deps-${{ hashFiles('**/package-lock.json') }}
- name: Check cache status
if: steps.cache.outputs.cache-hit != 'true'
run: |
echo "::warning::Cache miss detected. This is expected for fork PRs."
echo "cache-status=miss" >> $GITHUB_OUTPUT
キャッシュがヒットしなかった場合に警告メッセージを出力することで、問題の原因が権限であることを明確にします。
具体例
ここでは、実際のプロジェクトでよく使われる構成におけるキャッシュ設定の具体例をご紹介します。
例 1: Next.js プロジェクトの最適化
Next.js アプリケーションでは、複数のキャッシュポイントを活用することで大幅な高速化が可能です。
yamlname: Next.js CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
最初に、基本的なセットアップを行います。Node.js のバージョンは LTS 版を指定しましょう。
yaml- name: Get npm cache directory
id: npm-cache-dir
shell: bash
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
- name: Cache npm dependencies
uses: actions/cache@v4
with:
path: ${{ steps.npm-cache-dir.outputs.dir }}
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
npm-${{ runner.os }}-
npm のグローバルキャッシュをキャッシュすることで、依存関係のダウンロード時間を短縮します。
yaml- name: Cache Next.js build
uses: actions/cache@v4
with:
path: |
.next/cache
!.next/cache/webpack
key: nextjs-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
restore-keys: |
nextjs-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}-
nextjs-${{ runner.os }}-
Next.js のビルドキャッシュも保存します。Webpack キャッシュは容量が大きいため除外し、ページキャッシュのみを対象とします。
yaml- name: Install dependencies
run: npm ci
- name: Build Next.js app
run: npm run build
- name: Run tests
run: npm test
キャッシュを復元した後、依存関係のインストール、ビルド、テストを実行します。キャッシュがヒットすれば、これらの処理が大幅に高速化されます。
例 2: モノレポ構成での選択的キャッシュ
モノレポでは、変更されたパッケージのみを効率的にキャッシュすることが重要です。
yamlname: Monorepo CI
on: [push, pull_request]
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
packages: ${{ steps.changes.outputs.packages }}
steps:
- uses: actions/checkout@v4
- name: Detect changed packages
id: changes
run: |
PACKAGES=$(git diff --name-only HEAD^ HEAD | grep '^packages/' | cut -d'/' -f2 | sort -u | jq -R -s -c 'split("\n")[:-1]')
echo "packages=$PACKAGES" >> $GITHUB_OUTPUT
まず、変更されたパッケージを検出します。git diff を使って変更ファイルを抽出し、パッケージ名のリストを作成します。
yamlbuild:
needs: detect-changes
runs-on: ubuntu-latest
strategy:
matrix:
package: ${{ fromJSON(needs.detect-changes.outputs.packages) }}
steps:
- uses: actions/checkout@v4
- name: Cache package dependencies
uses: actions/cache@v4
with:
path: packages/${{ matrix.package }}/node_modules
key: deps-${{ matrix.package }}-${{ hashFiles(format('packages/{0}/package-lock.json', matrix.package)) }}
restore-keys: |
deps-${{ matrix.package }}-
変更されたパッケージごとにマトリックスビルドを実行し、パッケージ別にキャッシュを管理します。
yaml- name: Cache Turborepo
uses: actions/cache@v4
with:
path: .turbo
key: turbo-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}-
turbo-${{ runner.os }}-
Turborepo を使用している場合、.turbo ディレクトリのキャッシュも重要です。これにより、タスクの実行結果がキャッシュされます。
yaml- name: Install dependencies
run: npm ci
- name: Build package
run: npm run build --workspace=packages/${{ matrix.package }}
各パッケージのビルドを実行します。Turborepo のキャッシュにより、依存関係のないパッケージは前回のビルド結果が再利用されます。
例 3: Docker イメージビルドのキャッシュ
Docker を使ったビルドプロセスでも、レイヤーキャッシュを活用できます。
yamlname: Docker Build
on:
push:
branches: [main]
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
Docker Buildx を使用することで、高度なキャッシュ機能が利用できるようになります。
yaml- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: docker-${{ runner.os }}-${{ hashFiles('**/Dockerfile', '**/package-lock.json') }}
restore-keys: |
docker-${{ runner.os }}-
Docker のビルドキャッシュを保存します。Dockerfile と依存関係ファイルの両方をハッシュ対象に含めることで、適切なキャッシュ管理ができます。
yaml- name: Build Docker image
uses: docker/build-push-action@v5
with:
context: .
push: false
tags: myapp:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
cache-from と cache-to を使って、Docker のレイヤーキャッシュを制御します。mode=max を指定することで、すべてのレイヤーがキャッシュされます。
yaml- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
キャッシュディレクトリを更新します。この手順により、古いキャッシュが削除され、最新のビルド結果が次回のキャッシュとして保存されます。
例 4: マルチステージビルドでの段階的キャッシュ
複雑なビルドプロセスを段階的にキャッシュすることで、最大限の効率化が図れます。
yamlname: Multi-stage Build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
actions/setup-node のビルトインキャッシュ機能を使うことで、npm のキャッシュが自動的に処理されます。
yaml- name: Cache TypeScript compilation
uses: actions/cache@v4
with:
path: |
**/*.tsbuildinfo
dist
key: tsc-${{ runner.os }}-${{ hashFiles('tsconfig.json', 'src/**/*.ts') }}
restore-keys: |
tsc-${{ runner.os }}-
TypeScript のインクリメンタルビルド情報(.tsbuildinfo)をキャッシュすることで、再コンパイルを最小限に抑えられます。
yaml- name: Cache ESLint results
uses: actions/cache@v4
with:
path: .eslintcache
key: eslint-${{ runner.os }}-${{ hashFiles('.eslintrc.js', 'src/**/*.ts', 'src/**/*.tsx') }}
ESLint のキャッシュも保存します。これにより、変更されていないファイルの linting がスキップされます。
yaml- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint -- --cache --cache-location .eslintcache
- name: Type check
run: npm run type-check -- --incremental
- name: Build
run: npm run build
- name: Test
run: npm test -- --coverage --cache
各ステップで適切なキャッシュオプションを指定することで、繰り返し実行時のパフォーマンスが向上します。
キャッシュ効果の測定
キャッシュが正しく機能しているかを確認するため、ワークフローの実行時間を測定しましょう。
yaml- name: Cache summary
if: always()
run: |
echo "### Cache Status Report" >> $GITHUB_STEP_SUMMARY
echo "- npm cache: ${{ steps.npm-cache.outputs.cache-hit && '✅ Hit' || '❌ Miss' }}" >> $GITHUB_STEP_SUMMARY
echo "- Next.js cache: ${{ steps.nextjs-cache.outputs.cache-hit && '✅ Hit' || '❌ Miss' }}" >> $GITHUB_STEP_SUMMARY
各キャッシュステップに id を設定し、キャッシュヒット状況をサマリーに出力することで、効果を可視化できます。
まとめ
GitHub Actions のキャッシュは、CI/CD パイプラインを高速化する強力な機能ですが、正しく設定しなければその効果を発揮できません。本記事では、キャッシュがヒットしない主な原因 10 個と、それぞれの対処レシピをご紹介しました。
キャッシュ設定のベストプラクティス
最後に、効果的なキャッシュ設定のポイントをまとめます。
| # | ポイント | 重要度 |
|---|---|---|
| 1 | hashFiles() で依存関係ファイルのハッシュを正確に取得する | ★★★ |
| 2 | restore-keys を具体的なものから汎用的なものへ段階的に設定する | ★★★ |
| 3 | パッケージマネージャーのキャッシュディレクトリを動的に取得する | ★★★ |
| 4 | ブランチ間のキャッシュ共有ルールを理解する | ★★☆ |
| 5 | OS やバージョンをキャッシュキーに含めて環境を分離する | ★★☆ |
| 6 | 容量制限(10GB)を意識して不要なファイルを除外する | ★★☆ |
| 7 | 並列ジョブではマトリックス変数をキーに含めて競合を防ぐ | ★☆☆ |
| 8 | タイムスタンプなど可変値をキーに含めない | ★★☆ |
| 9 | Fork からの PR では権限制限があることを考慮する | ★☆☆ |
| 10 | キャッシュヒット率を測定して効果を検証する | ★★☆ |
これらのポイントを押さえることで、GitHub Actions のキャッシュを最大限に活用でき、開発チームの生産性向上につながります。
トラブルシューティングの流れ
キャッシュがヒットしない場合は、以下の順序で確認していきましょう。
mermaidflowchart TD
start["キャッシュミス発生"] --> check1{"キャッシュキーに<br/>hashFiles() 使用?"}
check1 -->|No| fix1["原因1: hashFiles() を追加"]
check1 -->|Yes| check2{"対象パスは<br/>存在する?"}
check2 -->|No| fix2["原因3: パスを修正"]
check2 -->|Yes| check3{"ブランチ間で<br/>共有可能?"}
check3 -->|No| fix3["原因4: スコープ確認"]
check3 -->|Yes| check4{"キャッシュ容量は<br/>上限以内?"}
check4 -->|No| fix4["原因6: 容量削減"]
check4 -->|Yes| check5{"並列ジョブで<br/>競合している?"}
check5 -->|Yes| fix5["原因7: キーを一意化"]
check5 -->|No| other["その他の原因を<br/>個別確認"]
fix1 --> resolved["解決"]
fix2 --> resolved
fix3 --> resolved
fix4 --> resolved
fix5 --> resolved
other --> resolved
上図のフローに沿って、順番に原因を特定していくことで、効率的にトラブルシューティングができます。
キャッシュの問題は、ログを丁寧に読み解くことで必ず原因を特定できます。「Cache not found」や「Cache saved」といったメッセージに注目し、本記事で紹介した対処法を試してみてください。
適切に設定されたキャッシュは、ビルド時間を 50% 以上短縮することも珍しくありません。ぜひ、この記事を参考に、あなたのプロジェクトのキャッシュ設定を最適化してみてくださいね。
関連リンク
articleGitHub Actions のキャッシュがヒットしない原因 10 と対処レシピ
articleGitHub Actions コンテキスト辞典:github/env/runner/secrets の使い分け最速理解
articleGitHub Actions 実行コストを見える化:Usage API でジョブ別分析ダッシュボード
articleGitHub Actions でゼロダウンタイムリリース:canary/blue-green をパイプライン実装
articleGitHub Actions 条件式チートシート:if/contains/startsWith/always/success/failure
articleGitHub Actions を macOS ランナーで使いこなす:Xcode/コード署名/キーチェーン設定
articleWebLLM とは?ブラウザだけで動くローカル推論の全体像【2025 年版】
articleMistral とは? 軽量・高速・高品質を両立する次世代 LLM の全体像
articleOllama コマンドチートシート:`run`/`pull`/`list`/`ps`/`stop` の虎の巻
articletRPC とは?型安全なフルスタック通信を実現する仕組みとメリット【2025 年版】
articleJest の “Cannot use import statement outside a module” を根治する手順
articleObsidian プラグイン相性問題の切り分け:セーフモード/最小再現/ログの活用
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来