🐍

NixOS に manylinux 互換環境を整えて Python を簡単に利用・開発する

2025/03/08に公開

0. TL;DR

nix-ld を設定で有効化し、

programs.nix-ld = {
  enable = true;
  libraries = [];
};

次のコマンドで manylinux 互換の開発シェルを起動。

nix --experimental-features "nix-command flakes" develop -L github:ymd-h/manylinux-nixos

あとは uvhatch 等を通常通り利用。

# Pandas を利用するスクリプト my-program.py を実行
uv run --with pandas my-program.py

# hatch で dev 環境で my-program.py を実行
uvx hatch run dev:python my-program.py

1. 背景

1.1 NixOS

Nix は独自の Nix 言語による設定ファイルにより再現性のある環境を構築できる Linux・macOS 対応のパッケージマネージャーであり、NixOS は OS全体の環境設定まで Nix で管理する Linux ディストリビューションです。 (参考)

大きな特徴として、ハッシュ値レベルでビルド時の依存関係を管理していることが挙げられます。
ライブラリ・バイナリ等を各OSの標準的なパスではなく /nix/store 配下のハッシュ値を含むパスに配置しつつ、PATH環境変数や動的リンクを明示的に設定します。
これによって同じライブラリの異なるバージョンに依存するS/Wを混ぜて利用する事ができたり、一時的に異なるバージョンのプログラムを有効化したりする事ができたりします。

プロジェクト毎に様々な開発環境が必要な開発者(S/Wエンジニア)には非常に便利であり、私もその魅力に最近はまりつつあります。Zenn でも多くの記事が書かれており、注目を集めていると感じますね。
https://zenn.dev/topics/nix

1.2 manylinux

manylinux は Linux 向けの標準的な Python バイナリ形式を定め、バイナリをビルドするための開発環境を提供しているプロジェクトです。

一言に Linux と言っても様々なディストリビューションが存在するため、主要なディストリビューションで EOL を迎えていないバージョンが共通して保有しているライブラリ (glibc 他) の最も古いバージョンをピックアップして、安全に依存できるライブラリと定めています。

PyPI ではこの manylinux 向けにビルドされた wheel だけが Linux 向けのバイナリとして配布されています。

(manylinux は最大公約数であり十分に最適化できないため、PyTorch は独自にバイナリをホスティングしていたり(参考)、Python 以外も含めて一緒に管理できる Anaconda の方が好まれたりもしますが、今回の主題からは外れますのでこの話はこのぐらいで。)

2. NixOS における Python 利用・開発の課題

NixOS は動的リンクのローダーも含めたライブラリを Linux の標準的なパスに配置しないため manylinux に準拠していません。そのため Nix 外で配布されている (manylinux 準拠などの) バイナリを実行しようとすると、ファイルが存在しないというちょっと親切ではない語弊のあるエラーが発生します。

Nix 向けのパッケージを一元的に管理している nixpkgs ではコミュニティーにより Python のパッケージもパッチをあてたりしながらメンテナンスされています。とはいえ限界があるため PyPI の多数のパッケージの全てが対応されているわけではありません。

3. nix-ld による manylinux 互換環境の構築

nix-ld は Python に限らず Nix 向けにパッチを当てられていない動的リンクを含むバイナリを NixOS で実行できるようにするリンカー (ローダー?) です。

Linux 標準のパスにシンボリックリンクを張る必要があるので、nix-ld 自体は OS 全体の環境に NixOS の設定を通じてインストールすることになります。

{
  programs.nix-ld = {
    enable = true;
    libraries = [];
  };
}

nix-ld は NIX_LD_LIBRARY_PATH 環境変数を用いて動的リンク先を指定するので、devShell 毎に指定して使い分ける事ができます。

manylinux に対応する Nix のパッケージ群は nixpkgs に定義されているものが見つかります。
https://github.com/NixOS/nixpkgs/blob/664c8ef3c1e4e1397f7b4901872fffef0621ba8b/pkgs/development/interpreters/python/manylinux/default.nix#L43-L69

外からどういうパスでこの定義にアクセスできるのか見つけられなかったので、今回は手で直接コピーします。

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

  outputs = inputs@{ self, nixpkgs, ... }:
  let
    system = "x86_64-linux";
    pkgs = nixpkgs.legacyPackages.${system};
    libraries = with pkgs; [
      glibc
      stdenv.cc.cc
      xorg.libX11
      xorg.libXext
      xorg.libXrender
      xorg.libICE
      xorg.libSM
      libGL
      glib
      zlib
      expat
    ];
  in {
    devShells.${system}.default = pkgs.mkShell {
      NIX_LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath libraries;
      packages = libraries ++ [pkgs.uv];
    };
  }
}

あとは nix develop で devShell (開発シェル) を起動して使うことができます。注意事項として Nix で管理された Python を利用することはできないので、uv から stand alone build の Python を利用します。

uv run --with pandas example.py

4. ライブラリ化

ここに書いた内容を再利用できるように公開しました。
https://github.com/ymd-h/manylinux-nixos

とりあえず manylinux 互換環境を立ち上げるだけであれば、(nix-ld の設定をした上で) 以下のコマンドで開発シェルが立ち上がります。

nix --experimental-features "nix-command flakes" develop -L github:ymd-h/manylinux-nixos

オプションで -I nixpkgs=github:NixOS/nixpkgs/nixos-24.11 (参考) 等と指定する事で nixpkgs のバージョンを固定する事もできます。

毎回長いコマンドを実行するのは大変なので、通常は flake.nix を書いて開発環境を定義する事が好まれます。また開発環境に追加でインストールするライブラリを指定する事もできます。

flake.nix
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    manylinux = {
      url = "github:ymd-h/manylinux-nixos";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = inputs@{ self, nixpkgs, manylinux, ... }:
  let
    system = "x86_64-linux";
    pkgs = nixpkgs.legacyPackages.${system};
    lib = manylinux.lib.${system};
  in
  {
    devShells.${system}.default = lib.manylinux2014.mkShell {
      extraPackages = [ pkgs.actionlint ];
    };
  };
}

どちらの場合も uv をインストール済みであり、Python 本体やライブラリの管理実行を uv で行えます。

uv run --with pandas example.py

また HATCH_ENV_TYPE_VIRTUAL_UV_PATH をインストールしている uv のパスに指定済みのため、 uv をインストーラーにして hatch を利用できるようにしてあります。

uvx hatch run my-env:python example.py

5. 長所と短所

5.1 長所

  • manylinux のバイナリをパッチを当てることなくそのまま使える
  • uv 等のパッケージマネージャーを使える
    • 非 Python 製の uv を推奨

5.2 短所

  • Nix ベースの再現性を活用できない
    • uv のロック機構により問題を緩和できる

6. まとめ

nix-ld を利用して NixOS 上に manylinux 互換環境を構築する方法を調べてライブラリとして公開しました。

https://github.com/ymd-h/manylinux-nixos

これにより Nix による非 Python の再現可能な環境構築を維持しながら、uv を用いて Python の開発環境を簡単にセットアップできるようになりました。

Discussion