🐡

GitHub Actions における Nix バイナリキャッシュ 3 ツールの実測比較

に公開

はじめに

GitHub Actions の CI/CD に Nix を利用する際、バイナリキャッシュを活用することでビルド時間を短縮できます

複数のキャッシュツールが存在しますが、スター数もまちまちで、人気だけでツールを選んでよいか判断に迷う場面がありました
また、各ツールの性能を定量的に比較した記事は少なく、自分の環境に最適なツールを選ぶのが難しいと感じました。

本記事では GitHub Actions の実行速度(job 時間・build 時間)を基準に3 つのツールを比較しました。

比較は「公式バイナリキャッシュ(cache.nixos.org)があるパッケージのみの環境」と「公式バイナリキャッシュが無いパッケージを含む環境」の 2 パターンで実施しました。

検証に利用したコードを直接確認したい方は以下のレポジトリをご確認ください。

https://github.com/ryuryu333/nix_cache_ci_experiments

対象読者

  • Nix を利用している方
  • GitHub Actions × Nix で CI/CD を構築し、ビルド時間の短縮を検討している方
  • 各種キャッシュツールの違いを知り、「どれを選べばよいか」判断材料を得たい方

結論

基本的には cache-nix-action を利用し、必要に応じて cachix-action も比較検討する、といったスタイルが良いと思いました

項目 cachix-action cache-nix-action magic-nix-cache-action
保存先 Cachix GitHub Actions Cache GitHub Actions Cache
キャッシュ単位 ストアパス単位 nix/store/<hash><name> job ごとに 1 つ ストアパス単位 nix/store/<hash><name>
キャッシュ対象 cache.nixos.org にないストアパス nix/ ほぼ全体 cache.nixos.org にないストアパス

※デフォルト設定で利用する場合

環境別まとめ

公式バイナリキャッシュ(cache.nixos.org)があるパッケージのみの環境

キャッシュの効果は限定的でした。
使うなら cache-nix-action が有用です(約 10% 短縮)。

marp job 時間一覧 use cache のみ

公式バイナリキャッシュが無いパッケージを含む環境(e.g. importNpmLock で Node.js パッケージを Nix で再現)

最有力は cache-nix-action、次点で cachix-action です(約 50% 短縮)。

zenn job 時間一覧 use cache のみ

記事の流れ

  • 背景
  • 各ツールの紹介
  • 検証方法
  • 検証結果

という流れで進行します。

1. 背景

1.1 Nix のビルドとキャッシュ

Nix のビルドはキャッシュによって最適化されています
キャッシュツールの仕様理解に繋がるため、簡易的にビルドの流れを紹介します。

nix build コマンドを実行すると、flake.nix などの設定をもとにビルドレシピ(derivation)が生成されます。
そして、ビルド成果物(例:treefmt)のハッシュが算出されます。

flake.nix
packages.default = buildEnv {
    name = "example";
    paths = [ pkgs.treefmt ];
};

/nix/store/pnl4n8xandadzdp0jlnbkq4smkldprzs-treefmt-2.3.1

treefmt のバイナリを構築する前に、Nix は以下の順でビルド成果物のキャッシュの有無を確認します。

  1. ローカルの Nix ストアに同一パスがあるか
  2. なければ、設定されたバイナリキャッシュにあるか
  3. どちらにもなければ、一からビルド

デフォルトのバイナリキャッシュは cache.nixos.org です
nixpkgs の多くはここにキャッシュが存在するため、初回からビルドをスキップして高速になります

ビルド時のログ例
these 464 paths will be fetched (265.13 MiB download, 1391.91 MiB unpacked):
  # ...
  /nix/store/pnl4n8xandadzdp0jlnbkq4smkldprzs-treefmt-2.3.1
  # ...
copying path '/nix/store/pnl4n8xandadzdp0jlnbkq4smkldprzs-treefmt-2.3.1' from 'https://cache.nixos.org'...
# ...

一方で、独自定義のパッケージは Nix ストア、cache.nixos.org に存在しないため、初回はキャッシュを活用できません。

ビルド時のログ例
these 464 paths will be fetched (265.13 MiB download, 1391.91 MiB unpacked):
# ...
# 公式バイナリキャッシュがある場合は、cache.nixos.org を利用
copying path '/nix/store/pnl4n8xandadzdp0jlnbkq4smkldprzs-treefmt-2.3.1' from 'https://cache.nixos.org'...
# ...
# 公式バイナリキャッシュが無い場合は、ビルド
building '/nix/store/d314dc7v1hakn4g9824yyrff2gxckg0h-zenn-cli-0.2.3.tgz.drv'...
# ...

