Skipbox

GitHub Actions を高速化する方法:20分かかる CI を、効く順に短くする

PR ごとに 20 分かかる GitHub Actions は、たいてい 4 つの遅れが重なっています。ジョブどうしの待ち合わせ、要らない実行、まっさらな VM がジョブのたびにくり返す準備、そして 2 コアに押し込まれた重いステップです(プライベートリポジトリの ubuntu-latest は 2 CPU・メモリ 8 GB)。直す順番はこうです。まず時間を決めている列を測り、要らない実行をなくし、独立したジョブを同時に動かす。次に準備のやり直しをなくし、重いステップにコアを足し、まだ長いテストだけを分割する。前半の 3 つはワークフローの直しで、お金はかかりません。後半はマシンの問題で、キャッシュがジョブのあとも残り、コアの多いランナーが答えです。

Skipbox は、そのランナーです。ジョブはアカウント専用のマシンで動き、Docker のレイヤー、パッケージのキャッシュ、ツール、ビルドの結果がジョブのあとも残ります。4〜16 vCPU を、月 6,000 円の定額で、実行時間は無制限。astro の CI は GitHub で 528 秒、Skipbox で 30 秒でした。移行は runs-on: skipbox の 1行です。

1. 時間を決めている列を測る

チームが待っているのは、最後に終わるジョブの列です。全ジョブの合計ではありません。代表的なプルリクエストの実行を開くと、グラフでどのジョブがどれを待っているかが見え、各ジョブのログでステップの時間がわかります。いちばん遅い列について、次を書き出します。

キャッシュが当たった実行と、外れた実行を分けて比べます。半分外れるキャッシュも、平均すると効いているように見えます。

2. 要らない実行をなくす

  • 古い実行をキャンセルする。 2 回目の push のあと、動いている 1 回目の実行は無駄です。cancel-in-progress を付けた concurrency のグループで止めます。デプロイを途中で止めないよう、プルリクエストだけに効かせます。
concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}
  • 変更に関係するものだけを動かす。 paths: でドキュメントだけの変更を飛ばせます。モノレポなら turbo run test --affected や Nx の affected で、変わったパッケージだけを動かします。必須チェックをトリガーの段階で飛ばすと結果が返らずマージできなくなるので、必須のものはジョブの中で判定します。

3. 独立したジョブを同時に動かす

GitHub は、needs: でつながないかぎりジョブを並列に動かします。lint、型チェック、単体テスト、ビルドは、たいてい互いを待つ必要がありません。いっしょに始めて、パッケージングやデプロイだけが全部を待つようにします。同じものを 2 つのジョブでビルドしているなら、1 回だけビルドして actions/upload-artifact で渡します。アップロードとダウンロードの時間も測ってください。

4. 準備のやり直しをなくす

長い CI の時間の大半は、ここにあります。GitHub ホストランナーのジョブは毎回新しい VM で始まるので、ツールチェーンのダウンロード、依存パッケージのインストール、Docker のレイヤーのビルド、コンパイルを毎回一からやります。

  • 本当に当たるキャッシュ。 actions/setup-node の cache: npm(pnpm・yarn も同じ)は、ロックファイルをキーにパッケージマネージャーのダウンロードを保存します。node_modules は保存しないので、インストールは毎回走ります。GitHub のキャッシュはリポジトリごとに 10 GB まで、7日使われないと消え、当たっても新しい VM へのダウンロードです(キャッシュの上限)。
  • ツールのキャッシュ。 eslint --cache、tsc --incremental、Jest のキャッシュ、turbo の .turbo/cache は、ダウンロードではなく処理の結果そのものを残します。新しい VM では、それも復元しないかぎり空です。
  • Docker のレイヤー。 新しい VM の docker build は、レイヤーなしで始まります。buildx の cache-to で書き出して毎回読み込むのが回り道です(Docker のレイヤーキャッシュ)。

ディスクが残るマシンなら、どれも何もしなくて済みます。キャッシュもツールのキャッシュもレイヤーもビルドの結果も、前のジョブのままそこにあります。

5. 重いステップにコアを足す

コンパイラ、バンドラ、テストランナーは、コアの数だけワーカーを動かします。2 CPU なら 2 つずつです。テストランナーが実際にいくつ使っているかも確かめます。Jest の既定はコアの数から 1 を引いた数(2 CPU なら 1)、pytest は -n auto を付けるまで 1 本、Playwright の既定はコアの半分で、CI ではワーカー 1 つが推奨です。コアを足して効くのは、ランナーに使わせたうえで、CPU を使い切るステップだけです。ダウンロードやデータベースの起動は速くなりません。

GitHub でコアを増やすなら大きいランナーです。4〜96 コアで、使えるのは GitHub Team か Enterprise Cloud の組織だけ、コアが増えるほど 1 分の単価も上がります。

6. まだ長いものだけを分割する

マトリクスでシャードに分けると、長いテストを複数のジョブで回せます。待ち時間は短くなりますが、分数は減りません。シャードごとにチェックアウト、インストール、ビルドをやり直すので、4 分割なら準備が 4 回です。ファイルの数ではなく測った時間で均し、準備がシャードの大半を占めるようになったら、それ以上は分けません。

遅いままだと、何がかかるか

15 分の CI を 1 日 40 回動かすなら、チーム全体で毎日 10 時間の待ちです。作業の切り替えのロスは、まだ数えていません。分課金では、準備の分数もテストの分数と同じように請求されます。

GitHub はコアが多いほど 1 分の単価が上がるので、マシンが大きいほど差が開きます。

GitHub の額は、GitHub Team に含まれる無料分を引いたあとの請求です。自分のチームの分数は、トップページの料金計算で出せます。

マシンが答えになるとき

