以下の内容はhttps://syu-m-5151.hatenablog.com/entry/2025/12/18/111500より取得しました。


「自分の環境では動く」から解放される Nix Flake

はじめに

「自分の環境では動くんだけど...」という言葉を、何度聞いたことがあるだろうか。開発環境の差異は、これまで「手順書」「Docker」「asdf/anyenv」で解決を試みてきたが、いずれも時間経過で破綻する。手順書は陳腐化し、Dockerfileのベースイメージは変わり、asdfは言語ごとにツールが分散する。問題の本質は「環境の固定」ではなく「依存関係の完全な追跡」にあった。これを根本から解決するのが、純粋関数型パッケージマネージャ「Nix」と、その最新機能「Nix Flake」だ。

これらの課題感については Infrastructure as Code, 3rd Edition が詳しく論じており、参考になる。2025年 俺が愛した本たち 技術書編 に入れれていなくて悲しいほどよい書籍である。オライリー・ジャパンさん 自分は翻訳の準備できてます!!!

本記事では、Nix Flake を使った開発環境の統一について、Docker との比較を交えながら包括的に解説する。実際に複数言語のプロジェクトで検証した結果も含めて、実践的な導入方法を紹介する。

この記事で分かること

  • Nix Flake の基本概念と従来の Nix との違い
  • Docker と Nix の使い分け・組み合わせ方
  • プログラミング言語(Rust, Go, Python, TypeScript)での開発環境の構築方法
  • CI/CD との統合方法と direnv による自動環境切り替え

Nix とは何か

純粋関数型パッケージマネージャ

Nix は、従来のパッケージマネージャ(apt, brew, npm など)とは根本的に異なるアプローチを取る。その核心は「純粋関数型」(入力が同じなら出力も必ず同じになる仕組み)という概念にある。

数学の関数と同様に、Nix では「同じ入力からは常に同じ出力が得られる」。パッケージのビルドに必要な全ての依存関係を明示的に指定し、外部環境に依存しない閉じた環境でビルドを行う。この仕組みにより、以下が保証される。

  • 再現性: 誰がいつどこでビルドしても、同じ結果が得られる
  • 分離性: システムの既存環境を汚さない
  • 共存性: 同じパッケージの異なるバージョンが同時に存在できる

nixos.org

ハッシュベースの依存管理

Nix は全てのパッケージを /nix/store/ 以下にハッシュ付きで保存する。例えば、Node.js 20.10.0 は以下のようなパスに保存される。

/nix/store/abc123...-nodejs-20.10.0/

このハッシュは、パッケージのソースコード、ビルドスクリプト、全ての依存関係から計算される。つまり、依存関係が少しでも異なれば、異なるハッシュ(異なるパス)になる。これにより、バージョン競合が原理的に発生しない。

Nix の核心概念

Nix を理解するには、いくつかの重要な概念を押さえておく必要がある。

Derivation(導出)

Derivation はビルドレシピのようなもので、Nix の中核概念だ。「既存の store object から新しい store object を生成する純粋関数」と捉えれば理解しやすい。ビルドは sandboxed プロセスとして実行され、指定された入力のみを読み込み、決定論的に出力を生成する。

Store(ストア)

Store は /nix/store/ に存在するオブジェクトの集合だ。全てのパッケージ、ビルド成果物、依存関係がここに保存される。Store は不変(immutable)であり、一度書き込まれたオブジェクトは変更されない。

Store Path

Store path は store object の一意な識別子だ。例えば以下のような形式になる。

/nix/store/a040m110amc4h71lds2jmr8qrkj2jhxd-git-2.38.1

この長い文字列(a040m110...)は、パッケージの全ての入力から計算されたハッシュだ。入力が変われば、パスも変わる。これが Nix の再現性を支える基盤となっている。

Realise(実現化)

Realise は derivation を実際にビルドし、store path を valid な状態にすることだ。既にキャッシュにあればダウンロードされ、なければビルドが実行される。

これらの概念については、公式マニュアルと用語集で詳しく解説されている。

nix.dev

