GitHub Actionsのキャッシュ入門|npm ciを速くして壊れたキャッシュを切り分ける
GitHub Actionsでnpmキャッシュを使う最小設定から、キーの決め方、ログの確認、古いキャッシュを安全に無効化して再検証する手順まで解説します。
GitHub Actionsのキャッシュは、前回までのダウンロード結果を再利用してnpm ciの待ち時間を減らす仕組みです。ビルドやテストを省略するものではありません。この記事では、Node.jsとnpmを例に、最小設定・効果の測り方・「壊れたように見える」ときの切り分け・安全な無効化までを順番に説明します。配信前の工程全体はGitHub Actionsで記事サイトを自動検品する手順も参照してください。
結論:キャッシュはnpm ciを省略せず、待ち時間だけを減らす
先に結論です。キャッシュが短くするのは「パッケージをネットワークから取ってくる時間」だけです。インストールと検査そのものは毎回行います。
GitHubの公式資料(Dependency caching)によると、GitHubが用意する実行マシン(runner)は、ジョブのたびにまっさらな状態で起動します。だから毎回、依存パッケージをダウンロードし直すことになります。この再ダウンロードの負担を減らすのがキャッシュです。同じ資料は「キャッシュがなくても再取得・再生成できる設計にする」ことを前提にしています。つまりキャッシュは補助輪です。無くてもジョブは成功する状態を保ちます。
actions/setup-nodeの公式READMEによると、保存されるのはnpmのグローバルキャッシュ(ダウンロード済みパッケージの置き場)であり、node_modulesそのものではありません。だからキャッシュがヒットしてもnpm ciは毎回実行します。
またnpm ciの公式ドキュメントによると、npm ciはlockfile(依存の設計図。package-lock.json)が必須で、package.jsonと食い違うとエラーになります。開始前に既存のnode_modulesを自動で削除してから、設計図どおりに入れ直します。この「毎回作り直す」動きが、結果の再現性を守っています。キャッシュはその材料取りを速くするだけです。
初回やlockfile変更後にキャッシュがミスするのは正常です。「常にヒット」ではなく「依存が同じときだけ再利用」が目的です。npm ciとnpm installの使い分けはnpm ciとnpm installの違いで整理しています。
npmキャッシュの最小設定
Node.jsプロジェクトでは、actions/setup-nodeにcache: 'npm'を1行足すのが最も簡単です。2026年7月25日に公式READMEで確認した書き方は次のとおりです。
steps:
- uses: actions/checkout@v6
- name: Node.jsを準備
uses: actions/setup-node@v7
with:
node-version: 24
cache: 'npm'
- name: 依存をインストール
run: npm ci
- name: テスト
run: npm test
注意点が1つあります。例に出てくる版(@v7など)は、公式側の更新で変わります。この記事の数字をそのまま信じず、貼り付ける前にその時点の公式READMEの最新例で確認してください。
公式READMEによると、setup-nodeはリポジトリ直下のpackage-lock.jsonなどのlockfileを探し、そのハッシュ(中身から作る指紋)をキャッシュのキーに使います。lockfileがサブディレクトリにある場合は、場所を明示します。
with:
node-version: 24
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
場所を間違えると、依存を変えてもキーが変わりません。古いキャッシュを引き当て続ける原因になります。
もう1つ、公式READMEには自動有効化の説明があります。package.jsonのpackageManager欄がnpmを示していると、cacheを書かなくてもnpmキャッシュが有効になります(package-manager-cacheの既定がtrueのため)。「設定していないのにキャッシュが動いている」ように見えたら、まずここを疑ってください。
キーと復元の順番をログで確かめる
キャッシュが「どれを引き当てたか」は、ログを見れば分かります。GitHubのDependency caching referenceによると、探索は次の順番です。
keyとの完全一致を探す。- 見つからなければ部分一致(前方一致)や
restore-keys(予備の探し方)で探す。 - 現在のブランチに無ければ、デフォルトブランチのキャッシュを探す。
重要なのは、cache-hitがtrueになるのは1の完全一致だけという点です。restore-keysで古いキャッシュを拾った場合、復元はされているのにcache-hitはfalseのままです。さらに、既存キャッシュの中身は後から変更できません。ミスしたジョブが成功すると、新しいキーで新しいキャッシュが作られます。
細かく制御したいときだけ、actions/cacheを直接使います。使い分けの目安は次のとおりです。
| 比較項目 | actions/setup-nodeのcache | actions/cacheを直接使用 |
|---|---|---|
| 向いている用途 | npm、Yarn、pnpmの標準的な依存キャッシュ | 保存先やキー、復元条件を細かく決めたい場合 |
| 主な指定 | cache、cache-dependency-path | path、key、restore-keys |
| npmで保存するもの | npmのグローバルキャッシュデータ | pathで指定したファイルやディレクトリ |
node_modules | 保存しない | 指定はできるが、再現性を保つには設計が必要 |
| キーの管理 | lockfileのハッシュから自動生成 | OS、ハッシュ、世代名などを自分で構成 |
| 最初の選択 | 多くの場合はこちら | 標準設定で不足するときに選ぶ |
直接使う場合の例です(版は2026年7月25日にREADMEで確認。貼り付け前に再確認してください)。
- name: npmキャッシュを復元
id: npm-cache
uses: actions/cache@v6
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
restore-keys: |
npm-${{ runner.os }}-
actions/cacheの公式READMEには、もう1つ落とし穴が書かれています。キャッシュには「cache version」という内部の指紋があり、圧縮方式とpathの組み合わせから作られます。同じ文字列のキーでも、OSやpathが違うと別キャッシュ扱いになり、ヒットしません。「キーは同じはずなのにミスする」ときの診断材料になります。
速くなったかは、感覚ではなく3回の実行で測ります。
- 同じコミットで、キャッシュ無効の実行を1回。
- キャッシュ有効の1回目(ミスして保存される)。
- キャッシュ有効の2回目(完全一致でヒット)。
| 実行 | 条件 | 記録すること |
|---|---|---|
| 1回目 | キャッシュ無効 | npm ciステップの時間、テスト結果 |
| 2回目 | 有効・初回(ミス) | 保存が行われたか、npm ciの時間 |
| 3回目 | 有効・2回目(ヒット) | 復元キー、cache-hit、npm ciの時間 |
比べるのはジョブ全体の時間ではなく、npm ciステップの時間です。短縮の幅は依存の量や回線状態で変わるため、「何秒縮む」とは断定できません。自分のリポジトリで測った数字だけを信じてください。定期運用への組み込みは毎日の作業を自動化する方法で整理しています。
壊れたように見えるときの切り分け順
「キャッシュが壊れた」という言葉は、実は3つの別の問題を一括りにしています。
- 古いキャッシュが復元された(広すぎる
restore-keysや、デフォルトブランチからの復元)。 - キー設計の誤り(lockfileの場所違い、OS違い、cache versionの違い)。
- npmキャッシュのデータ自体の破損。
このうち3つ目は、実はまれです。npm cacheの公式ドキュメントによると、npmのキャッシュは保存時と取得時に中身の整合性を検証し、破損していればエラーにするか取り直す、自己修復的な設計です。だからnpm cache clean --force(全削除)を最初の一手にしないでください。調べるならnpm cache verify(検証と掃除)が先です。
切り分けは次の順番で進めます。
package.jsonとlockfileの不一致を確認する。不一致ならnpm ciはキャッシュと無関係にエラーになります。- Node.jsとnpmの版、
.npmrc、通信・認証の失敗を確認する。 - ログで実際の復元キーを見る。完全一致か、
restore-keysで古いものを拾ったかを区別します。 - 同じコミットを「キャッシュなし」で再実行し、結果を比べる。
- なしで成功し、ありでだけ失敗が再現するなら、キャッシュ側に調査を絞る。
キャッシュなしの再実行は、公式READMEにあるpackage-manager-cache: falseで一時的に止めるのが安全です。
- uses: actions/setup-node@v7
with:
node-version: 24
package-manager-cache: false
- run: npm ci
- run: npm test
原因を記録したら、削除ではなく「新しい世代のキー」で切り替えるのが基本です。既存キャッシュは変更できないため、キーに世代名を足せば、古いものを残したまま新しいキャッシュで検証できます。
key: npm-v2-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
再検証では、1回目がミスして成功すること、2回目が同じキーにヒットすること、両方でnpm ciとテストが通ることを確認します。それでも削除が必要なら、Managing cachesの手順で、リポジトリのActions→Caches画面を開きます。書き込み権限があれば、キー・ブランチ・容量・最終利用日を見て、対象のキャッシュだけを個別に削除できます。
最後に安全面です。公式リファレンスによると、キャッシュの中身は署名も検証もされません。リポジトリを読める人や、forkからのプルリクエストのワークフローがキャッシュに触れる可能性があります。トークン・パスワード・secretをpathに含めないでください。また、復元した中身を「信頼済みの入力」と決めつけないことも公式が求めています。
よくある質問
キャッシュがヒットしたらnpm ciを省略できますか?
省略しません。setup-nodeが保存するのはnpmのグローバルキャッシュデータで、node_modulesではありません。ヒット後もnpm ciを実行して、lockfileどおりに依存を入れ直します。この作り直しが結果の再現性を守ります。
cache-hitがfalseでも何か復元されていますか?
復元されている場合があります。cache-hitがtrueになるのは、指定したkeyと完全一致したときだけです。部分一致やrestore-keysで別のキャッシュを拾ったときはfalseのままなので、ログに出る実際の復元キーで確認してください。
キャッシュが毎回ミスするのはなぜですか?
キーに入れた値が実行のたびに変わっていないかを確認します。cache-dependency-pathが実際のlockfileを指しているか、OSやpathの違いでcache versionが変わっていないかも見ます。初回、lockfile変更直後、長期間使われず削除された後のミスは正常です。
壊れたキャッシュはすぐ削除した方がよいですか?
先に比較です。同じコミットをキャッシュなしで再実行し、世代名を変えた新しいキーでも試します。npmのキャッシュは自己修復的とされているので、原因を記録しないまま全削除すると、学びが残りません。削除するときはCaches画面でキーとブランチを確認し、対象だけにします。
GitHub Actionsのキャッシュはいつ消えますか?
2026年7月26日時点の公式資料では、7日を超えてアクセスされていないキャッシュが削除対象です。リポジトリごとの保存上限は既定で10GBで、上限に達すると最終アクセスが古いものから消えます。これらの数字は管理者の設定や契約で変わる可能性があるため、お使いのリポジトリの設定と、その時点の公式案内で確認してください。
まとめ
キャッシュは「なくても成功し、あれば取得が短くなる」補助層です。読者が次にやることは3つです。
- ワークフローの
setup-nodeにcache: 'npm'を1行足し、npm ciとテストはそのまま残す。 - 同じコミットで「キャッシュなし・初回ミス・2回目ヒット」の3回を実行し、復元キーと
npm ciステップの時間を記録する。 - 不調のときは削除より先に「キャッシュなしの再実行」と「世代名を変えた新キー」で比較する。AIに相談するときも、最初のエラー・使った版・復元キー・所要時間の4点を渡すと切り分けが速くなります。
公式一次情報
確認日: 2026-07-26
- Dependency caching - GitHub Docs: runnerは毎回まっさらに始まるため依存の再取得負担をキャッシュで減らせること、キャッシュがなくても再生成できる設計にすべきことが書かれています。
- actions/setup-node 公式README: npmキャッシュの最小設定、
node_modulesは保存しないこと、cache-dependency-pathとpackage-manager-cacheの使い方が書かれています。 - Dependency caching reference - GitHub Docs: キーの探索順、
cache-hitが完全一致だけでtrueになること、保持期限・容量の既定値、セキュリティ上の注意が書かれています。 - Managing caches - GitHub Docs: Caches画面での容量・作成日・最終利用日の確認方法と、書き込み権限による個別削除の手順が書かれています。
- actions/cache 公式README:
key・path・restore-keys・cache-hit出力の仕様と、圧縮方式とpathから決まるcache versionの説明が書かれています。 - npm-cache | npm Docs: npmキャッシュが整合性検証つきの自己修復的な設計であることと、
npm cache verifyの役割が書かれています。 - npm-ci | npm Docs:
npm ciはlockfile必須で、不一致時はエラーになり、開始前に既存node_modulesを削除することが書かれています。
続き(結論と実データ)はnoteに置いています
ここでは手順のところまで書きました。実際に出た数字、うまくいかなかった条件、そのまま使える設定ファイルは、 note の記事にまとめてあります。