GitHub Actions キャッシュ戦略:CI/CD パイプラインを 5 倍高速化
npm install に 3 分 15 秒。
これは去年引き継いだあるプロジェクトの CI ビルド時間です。コードをプッシュするたびに、GitHub Actions のログがくるくる回るのを眺めながら、あの緑のチェックマークが出るのを待っていました。正直、あの頃はよく別のウィンドウに切り替えてサボっていました——どうせ待つしかないので。
その後キャッシュを入れたら、同じビルドが 40 秒で終わるようになりました。だいたい 5 倍速くなった計算です。
これは魔法ではなく、GitHub Actions のキャッシュ戦略を正しく設定しただけです。今日の記事では、踏んできた落とし穴、検証したデータ、そしてそのままコピーして使える設定テンプレートをまとめて紹介します。あなたも CI ビルドを待たされているなら、コーヒー休憩の時間をかなり節約できるかもしれません。
一、キャッシュの仕組みの基本概念
まずキャッシュがどう動くのかを押さえておきましょう。そうしないと設定で落とし穴にはまりやすくなります。
GitHub Actions のキャッシュの仕組みは意外とシンプルで、検索 → 復元 → 保存の 3 ステップだけです。key を定義すると、GitHub はすべてのキャッシュの中から一致するものを探します。見つかればそのまま作業ディレクトリに復元し、見つからなければジョブが終わったあとに新しく 1 つ保存します。
ただし、いくつか押さえておくべき制限があります。
| 制限項目 | 数値 |
|---|---|
| リポジトリあたりのキャッシュ上限 | 10 GB |
| 1 つのキャッシュファイルの上限 | 5 GB(実際は 1GB を超えると問題が出やすい) |
| キャッシュの保持期間 | 7 日間アクセスがないと削除 |
| 全体の同時アップロード制限 | 最大 5 個まで同時アップロード |
10GB の落とし穴を踏んだ人を見たことがあります。プロジェクトの依存が多すぎて、キャッシュがどんどん膨らみ、最後は新しいキャッシュが保存できず、古いものは消されて、毎回「コールドスタート」になっていました。
もう 1 つ混同しやすい点があります。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'
これ 1 行で完了です。ただし他のディレクトリ(たとえば 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 はさらに特殊で、グローバルな store を使います。
- 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% の範囲で、key 戦略の設計の良し悪し次第で変わります。
よくある落とし穴
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 を強制的に更新:key に接頭辞やタイムスタンプを足して、再生成させる
key: npm-v2-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}
ベストプラクティスのチェックリスト
最後に要点をまとめます。設定の前に照らし合わせて確認してください。
- 公式 action の組み込みキャッシュを優先する(setup-node、setup-python)
- key に hashFiles を含める。そうしないと依存が更新されてもキャッシュが古いままになる
- restore-keys を書く。段階的マッチが命綱になる
- node_modules はキャッシュしない。グローバルディレクトリをキャッシュする
- 期限切れのキャッシュを定期的に掃除する。10GB の制限を超えないようにする
五、よくある質問
Q1: キャッシュヒット率が高くないのはなぜ?
最も多い原因は key が変わりすぎることです。たとえば key にタイムスタンプやブランチ名を入れていると、push のたびに新しい key が生成されます。解決策は、runner.os と hashFiles だけを使い、不要な変数を外すことです。
もう 1 つの原因は、hashFiles がマッチすべきでないファイルにマッチしていることです。たとえば hashFiles('**/*.json') と書くと、設定ファイルを 1 つ変えただけでキャッシュが無効になります。package-lock.json か yarn.lock だけにマッチするよう変えましょう。
Q2: キャッシュ容量が上限を超えたらどうする?
10GB は大きそうに見えますが、monorepo や Docker キャッシュではすぐにあふれます。解決策はこうです。
- 定期的に掃除:GitHub Actions → Caches で古いものを手動削除
- キャッシュを分ける:依存ごとに別の key を使い、1 つのキャッシュに全部詰め込まない
- self-hosted runner を使う:10GB の制限がない
Q3: self-hosted runner には特別な設定が必要?
特別な設定は不要で、キャッシュの仕組みも同じように使えます。ただし self-hosted runner には利点があります。キャッシュがローカルに保存されるので、ネットワーク転送の遅延がなく、復元が速いことです。欠点はキャッシュが自動削除されないため、定期的に掃除するスクリプトを自分で用意する必要があることです。
Q4: キャッシュを強制的に更新するには?
key を変えます。接頭辞にバージョン番号を足しましょう。
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 を追加
• key にタイムスタンプやブランチ名を入れない - 3
ステップ3: キャッシュ設定を追加する
workflow ファイルにキャッシュのステップを追加します。
• 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)
• 古いキャッシュを定期的に削除
• 汚染が起きたら key の接頭辞を更新して強制的に再生成
FAQ
キャッシュヒット率が 30% しかないのはなぜ?
キャッシュが 10GB を超えるとどうなる?
• 依存の種類ごとにキャッシュを分ける(npm、Docker、pip はそれぞれ別の key)
• Actions → Caches ページで不要なキャッシュを定期的に手動削除
• monorepo はリポジトリ分割か self-hosted runner を検討
別々のブランチでキャッシュを共有できる?
self-hosted runner のキャッシュは何が違う?
キャッシュ復元に失敗するとビルドは止まる?
キャッシュを更新すべきか、どう判断する?
• 依存バージョンの変化:hashFiles が自動処理するので手動対応は不要
• キャッシュ汚染:ビルドが突然失敗するので古いキャッシュを削除
• 設定変更:Node のバージョンアップなど、key にバージョン番号を追加
ほとんどの場合、正しく設定すれば手動管理は不要です。
4分で読めます · 公開日: 2026年4月7日 · 更新日: 2026年6月8日
GitHub Actions 完全ガイド
検索からこのページに来た場合は、前後の記事もあわせて読むと同じテーマの理解がかなり早く深まります。
前の記事
GitHub Actions セルフホスト Runner:プライベート環境デプロイ完全ガイド
GitHub Actions セルフホスト Runner のプライベート環境デプロイ完全ガイド。2026 年の価格変更、3 つのデプロイ方式の比較、セキュリティのベストプラクティス、Runner Fleet によるオープンソース管理まで解説します。
第 4 / 9 記事
次の記事
GitHub Actions Secrets 管理:漏洩リスクから OIDC のキーレスデプロイまで
GitHub Actions Secrets 管理ガイド:3 層アーキテクチャの選び方、8 つのセキュリティ鉄則、OIDC キーレスデプロイ、サプライチェーン攻撃対策。tj-actions 事件から学び、workflow の YAML 設定例とベストプラクティスを掲載
第 6 / 9 記事
関連記事
GitHub Actions Matrix ビルド:マルチバージョン並列テスト実践
GitHub Actions Matrix ビルド:マルチバージョン並列テスト実践
GitHub Actions 入門:YAML ワークフローの基礎とトリガー設定
GitHub Actions 入門:YAML ワークフローの基礎とトリガー設定
GitHub Actions入門:YAMLワークフローの基礎とトリガー設定
コメント
GitHubアカウントでログインしてコメントできます