nix.dev

Nix Flake とは

Flake の基本構造

Nix Flake は、Nix の最新機能であり、プロジェクトの依存関係を宣言的に管理する仕組みだ。従来の Nix には2つの問題があった。(1) NIX_PATH<nixpkgs> などグローバルな状態に依存し、マシンごとに異なる結果を生む可能性があった。(2) 依存関係のバージョンを固定する標準的な方法を欠いていた。nix-channel の更新で環境が変わってしまうのだ。Flake は flake.lock でこれらを解決する。

project/
├── flake.nix          # プロジェクト定義
├── flake.lock         # 依存関係のロックファイル
└── src/               # ソースコード

flake.nix は以下の構造を持つ。

{
  description = "プロジェクトの説明";

  inputs = {
    # 依存する外部 Flake を定義
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixpkgs-unstable";
  };

  outputs = { self, nixpkgs }: {
    # 出力(devShells, packages, etc.)を定義
  };
}

flake.lock による再現性

flake.lock は npm の package-lock.json や Rust の Cargo.lock に相当する。全ての依存関係のコミットハッシュが固定されるため、時間が経っても同じ環境を再現できる。

{
  "nodes": {
    "nixpkgs": {
      "locked": {
        "lastModified": 1702312524,
        "narHash": "sha256-...",
        "rev": "abc123...",
        "type": "github"
      }
    }
  }
}

Flake についての詳細は NixOS Wiki を参照してほしい。

nixos.wiki

Docker / コンテナエコシステムとの比較

Nix と Docker は競合ではなく補完関係にある。Nix は「ビルド時の再現性」を、Docker は「ランタイムの分離とデプロイ」を担う。

各ツールとの関係

ツール 役割 Nix との関係
Dockerfile イメージビルド Nix で置き換え可能(より再現性が高い)
Docker Compose マルチコンテナ構成 devenv/process-compose で補完
Kubernetes コンテナオーケストレーション Nixidy/kubenix で統合可能
Helm K8s パッケージ管理 nix-helm で Nix から利用可能
Skaffold 開発ワークフロー自動化 ビルドフェーズで Nix を使用可能

Dockerfile の課題と Nix の解決策

Dockerfile は広く普及しているが、再現性に課題がある。

# Dockerfile: 再現性の問題
FROM python:3.12  # タグは可変
RUN apt-get update && apt-get install -y curl  # バージョン固定なし
RUN pip install requests  # バージョン固定なし
# Nix: 完全な再現性
{
  packages.docker-image = pkgs.dockerTools.buildImage {
    name = "my-app";
    copyToRoot = pkgs.buildEnv {
      name = "image-root";
      paths = [ pkgs.python312 pkgs.curl pkgs.python312Packages.requests ];
    };
  };
}

Nix の優位点: - ビット単位で同一の結果を保証 - 全ての依存を明示的に管理(暗黙の依存が混入しない) - パッケージ単位の効率的なキャッシュ - SBOM(Software Bill of Materials)の自動生成

blog.replit.com

www.devzero.io

Nix + Docker の組み合わせ

両者を組み合わせることで「再現可能なビルド」と「ポータブルなデプロイ」を両立できる。

{
  packages.docker-image = pkgs.dockerTools.buildLayeredImage {
    name = "my-app";
    tag = "latest";
    contents = [ myApp pkgs.cacert ];
    config.Cmd = [ "/bin/my-app" ];
  };
}

各依存パッケージが独立したレイヤーになるため、パッケージAを更新してもパッケージBのレイヤーは再利用される。Dockerfile を書く必要がなく、Nix の宣言的な記述で完結する。

flox.dev

Kubernetes との統合: Nixidy

Nixidy は Nix と Argo CD を組み合わせた GitOps ツールで、クラスター全体を NixOS のように管理できる。

{
  applications.nginx = {
    namespace = "default";
    helm.releases.nginx = {
      chart = inputs.nixhelm.chartsDerivations.nginx;
      values = { replicaCount = 3; service.type = "LoadBalancer"; };
    };
  };
}

nixidy.dev