実在のプロジェクトの CI を、ステップはそのままで、GitHub の公開リポジトリのランナー(4 vCPU・16 GB)と、同じ vCPU の Skipbox の専用のマシンで動かした結果です(くわしい結果)。

プライベートリポジトリの ubuntu-latest は、この GitHub 側の半分のコアです。

Skipbox に替えると

マシンが残る

プランごとに、アカウント専用のマシンが付き、ジョブのあとも残ります。Docker のイメージ・レイヤー・ビルドキャッシュ、パッケージのキャッシュ、ツール、そしてチェックアウトしたコードと Cargo の target/、turbo のキャッシュがディスクに残ります。GitHub 公式の 7倍以上のキャッシュ容量で、キーの設定も、7日の期限も、ブランチの区切りもありません。

コアを足しても定額

マシンの大きさは、ジョブのラベルで選びます。

実行時間は無制限なので、シャードも再実行も大きなマトリクスも、請求は変わりません。Skipbox のジョブは GitHub の分数を使わないので、GitHub の予算で止まることもありません。

マシンの管理は要らない

マシンの用意、イメージの更新、ジョブごとの後片付け、ヘルスチェックと自動復旧まで、Skipbox が行います。

移行は runs-on を 1行書き換えるだけ

jobs:
  test:
    runs-on: skipbox # ubuntu-latest から書き換える
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci && npm test
  1. Skipbox に GitHub でログインする
  2. Skipbox の GitHub アプリを入れて、使うリポジトリを選ぶ
  3. runs-on を書き換えて push する

ステップやアクションはそのまま、Docker、Node.js、Python の入った Ubuntu 24.04 で動きます。

セキュリティ

  • マシンはアカウントごとの専用です。ほかの会社のジョブと同じマシンで動くことはありません。
  • ジョブが終わるたびに、ランナーの認証情報、プロセスとコンテナを消し、残したチェックアウトからも git の認証情報と追跡していないファイルを消します。「毎回クリーン」なら、ジョブごとに新しいマシンで動きます。
  • 書き込み権限のない人のコード(フォークからのプルリクエストなど)は、使い捨ての VM で動きます。キャッシュは読めますが、書き換えられません。

くわしくはジョブの分離のしくみにあります。

いちばん遅いジョブで試す

カード不要で、7日間かジョブの合計 3,000分まで無料です。時間を決めている列のジョブを runs-on: skipbox に替えて 2 回動かし、2 回目のステップの時間を GitHub と並べてください。

よくある質問

PR ごとに CI で 20 分以上待たされる。高速化するにはどうすればいい?

効く順に直します。まず実行のグラフとステップの時間を見て、全体の時間を決めているジョブの列を探します(キュー待ちは別に数えます)。次に、古い実行のキャンセルとパスでの絞り込みで、要らない実行をなくします。習慣で書いた needs: を外して、独立したジョブを同時に動かします。そのうえで、ジョブごとの準備のやり直しをなくし、重いステップにコアを足し、まだ長いテストだけを分割します。プライベートリポジトリの ubuntu-latest(2 CPU・メモリ 8 GB)では、後ろの 3 つが 20 分の大半を占めることが多く、どれもマシンの問題です。Skipbox は、キャッシュがジョブのあとも残る専用のマシンで、4〜16 vCPU を使えます。

ランナーを替えるのとワークフローを最適化するのは、どちらが効く?

先にワークフローの無料の直しをします。古い実行のキャンセル、パスでの絞り込み、要らない needs: の削除は、どのランナーでもやってくれません。そのあとに残る時間を見ます。ジョブごとにくり返す準備(ツールチェーン、依存パッケージ、Docker のビルド)と、コアを使い切るステップは、マシンで決まります。キャッシュが残り、コアの多いランナーに替えれば、runs-on の 1行で消えます。ネットワークの待ちと、1 本道の処理は、マシンを大きくしても速くなりません。

GitHub Actions はなぜローカルより遅い?

手元の PC は、パッケージもツールも Docker のレイヤーもビルドの結果もディスクに残っていて、コアも 8 以上あることが多いからです。プライベートリポジトリの ubuntu-latest は 2 CPU・メモリ 8 GB で、毎回それが何もない新しい VM です。Skipbox は手元の PC と同じで、マシンがジョブのあとも残り、4 vCPU からです。

テストを分割(シャード)すると速くなる?

待ち時間は短くなりますが、分数は減りません。シャードはそれぞれ新しいジョブなので、チェックアウト、インストール、ビルドをやり直します。4 分割なら準備が 4 回で、分課金ではそのすべてに請求が来ます。準備が軽くなり、コアを使い切ってから分割するのが順番です。Skipbox なら準備がディスクに残り、実行時間は無制限なので、分割しても請求は増えません。

GitHub Actions のランナーを 4 コアより大きくするには?

GitHub では、大きいランナー(4〜96 コア)が使えるのは GitHub Team か Enterprise Cloud の組織だけで、分課金です。Skipbox なら runs-on を skipbox-8x16 にすると 8 vCPU、skipbox-16x32 にすると 16 vCPU で、月額は定額。組織でも個人アカウントでも使えます。

試すのにお金はかかる?

かかりません。カード不要で、7日間かジョブの合計 3,000分まで無料です。いちばん遅いジョブを runs-on: skipbox に替えて 2 回動かし、ステップの時間を比べてください。

2026年11月13日 更新

常に warm なキャッシュで CI/CD を高速化するなら

専用ランナーを定額で。実行時間は無制限

フル稼働なら GitHub の 1/12 の費用。定額だから一日中動かせる。GitHub の 7倍のキャッシュ容量。

  • カード不要
  • GitHub でログイン
  • いつでも解約