1.2 ローカル / GitHub Actions でのビルドの差異

ローカル環境」では、独自定義のパッケージであっても、2 回目以降はローカルの Nix ストアでのキャッシュによりビルドが高速化されます。

しかし、「GitHub Actions」では、実行ごとにまっさらな環境が立ち上がるため、ローカルの Nix ストアによる再利用が効きません。

つまり、公式バイナリキャッシュが無いパッケージを含む環境は、毎回ビルドが必要となり、CI/CD の実行時間が長くなる傾向にあります

1.3 バイナリキャッシュツール

Nix には cache.nixos.org 以外の手段でビルド成果物を保存・再利用する仕組みが用意されています。

本記事では、GitHub Actions での Nix ビルドにフォーカスして、以下のキャッシュツールを比較します

  • cachix-action
  • cache-nix-action
  • magic-nix-cache-action

nix cache で検索した際に名前を見かけたツール達を対象にしました。

1.4 最適なツールはどれか

GitHub には複数のキャッシュツールが公開されており、スター数もまちまちです。
次項でデータを提示しますが、スター数だけで判断するなら magic-nix-cache-action が最も人気があります。

人気という理由だけで判断して良いのでしょうか?

自分の環境にとってベストなツール(=CI/CD が高速化できるツール)はどれか」を知りたいと思った事が本調査のきっかけです。

job 時間・build 時間を評価軸とし、初回ビルドと 2 回目のビルドの時間を計測して、キャッシュツールを定量的に比較しました

参考資料

  • Nix 全般のリファレンス

https://nixos.org/manual/nixpkgs/stable/

  • Cache の解説ページ(分かりやすい)

https://zero-to-nix.com/concepts/caching/

  • attic(self-hostable Nix Binary Cache server)

https://github.com/zhaofengli/attic

  • cachix(バイナリキャッシュサーバー)

https://docs.cachix.org/getting-started

  • バイナリキャッシュの解説記事

https://zenn.dev/asa1984/books/nix-introduction/viewer/07-binary-cache

https://zenn.dev/asa1984/articles/make-a-binary-cache

2. 各ツールの紹介

項目 cachix-action cache-nix-action magic-nix-cache-action
保存先 Cachix GitHub Actions Cache GitHub Actions Cache
キャッシュ単位 ストアパス単位 nix/store/<hash><name> job ごとに 1 つ ストアパス単位 nix/store/<hash><name>
キャッシュ対象 cache.nixos.org にないストアパス nix/ ほぼ全体 cache.nixos.org にないストアパス

※デフォルト設定で利用する場合

2.1 cachix-action

  • 外部のバイナリキャッシュサービス(Cachix)を利用
  • ローカル・GitHub Actions・リポジトリ間・Job 間でキャッシュを共有可能
  • 無料枠あり
    • ただし、キャッシュは公開される
    • プライベートは有料
  • cache.nixos.org に存在しないモノのみをキャッシュする

https://app.cachix.org/

https://github.com/cachix/cachix-action

https://zenn.dev/trifolium/articles/fc89526ce652fa

2.2 cache-nix-action

  • GitHub Actions のキャッシュを利用
  • デフォルト設定では /nix~/.cache/nix~root/.cache/nix がキャッシュ対象
  • GitHub Actions Cache 上では大きな 1 ファイルとして保存される
    • e.g. cache name: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
  • Nix の環境全体をキャッシュ&復元するイメージ(筆者の印象)

https://github.com/nix-community/cache-nix-action

2.3 magic-nix-cache-action

  • GitHub Actions のキャッシュを利用
  • 細かい設定無しで手軽に利用できる(公式レポジトリの受け売り)
    • Use Magic Nix Cache, a totally free and zero-configuration binary cache for Nix on GitHub Actions.
  • キャッシュの挙動は cachix に似ている(筆者の印象)

https://github.com/DeterminateSystems/magic-nix-cache-action

2.4 各ツールのスター数

GitHub Star 数の比較

https://www.star-history.com/#cachix/cachix-action&nix-community/cache-nix-action&DeterminateSystems/magic-nix-cache-action&type=date&legend=top-left