近年、ソフトウェアサプライチェーンのセキュリティが重視されている。ビルドの再現性と依存関係の透明性は「必須」になりつつある。Nix はビルドプロセス全体を宣言的に記述するため、SBOM の自動生成と来歴の追跡が容易だ。

thenewstack.io

実践:複数言語での開発環境構築

flake-parts によるモジュール化

複雑な Flake を管理しやすくするために、flake-parts を使う。これは NixOS モジュールシステムの考え方を Flake に適用したもので、設定を複数ファイルに分割できる。

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixpkgs-unstable";
    flake-parts.url = "github:hercules-ci/flake-parts";
    treefmt-nix.url = "github:numtide/treefmt-nix";
  };

  outputs = { flake-parts, ... }@inputs:
    flake-parts.lib.mkFlake { inherit inputs; } {
      imports = [ inputs.treefmt-nix.flakeModule ];

      systems = [ "aarch64-darwin" "aarch64-linux" "x86_64-linux" ];

      perSystem = { config, pkgs, ... }: {
        devShells.default = pkgs.mkShell {
          packages = with pkgs; [
            nodejs_22
            config.treefmt.build.wrapper
          ];
        };

        treefmt = {
          projectRootFile = "flake.nix";
          programs.prettier.enable = true;
          programs.nixfmt.enable = true;
        };
      };
    };
}

flake.parts

Rust 開発環境

Rust プロジェクトでは、rust-overlay を使う。rustupなしで stable/nightly を切り替えられる。rust-analyzer や clippy も flake.nix で宣言的に管理できる。

{
  inputs.rust-overlay.url = "github:oxalica/rust-overlay";

  perSystem = { pkgs, system, ... }:
    let
      overlayPkgs = import inputs.nixpkgs {
        inherit system;
        overlays = [ inputs.rust-overlay.overlays.default ];
      };
      rustToolchain = overlayPkgs.rust-bin.stable.latest.default.override {
        extensions = [ "rust-src" "rust-analyzer" "clippy" ];
      };
    in {
      devShells.default = pkgs.mkShell {
        packages = [
          rustToolchain
          pkgs.cargo-watch
          pkgs.cargo-edit
        ];
      };
    };
}

github.com

Go 開発環境

{
  devShells.default = pkgs.mkShell {
    packages = with pkgs; [
      go
      golangci-lint
      gopls
      delve
    ];
    env = {
      CGO_ENABLED = "0";
    };
  };
}

Python 開発環境

Python では uv との組み合わせを推奨する。Nix で Python 本体と uv を提供し、パッケージ管理は uv に任せる。pyenv/venv/pip の組み合わせより高速で、依存解決も確実だ。

{
  devShells.default = pkgs.mkShell {
    packages = with pkgs; [
      python312
      uv
      ruff
      pyright
    ];
    env = {
      UV_PROJECT_ENVIRONMENT = ".venv";
    };
  };
}

マルチ言語プロジェクト

1つの Flake で複数の開発環境を提供できる。

{
  devShells = {
    default = pkgs.mkShell {
      packages = [ rustToolchain pkgs.go pkgs.nodejs_22 ];
    };
    rust = pkgs.mkShell { packages = [ rustToolchain ]; };
    go = pkgs.mkShell { packages = [ pkgs.go ]; };
    nodejs = pkgs.mkShell { packages = [ pkgs.nodejs_22 ]; };
  };
}

使用時は以下のように選択できる。

nix develop        # デフォルト(全言語)
nix develop .#rust # Rust のみ
nix develop .#go   # Go のみ

様々な言語向けのテンプレートが dev-templates リポジトリで公開されている。

github.com

direnv との連携

direnv とは

direnv は、ディレクトリごとに環境変数を自動で切り替えるツールだ。.envrc ファイルを配置したディレクトリに入ると自動的に環境がロードされ、離れるとアンロードされる。

direnv.net

nix-direnv のセットアップ

Nix Flake と direnv を連携させるには、nix-direnv が必要だ。実際にセットアップした手順を紹介する。

