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

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.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'

これ 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 install3 分40 秒約 5 倍
yarn install2 分 30 秒35 秒約 4 倍
Docker build5 分1 分約 5 倍
pip install45 秒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

キャッシュ汚染の問題

ときどき、壊れた依存がキャッシュに保存され、ビルドがずっと失敗し続けることがあります。解決策はこうです。

  1. 手動でキャッシュを削除:GitHub リポジトリの Actions → Caches ページに入り、削除をクリック
  2. key を強制的に更新:key に接頭辞やタイムスタンプを足して、再生成させる
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 が変わりすぎることです。たとえば key にタイムスタンプやブランチ名を入れていると、push のたびに新しい key が生成されます。解決策は、runner.oshashFiles だけを使い、不要な変数を外すことです。

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

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

10GB は大きそうに見えますが、monorepo や Docker キャッシュではすぐにあふれます。解決策はこうです。

  1. 定期的に掃除:GitHub Actions → Caches で古いものを手動削除
  2. キャッシュを分ける:依存ごとに別の key を使い、1 つのキャッシュに全部詰め込まない
  3. 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

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

    プロジェクトのパッケージマネージャーに合わせて方式を選びます。

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

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

    hashFiles() でロックファイルをもとに安定したキーを生成します。

    • 基本パターン:{{ runner.os }}-node-{{ hashFiles('**/package-lock.json') }}
    • 予備マッチ用に restore-keys を追加
    • key にタイムスタンプやブランチ名を入れない
  3. 3

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

    workflow ファイルにキャッシュのステップを追加します。

    • 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)
    • 古いキャッシュを定期的に削除
    • 汚染が起きたら key の接頭辞を更新して強制的に再生成

FAQ

キャッシュヒット率が 30% しかないのはなぜ?
たいていは key の設計に問題があります。タイムスタンプやブランチ名など頻繁に変わる変数を key に入れていないか確認し、runner.os と hashFiles だけに変えましょう。また hashFiles のパスがロックファイルに正確に一致しているかも確認し、ワイルドカードで多くのファイルにマッチしないようにします。
キャッシュが 10GB を超えるとどうなる?
GitHub が最も古いキャッシュを自動で削除して空きを作ります。おすすめは次のとおりです。

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

• 依存バージョンの変化:hashFiles が自動処理するので手動対応は不要
• キャッシュ汚染:ビルドが突然失敗するので古いキャッシュを削除
• 設定変更:Node のバージョンアップなど、key にバージョン番号を追加

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

4分で読めます · 公開日: 2026年4月7日 · 更新日: 2026年6月8日

関連記事

コメント

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