スター数の推移を見ると、magic-nix-cache-action が最も多くの支持を集めています

一方で、2025 年以降は cache-nix-action の伸びが目立ちます。

この増加は、2025 年中頃に magic-nix-cache-action が一時的に非推奨となり、cache-nix-action へ移行するプロジェクトが増えた為と推測されます。

具体例:Hyprland

Hyprland リポジトリにて、2025 年 6 月に cache-nix-action へ移行する PR が確認できました。

2025/6/20
Describe your PR, what does it fix/add?
After https://github.com/DeterminateSystems/magic-nix-cache-action has been deprecated, we've been running Nix Actions without a cache for /nix/store. This meant that all dependencies had to be downloaded on each action run.

https://github.com/hyprwm/Hyprland/pull/10794

https://github.com/hyprwm/Hyprland/blob/main/.github/workflows/nix.yml

3. 検証方法

3.1 ビルドする環境

検証にあたり 2 種類の Nix 環境を用意しました。

  • marp
    • 公式バイナリキャッシュがあるパッケージのみの環境
    • pkgs.marp 等を利用
  • zenn
    • 公式バイナリキャッシュが無いパッケージを含む環境
    • importNpmLock を利用して node_modules を Nix で再現する環境

3.1.1 marp - 公式バイナリキャッシュがあるパッケージのみの環境

Marp CLI を利用して Markdown からスライドを静的作成するのに利用している環境です。

pkgs.marp-cli の様に nixpkgs のパッケージを利用します。
そのため、ビルドでは公式バイナリキャッシュ(cache.nixos.org)を活用できます。