1. nix-direnv のインストール

# Nix profile でインストール
nix profile install nixpkgs#nix-direnv

# インストール確認
ls ~/.nix-profile/share/nix-direnv/
# direnvrc が存在することを確認

2. direnvrc の設定

~/.config/direnv/direnvrc に以下を追加する。

# nix-direnv を使用して Nix Flake 環境を高速にロード
# キャッシュにより、シェル起動時の遅延を大幅に削減

if [ -f "$HOME/.nix-profile/share/nix-direnv/direnvrc" ]; then
  source "$HOME/.nix-profile/share/nix-direnv/direnvrc"
elif [ -f "/nix/var/nix/profiles/default/share/nix-direnv/direnvrc" ]; then
  source "/nix/var/nix/profiles/default/share/nix-direnv/direnvrc"
elif [ -f "/run/current-system/sw/share/nix-direnv/direnvrc" ]; then
  source "/run/current-system/sw/share/nix-direnv/direnvrc"
fi

3. シェルへの hook 追加

使用しているシェルに応じて設定を追加する。

# bash (~/.bashrc)
eval "$(direnv hook bash)"

# zsh (~/.zshrc)
eval "$(direnv hook zsh)"

# fish (~/.config/fish/config.fish)
direnv hook fish | source

github.com

プロジェクトでの使用

1. .envrc ファイルの作成

プロジェクトルートに .envrc を作成する。

# .envrc - 基本的な使い方
use flake

より詳細な設定も可能だ。

# .envrc - 詳細な設定例
# nix-direnv を使用(高速・キャッシュ対応)
use flake

# 特定の devShell を使用する場合
# use flake .#rust

# 追加の環境変数
export EDITOR="nvim"
export MY_PROJECT_ENV="development"

2. direnv の許可

セキュリティのため、初回は明示的に許可が必要だ。

cd my-project
direnv allow

動作確認

実際に動作を確認した結果を示す。

# direnv のステータス確認
$ direnv status
direnv exec path /opt/homebrew/bin/direnv
DIRENV_CONFIG /Users/nwiizo/.config/direnv
Found RC path /path/to/project/.envrc
Found RC allowed 0
Found RC allowPath /Users/nwiizo/.local/share/direnv/allow/...

nix-direnv のキャッシュ機構

nix-direnv は .direnv/ ディレクトリにキャッシュを作成する。実際のキャッシュ構造は以下のようになる。

.direnv/
├── bin/                    # 一時的なバイナリラッパー
├── flake-inputs/           # 入力 Flake のキャッシュ
├── flake-profile-*         # Nix Store へのシンボリックリンク
└── flake-profile-*.rc      # 環境変数のキャッシュ(約86KB)

キャッシュの効果

  • flake-profile-* は Nix Store の実際のパッケージを指す
  • 例: /nix/store/l5rhpr6i98h3kvydy6gww5cvszmqi05a-nix-shell-env
  • 2回目以降のロードは数ミリ秒で完了
  • nix-collect-garbage でもキャッシュは保護される

nix-direnv vs 標準 direnv

観点 nix-direnv 標準 direnv + use nix
初回ロード 同等(ビルドが必要) 同等
2回目以降 数ミリ秒 数秒〜数十秒
GC 耐性 保護される 削除される可能性
Flake 対応 ネイティブ 追加設定が必要
キャッシュサイズ 〜100KB/プロジェクト なし

マルチ言語プロジェクトでの設定

複数の devShell を持つプロジェクトでは、以下のように使い分けられる。

# .envrc
# デフォルトで全言語環境をロード
use flake

# または特定の言語環境のみロードする場合:
# use flake .#rust
# use flake .#go
# use flake .#python
# use flake .#nodejs

トラブルシューティング

direnv が反応しない

# シェルフックが設定されているか確認
which direnv
direnv status

# 許可されているか確認
direnv allow

環境がロードされない

# .envrc の構文エラーをチェック
direnv edit

# キャッシュをクリアして再構築
rm -rf .direnv
direnv allow

Flake が見つからない

