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.json や yarn.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/cache は cache-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 install | 3 分 | 40 秒 | 約 5 倍 |
| yarn install | 2 分 30 秒 | 35 秒 | 約 4 倍 |
| Docker build | 5 分 | 1 分 | 約 5 倍 |
| pip install | 45 秒 | 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
キャッシュ汚染の問題
時々、キャッシュに問題のある依存関係が保存され、ビルドが常に失敗することがあります。解決方法:
- 手動でキャッシュを削除:GitHub リポジトリの Actions → Caches ページで、削除をクリック
- キーを強制更新:キーにプレフィックスやタイムスタンプを追加して、再生成させる
key: npm-v2-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}
ベストプラクティスチェックリスト
最後に要点をまとめます。設定前に確認してください:
- 公式 action の組み込みキャッシュを優先(setup-node、setup-python)
- key に hashFiles を含める。そうしないと、依存関係が更新されても古いバージョンのキャッシュが使われ続ける
- restore-keys を書く。フォールバックマッチングが命を救う
- node_modules をキャッシュしない。グローバルディレクトリをキャッシュする
- 期限切れキャッシュを定期的にクリーンアップ。10GB 制限を超えないように
五、よくある質問
Q1: キャッシュヒット率が低いのはなぜ?
最も一般的な原因は、key が頻繁に変わることです。例えば、キーにタイムスタンプやブランチ名を含めると、プッシュのたびに新しいキーが生成されます。解決策:runner.os と hashFiles だけを使い、不要な変数を削除する。
もう一つの原因は、hashFiles が不適切なファイルにマッチしていること。例えば hashFiles('**/*.json') と書くと、設定ファイルを変更するだけでキャッシュが無効になります。package-lock.json や yarn.lock だけにマッチするように変更してください。
Q2: キャッシュ容量が上限を超えたらどうなる?
10GB は大きく見えますが、monorepo や Docker キャッシュでは簡単に超過します。解決策:
- 定期的なクリーンアップ:GitHub Actions → Caches で、古いものを手動削除
- キャッシュを分ける:異なる依存関係に異なるキーを使い、一つのキャッシュにすべてを保存しない
- 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: キャッシュ方式を選択
プロジェクトのパッケージマネージャーに応じてキャッシュ方式を選択:
• npm プロジェクト:setup-node の組み込みキャッシュを優先
• yarn/pnpm プロジェクト:キャッシュパスを設定
• Docker ビルド:BuildKit の gha バックエンドを使用 - 2
ステップ2: キャッシュキーを設計
hashFiles() を使ってロックファイルベースの安定したキーを生成:
• 基本パターン:{{ runner.os }}-node-{{ hashFiles('**/package-lock.json') }}
• バックアップマッチングとして restore-keys を追加
• キーにタイムスタンプやブランチ名を含めない - 3
ステップ3: キャッシュ設定を追加
ワークフローファイルにキャッシュステップを追加:
• npm:actions/setup-node@v4 を使用、cache: 'npm' を設定
• カスタムパス:actions/cache@v4 を使用
• Docker:cache-from と cache-to を設定 - 4
ステップ4: キャッシュ効果を検証
キャッシュがヒットしたかどうかを確認:
• cache-hit 出力変数を確認(true は正確なヒット)
• ビルド時間を比較(4-5 倍短縮されるはず)
• Actions → Caches ページでキャッシュが保存されたか確認 - 5
ステップ5: キャッシュを定期的にメンテナンス
キャッシュの問題を回避:
• キャッシュ容量の使用状況を監視(上限 10GB)
• 古いキャッシュを定期的にクリーンアップ
• 汚染が発生したらキーのプレフィックスを更新して強制再構築
FAQ
キャッシュヒット率が 30% しかないのはなぜ?
キャッシュが 10GB を超えるとどうなる?
• 異なる種類の依存関係を別々にキャッシュ(npm、Docker、pip それぞれにキーを使用)
• Actions → Caches ページで不要なキャッシュを定期的に手動削除
• monorepo プロジェクトはリポジトリを分割するか self-hosted runners の使用を検討
異なるブランチ間でキャッシュを共有できる?
self-hosted runner のキャッシュは何が違う?
キャッシュの復元に失敗するとビルドが中断される?
キャッシュを更新する必要があるかどうかの判断方法は?
• 依存関係のバージョン変更:hashFiles が自動的に処理、手動介入不要
• キャッシュ汚染:ビルドが突然失敗したら、古いキャッシュをクリアする必要あり
• 設定変更:Node バージョンのアップグレードなど、キーにバージョン番号を追加
ほとんどの場合、正しく設定すれば手動管理は不要です。
5 min read · 公開日: 2026年4月7日 · 更新日: 2026年4月8日
関連記事
GitHub Actions Matrix ビルド:マルチバージョン並列テストの実践
GitHub Actions Matrix ビルド:マルチバージョン並列テストの実践
Supabase Auth 実践ガイド:メール認証、OAuth、セッション管理
Supabase Auth 実践ガイド:メール認証、OAuth、セッション管理
GitHub Actions デプロイ戦略:VPSからクラウドプラットフォームまでのCDパイプライン

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