flake.nix
{
  description = "Marp environment";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs =
    { nixpkgs, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (
      system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
      in
      rec {
        packages = {
          marp_build = pkgs.buildEnv {
            name = "marp_build_tools";
            paths = with pkgs; [
              marp-cli
              chromium
              noto-fonts-cjk-sans
            ];
          };
          default = packages.marp_build;
        };

        devShells = {
          default = pkgs.mkShell {
            nativeBuildInputs = with pkgs; [
              packages.marp_build
              gh
            ];
          };
        };

        apps = {
          marp = {
            type = "app";
            program = "${pkgs.marp-cli}/bin/marp";
          };
          default = apps.marp;
        };
      }
  );
}

3.1.2 zenn - 公式バイナリキャッシュが無いパッケージを含む環境

Zenn CLI を利用してローカル環境で記事を作成するのに利用している環境です。

Node.js のツールを利用するために、importNpmLock で node_modules を Nix で構築します。

https://github.com/NixOS/nixpkgs/tree/master/pkgs/build-support/node/import-npm-lock

importNpmLock によるビルドは公式バイナリキャッシュ(cache.nixos.org)が効きません
この点が、marp 環境との明確な違いです。

flake.nix
{
  description = "Zenn CLI environment";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs =
    { nixpkgs, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (
      system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
        npmRoot = ./node-pkgs;
        nodejs = pkgs.nodejs_24;
        inherit (pkgs) importNpmLock;
        npmDeps = importNpmLock.buildNodeModules {
          inherit nodejs npmRoot;
        };
      in
      rec {
        packages = {
          zenn_tools = pkgs.buildEnv {
            name = "zenn_tools";
            paths = with pkgs; [
              treefmt
              lychee
              npmDeps
            ];
          };
          default = packages.zenn_tools;
        };

        devShells = {
          zenn = pkgs.mkShell {
            nativeBuildInputs = [
              packages.zenn_tools
              pkgs.go-task
              importNpmLock.hooks.linkNodeModulesHook
            ];
            inherit npmDeps;
          };
          node = pkgs.mkShell {
            packages = [
              nodejs
              pkgs.npm-check-updates
            ];
          };
          default = devShells.zenn;
        };
      }
    );
}
  • 参考資料

https://zenn.dev/trifolium/articles/5b01a68b80808b

https://zenn.dev/trifolium/articles/6678b0c0fb0d27

3.2 ワークフロー

3.2.1 フローの内容について

ジョブ

マトリックス機能を利用して、ビルド環境 2 種 x(キャッシュツール 3 種 + ツール無し)= 8 パターンのジョブを定義しました。

トリガー

実行トリガーは手動(workflow_dispatch)です。

トリガー時に指定する変数

キャッシュ生成時とキャッシュ利用時の job 時間を比較するため、実行時に generate-cache または use-cache を指定できるようにしました。

処理内容に違いはなく、ジョブ名のプレフィックスとして利用しています。

ステップ

各ジョブのステップでは、各キャッシュツールの公式 README.md に記載されているサンプル構成を踏襲しました。

Nix のインストール方法はツールごとに異なりますが、あえて統一せず、それぞれの推奨方法に従う方針としています。

コード

build.yml
name: "build"

on:
  workflow_dispatch:
    inputs:
      phase:
        description: "Choose cache phase. Only used for naming the job."
        required: true
        type: choice
        options:
          - generate-cache
          - use-cache

jobs:
  build:
    strategy:
      matrix:
        cache_tool: [none, cachix-action, cache-nix-action, magic-nix-cache-action]
        target_nix_env: [marp, zenn]
    name: build_${{ matrix.target_nix_env }}_cachetool_${{ matrix.cache_tool }}_phase_${{ github.event.inputs.phase }}
    runs-on: ubuntu-latest
    concurrency:
      group: ${{ matrix.target_nix_env }}_${{ matrix.cache_tool }}_${{ github.event.inputs.phase }}_${{ github.ref }}
      cancel-in-progress: true
    defaults:
      run:
        working-directory: ${{ matrix.target_nix_env }}

    steps:
      - uses: actions/checkout@v5

      # === Install nix & setup cache tool ===
      # none
      - name: Install nix
        uses: cachix/install-nix-action@v31
        if: ${{ matrix.cache_tool}} == 'none'

      # cachix Action
      - name: Install nix
        uses: cachix/install-nix-action@v31
        if: ${{ matrix.cache_tool}} == 'cachix-action'

      - name: Setup cache tool
        uses: cachix/cachix-action@v16
        with:
          name: ryuryu333
          authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
        if: ${{ matrix.cache_tool }} == 'cachix-action'

      # cache-nix-action
      - name: Install nix
        uses: nixbuild/nix-quick-install-action@v30
        with:
          nix_conf: |
            keep-env-derivations = true
            keep-outputs = true
        if: ${{ matrix.cache_tool}} == 'cache-nix-action'

      - name: Setup cache tool
        uses: nix-community/cache-nix-action@v6
        with:
          primary-key: nix-${{ matrix.target_nix_env }}-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
          restore-prefixes-first-match: nix-${{ matrix.target_nix_env }}-${{ runner.os }}-
          gc-max-store-size-linux: 1G
          purge: true
          purge-prefixes: nix-${{ runner.os }}-
          purge-created: 0
          purge-last-accessed: 0
          purge-primary-key: never
        if: ${{ matrix.cache_tool }} == 'cache-nix-action'

      # magic-nix-cache-action
      - name: Install nix
        uses: DeterminateSystems/nix-installer-action@v20
        if: ${{ matrix.cache_tool}} == 'magic-nix-cache-action'

      - name: Setup cache tool
        uses: DeterminateSystems/magic-nix-cache-action@v13
        if: ${{ matrix.cache_tool }} == 'magic-nix-cache-action'

      # === Build ===
      - run: nix build

      # === Run test command ===
      - run: nix develop -c zenn --version
        if: ${{ matrix.target_nix_env }} == 'zenn'

      - run: nix run .#marp -- --version
        if: ${{ matrix.target_nix_env }} == 'marp'

3.2.2 ワークフローの実行手順

以下の手順でワークフローを実行しました。

  1. GitHub Actions Cache を全て削除
    gh cache delete --all を実行。

  2. Cachix のキャッシュを全て削除
    Cachix の画面より Caches > ryuryu333 > Settings > Danger Zone > Clear をクリック。

  3. ワークフロー build を手動実行
    実行時に generate-cache を選択。

  4. 全ジョブの完了を待機

  5. ワークフロー build を再度手動実行
    実行時に use-cache を選択。

  6. 全ジョブの完了を待機

  7. 手順 1 に戻る

この一連のフローを 4 回繰り返し、キャッシュ生成時とキャッシュ利用時の差分を検証しました。

3.3 ジョブ時間の集計

ジョブ時間の計測には GitHub API を利用しました。
各ジョブ・ステップのログを取得し、Python で集計・グラフ化しています。

コードは nix_cache_ci_experiments/reports/main.py に保存しています。

https://github.com/ryuryu333/nix_cache_ci_experiments/tree/main/reports

以下の run_id を対象としました

run_id run_number note
18753771242 15 1 generate-cache
18754054171 16 1 use-cache
18777714953 21 2 generate-cache
18777913273 22 2 use-cache
18778308420 23 3 generate-cache
18778547830 24 3 use-cache
18779716921 27 4 generate-cache
18779934804 28 4 use-cache

以下のビルド一覧ページには試行錯誤の過程で実行した job も含まれるので、ご注意ください。

https://github.com/ryuryu333/nix_cache_ci_experiments/actions/workflows/build.yml

4. 検証結果

4.1 marp - 公式バイナリキャッシュがあるパッケージのみの環境

4.1.1 job 時間

キャッシュツール 時間(秒) vs none
none 36.50 -
cachix-action 42.00 115%
cache-nix-action 31.75 87%
magic-nix-cache-action 54.00 148%

marp job 時間一覧 use cache のみ

4.1.2 build 時間

キャッシュツール 時間(秒) vs none
none 24.00 -
cachix-action 25.25 105%
cache-nix-action 9.25 39%
magic-nix-cache-action 30.75 128%

いずれも 2 回目のビルド(キャッシュを利用した状態)での結果

marp build 時間一覧

4.1.3 job 時間(キャッシュ生成時 vs キャッシュ利用時)

キャッシュツール キャッシュ生成時
job 時間(秒)
vs none キャッシュ利用時
job 時間(秒)
vs none
none 36.50 - 36.50 -
cachix-action 42.75 117% 42.00 115%
cache-nix-action 45.50 125% 31.75 87%
magic-nix-cache-action 428.50 1174% 54.00 148%

marp job 時間一覧

4.2 zenn - 公式バイナリキャッシュが無いパッケージを含む環境

4.2.1 job 時間

キャッシュツール 時間(秒) vs none
none 66.25 -
cachix-action 40.50 61%
cache-nix-action 29.75 45%
magic-nix-cache-action 66.50 100%

zenn job 時間一覧 use cache のみ

4.2.2 build 時間

キャッシュツール 時間(秒) vs none
none 51.50 -
cachix-action 21.25 41%
cache-nix-action 9.00 17%
magic-nix-cache-action 38.50 75%

いずれも 2 回目のビルド(キャッシュを利用した状態)での結果

zenn build 時間一覧

4.2.3 job 時間(キャッシュ生成時 vs キャッシュ利用時)

キャッシュツール キャッシュ生成時
job 時間(秒)
vs none キャッシュ利用時
job 時間(秒)
vs none
none 66.25 - 66.25 -
cachix-action 99.00 149% 40.50 61%
cache-nix-action 108.25 163% 29.75 45%
magic-nix-cache-action 377.00 569% 66.50 100%

zenn job 時間一覧

4.3 キャッシュ容量

補足として、各ツールが生成したキャッシュの容量を比較しました。
対象は、marp・zenn 両環境のビルドを完了した後に生成されたキャッシュです。

キャッシュツール 保存場所 ファイル数 容量(MB)
cachix-action cachix 287 69
cache-nix-action GitHub Actions Cache 2 1130
magic-nix-cache-action GitHub Actions Cache 2202 1177
詳細データを見たい人用

キャッシュをすべて削除した後、build #33 を実行し、削除せずに使用状況を確認しました。

# build #33 を実行後、キャッシュを削除していない状態で実行
$ gh api repos/ryuryu333/nix_cache_ci_experiments/actions/cache/usage
{
  "full_name": "ryuryu333/nix_cache_ci_experiments",
  "active_caches_size_in_bytes": 2307766854,
  "active_caches_count": 2202
}

zenn GitHub Actions Cache 容量

zenn cachix Cache 容量

zenn cachix Cache 容量 個別

おわりに

今回の検証では、3 種のキャッシュツールを定量的に比較し、環境構成によってキャッシュ効果がどのように変化するかを評価しました。

結果として、ツールごとに挙動や得意なケースが異なり、プロジェクトの構成次第で最適解が変わると分かりました。

今後は cache-nix-action をメインに運用しつつ、 実際のプロジェクトで得られた知見も記事にしたいと考えています。

Nix は日本語文献がまだ少ない分野ですが、少しずつ実例を増やしていけたらと思います。

Discussion