# flake.nix が Git に追加されているか確認
git status flake.nix
git add flake.nix flake.lock

Determinate Systems のブログでは、direnv と Nix の連携について詳しく解説されている。

determinate.systems

CI/CD との統合

GitHub Actions での使用

name: CI with Nix Flake

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # Nix インストール(Determinate Systems 推奨)
      - uses: DeterminateSystems/nix-installer-action@main

      # Magic Nix Cache でビルドを高速化
      - uses: DeterminateSystems/magic-nix-cache-action@main

      # Flake のチェック
      - run: nix flake check

      # フォーマットチェック
      - run: nix develop --command treefmt --ci

      # ビルド
      - run: nix build

github.com

Cachix によるバイナリキャッシュ

CI でビルドした成果物を Cachix にプッシュすると、他の開発者やCI環境ではビルド済みバイナリをダウンロードするだけで済む。ビルド時間が大幅に短縮される。

- uses: cachix/cachix-action@v15
  with:
    name: your-cache
    authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'

overlay によるカスタマイズ

パッケージのカスタマイズ

overlay を使うと、既存のパッケージをカスタマイズしたり、独自のパッケージを追加したりできる。

{
  customOverlay = final: prev: {
    # 既存パッケージをラップ
    myGit = prev.writeShellScriptBin "git" ''
      exec ${prev.git}/bin/git -c init.defaultBranch=main "$@"
    '';

    # カスタムスクリプト
    project-init = prev.writeShellScriptBin "project-init" ''
      echo "Initializing project..."
      ${prev.git}/bin/git init
      echo "# New Project" > README.md
    '';
  };
}

treefmt による統一フォーマット

複数言語のフォーマッターを1つのコマンドで実行できる。

{
  treefmt = {
    projectRootFile = "flake.nix";
    programs = {
      nixfmt.enable = true;
      rustfmt.enable = true;
      gofmt.enable = true;
      prettier.enable = true;
      ruff-format.enable = true;
    };
  };
}
treefmt      # 全ファイルをフォーマット
treefmt --ci # CI でのチェック(変更があればエラー)

github.com

トラブルシューティング

experimental-features エラー

error: experimental Nix feature 'nix-command' is disabled

~/.config/nix/nix.conf に以下を追加する。

experimental-features = nix-command flakes

nix develop が遅い

初回は依存関係のダウンロードとビルドに時間がかかる。2回目以降はキャッシュが効くため高速だ。Cachix を使うとより高速化できる。

direnv が無限ループする

Fish shell を使っている場合、shellHook で exec fish を呼ばないように注意する。

Flake が見つからない

Flake ファイルは Git に追加されている必要がある。未追跡ファイルは Nix から見えない。

git add flake.nix flake.lock

まとめ

Nix Flake を導入することで、開発環境の「再現性」「分離性」「共有性」を根本から改善できる。Docker とは競合ではなく補完関係にあり、両者を組み合わせることで「再現可能なビルド」と「ポータブルなデプロイ」を両立できる。

導入の主なメリットをまとめる。

  • 開発環境のセットアップが nix develop の1コマンドに
  • チーム全員が同じツールバージョンを使用
  • CI と開発環境の乖離がなくなる
  • フォーマットの一貫性を自動で保証
  • Docker イメージのビルドも再現可能に

学習コストは確かに高い。Nix言語の習得やStore/Derivationの概念理解には時間がかかる。しかし一度導入すれば、環境構築が1コマンドで完了する。「環境差異によるバグ」が原理的になくなり、CIと開発環境が同一になる。特に複数言語プロジェクトでは、rustup/pyenv/nvm/goenvの個別管理から解放され、単一のflake.nixで全ての言語ツールチェーンを統一できる。

まずは小規模なサイドプロジェクトで試してみてほしい。nix flake init -t github:the-nix-way/dev-templates#rust ですぐに始められる。

このブログが良ければ読者になったりnwiizoXGithubをフォローしてくれると嬉しいです。




以上の内容はhttps://syu-m-5151.hatenablog.com/entry/2025/12/18/111500より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14