世界を動かす技術を、日本語で。

Nixからの移行理由

概要

Railwayが新たなビルダー Railpack をリリース。 Nixpacksの課題を解決し、 より高速・柔軟なビルド を実現。 イメージサイズ削減 やキャッシュ最適化によりデプロイ効率向上。 主要言語・フレームワークを幅広くサポート。 Railpackはオープンソース で、今すぐ利用可能。

Railpackリリース概要

  • Railwayが新ビルダー Railpack を発表
  • 14M以上のアプリ開発経験を活かした設計
  • Nixpacksの課題を踏まえ、 ゼロから再構築
  • Beta版 として本日より利用可能
  • railway.comやCentral Stationで既に運用中

Nixpacksの課題

  • バージョン管理の柔軟性不足
    • Nixは コミットベースのバージョン管理 のみ対応
    • パッチバージョンごとの指定や管理が困難
    • 例:NodeやPythonは最新メジャーのみ対応
  • イメージサイズの肥大化
    • 依存パッケージ全てを1レイヤーで管理
    • 分割不可 なため、サイズ削減困難
  • キャッシュ制御の難しさ
    • レイヤーキャッシュの 無効化が頻発
    • 環境変数の注入でキャッシュヒット率低下
  • ユーザー体験の不統一
    • デフォルトバージョン更新時に ビルド失敗リスク
    • Nix独自仕様の理解が必要

Railpackの主な特徴

  • パッケージの細かなバージョン指定
    • major.minor.patch 単位で管理
  • イメージサイズの大幅削減
    • Nodeで 38%減、Pythonで 77%減
  • BuildKitによる高度なキャッシュ制御
    • 環境横断のキャッシュ共有 に対応
  • 依存関係のロック
    • デフォルトバージョン更新時も 既存ビルドの安定性維持
  • シークレット管理の強化
    • BuildKitシークレットで 環境変数の漏洩防止
  • Go言語ベースの新アーキテクチャ
    • BuildKitとの親和性向上
  • Miseによるバージョン解決・パッケージインストール
    • 他ソースにも将来的に対応予定

Railpackの動作フロー

  • Analyze
    • コード解析で必要パッケージ・コマンド・起動コマンドを特定
  • Plan
    • JSON形式でビルドプランを生成
    • 各ステップに必要な入力・出力を明確化
  • Generate
    • BuildKit用の LLBグラフ を生成
    • コマンドごとにマルチステージでビルド
    • キャッシュヒット最適化 のため、環境変数等の変更時はハッシュで管理

Railpackで実現できること

  • Vite, Astro, CRA, Angular 等の静的サイトを ゼロ設定でビルド・デプロイ
  • Railway UIとの 統合
  • 言語最新版対応 (Railpack自体の更新不要)
  • 環境横断でのレイヤーキャッシュ最適化

対応言語・フレームワーク

  • Node, Python, Go, Php, Static HTML
  • Vite, Astro, CRA, Angular等の主要フレームワーク
    • フロントエンド・バックエンド両対応

利用方法・今後の展望

  • Beta版として今すぐ利用可能
    • サービス設定から Railpackを有効化
  • オープンソース (railpack.comにドキュメントあり)
  • 今後も 対応言語・フレームワークを拡充予定
    • コアAPIや抽象化の安定を優先
    • 要望はHelp Stationで受付

まとめ

  • RailpackはNixpacksの課題を解決し、より柔軟・高速なビルド基盤を提供
  • イメージサイズ削減・キャッシュ最適化・バージョン管理強化 により、開発者体験を大幅向上
  • 今すぐBeta版を試用可能、オープンソースとして継続的に進化

Hackerたちの意見

Nix自体には問題ないけど、使い方に問題があるんだよね。まさに「適材適所」って感じ。Nixは特定の使い方にはすごく良いけど、他の使い方には最悪。問題なのは、Nixの習得曲線がめっちゃ高いから、理解できる頃にはもう時間をかけすぎて引き返せなくなっちゃうんだよね。だから、元のニーズを解決するために無理やり使おうとする。

俺もそう感じるけど、ある意味でNixは他のOSよりも普通のプログラミングパラダイムに近いと思う。単に、そういうOSの考え方に慣れてないだけなんだよね。Nixの式は入力(パッケージリポ、たくさんのキーとバリューのペア)と出力(Linuxシステム)がある。数年後にはもっと普通になるかもね。つまり、AIが仕様通りのshell.nix(いくつかのPythonパッケージ、いくつかのLinuxパッケージ、いくつかの環境変数、いくつかのパスエントリなど)やconfiguration.nixを作るのはすごく簡単になると思う。このパラダイムのおかげで、完全にパッケージをサポートするリポと一緒に環境を含めることが多いんだ。フレークを使えばもっと再現性が高くなるだろうね(flake.nixはshell.nixのようなもので、バージョン固定がある…みたいな感じ、まだその学習の山を登ってるところ)。

バージョン選択の部分が変だね。nixpkgsのバージョンは、システムを運用・構築しているときには意味があるけど、ランタイムやコンパイラをプラットフォームとして提供するなら、devenvみたいに自分でバージョンを提供したいよね。古いnodejsを提供するために古いシステムを構築するなんて、本当に避けたい。依存関係にセキュリティパッチを残したままになっちゃうから。devenvは例えば、https://github.com/cachix/nixpkgs-python で「すべてのPythonバージョンを、Nixを使って毎時最新に保っている」ってやってる。> RailwayはすべてのビルドにデプロイIDの環境変数を注入してる。インストール後の次のレイヤーでやってもよかったはずだし、パッケージを異なるレイヤーに分けることもできる。レイヤーの数を減らすための自動化もあるし、バッチが必要ならそれもできるよ。

ここでの主な問題は、言語パッケージマネージャーが奨励する「特注バージョンスープ」的な考え方を手放したくないってことだね(これは完全に持続不可能)。代替のMiseは、パッケージ間のバージョン制約を理解する能力がないみたいだし、インストールされた各パッケージが周囲のバージョンと正しく動作するかテストもしてないから、全然同じものが得られないよ。

「言語パッケージマネージャーが奨励する「特注バージョンスープ」的な考え方」って何を意味するのか、そしてその代替は何なのか詳しく教えてくれる?

実際、うまくやれば両方とも持てるよ。例えば、Cargo.lockファイルからNixでRustパッケージをビルドするのは簡単だし。Nixpkgsは特注のバージョンスープとは逆だけど、Nix自体はそれでも大丈夫だよ。

特注のバージョンスープは持続不可能だけど、みんながそれを続ける理由の一つは、うまくいくことが多いから。OSレベルのライブラリは、互換性を壊さないように気を使う保守的な世界から来ているから、安定した、よく管理されたOSをベースにして、miseやasdfみたいなツールを使って特注のバージョンスープを構築して、その上でアプリを動かすことができる。ほとんど壊れることはないし、壊れたらバージョンや小さな修正をいじってまた動くようにして、次に進む。壊れたことはイライラするけど、重要ではない。摩擦を生むもの、学ぶことが増えるもの、作業が増えるものは時間の無駄だと思う人が多い。逆に、壊れないようにする解決策を探す人もいるけど、その解決策は摩擦を生んだり、学ぶことが増えたり、作業が増えたりすることが許される。なぜなら、その問題が重要だと考えているから。そういう人たちはNixを求めてる。ほとんどの人は最初のグループにいるから、成長したい会社はそのグループに合った解決策を提供することになるんだよね。

彼らはバージョンがないところに無理やりバージョンを押し込もうとしてるみたい。まるで四角い立方体を丸い穴に押し込もうとしてる感じ。「デフォルトバージョン」がそれに依存しているものを壊す?それって何?dockerの「:latest」タグを使って、毎回新しいサーバーが転倒するのに驚いてるみたいなもんだよ。前の「デフォルト」イメージとは実際には違うバージョンなのに。この記事の説明は全然理解できない。まるでソフトウェアの「バージョン」が何か全く分かってない人たちのようだ。「Nixの依存関係を別々のレイヤーに分ける方法がない」って - なんで?もちろん/nix/storeを必要なだけのレイヤーに分けることはできるよ。彼らはそもそもコンテナの使い方やNixの使い方を知ってるのか?この人たちの明らかな無能さを見れば、彼らの提案する解決策が腐った魚の匂いがするのも納得だね。典型的なNIH症候群。彼らがNixで解決できなかった問題に直面するのは驚くことじゃないよ。

VCを集めるのにどっちが良いか(多くの人にとってそれが目標) - Nixラッパーかデプロイメントプラットフォームか。

Nixを使わないのには賛成だけど、実際には問題じゃない理由で動作するシステムをゼロから再構築するのは根本的に狂ってると思う。ここで他の人が言ってるように、nix2containerやflakesは彼らの抱えている問題を解決すると思う。バージョン管理に関しては、3年前に書いたflakesが、最初に書いたときと全く同じバージョンと出力でビルドできるよ。市場に出てプラットフォームから資金を調達したいみたいだね :D 編集: さっきnixpacksのGitHubをチェックしたら、nixpkgsでrustPlatformを使っていることにすぐ気づいた。oxalicaのrust-overlayではなくて、これはRustの問題を調べる際にどんな軽い検索でも出てくるはずのものだし、私が使った中で最も便利で強力なオーバーレイの一つだよ。 [0] https://github.com/oxalica/rust-overlay

nixpkgsのハッシュに頼るんじゃなくて、自分たちの派生物を作れなかった理由が分からない。

Nixは、ランダムなバージョンじゃなくてコミット保証を提供してくれるんだ。エッジケースがあるときに、glibcの変更や競合する共有ライブラリがあると、厄介なことになるよ。ちょっと遅いかもしれないけど、Nixと上手く連携させる方法についてアドバイスできるよ。プロダクトはかっこいいね!

nixは、共有ライブラリの互換性の問題を非常に保守的に解決してる。何かが変わるたびに、重要かどうかに関わらず - コメントが変更されたり、ドキュメントが変わったり、テストケースが追加されたりすると - すべての依存関係を再ビルドするんだ。それだけじゃなくて、依存関係の依存関係、さらにそのまた依存関係まで、どんどん続いていく。これって、しばしば大規模な再ビルドを引き起こすことになるよ。確かに共有ライブラリの競合は避けられるけど、この解決策は非常に無駄だと思うし、開発も辛くなることがあるよ。nixpkgsのステージングプロセスを見てみて。

エッジケースがあると、悪い状況に陥ることになるよね。glibcの変更や、競合する共有ライブラリとか。Nixの価値は理解してるけど、「悪い状況」って言うのはちょっと大げさかな。せいぜい「Nixに比べてかなりの保証を失う」って感じ。とはいえ、95%のソフトウェアよりは「正しく動作する可能性が高い」ってことは間違いないよ。

まあ、私はNixのファンだけど、Nixから離れることを批判してるわけじゃないから、信じてほしい。そこまで強い感情的なつながりはないんだ。ただ、いくつかの不満についてはよく理解できてないし、もっと説明が必要だと思う。例えば:> Nixの最大の問題は、コミットベースのパッケージバージョニングだ。各パッケージの最新のメジャーバージョンしか利用できず、バージョンはnixpkgsリポジトリの特定のコミットに結びついている。Nixpkgsは素晴らしいリソースだけど、NixはNixpkgsじゃない。Nixpkgsは、任意のバージョンのツールチェーンを引っ張りたい場合には理想的じゃないけど、唯一の方法じゃないよ。例えば、Rustの任意のバージョンを引っ張るための素晴らしいNixツールがあるし、他のNixベースの開発ツールもこれを上手くやってる。> Nixの依存関係を別のレイヤーに分ける方法がない これは意味がわからない。文字通り、好きなように別のレイヤーに分けることができるよ。NixpkgsのDockerツールには、これをサポートする機能もあるし。> BuildkitライブラリのためにコードベースをRustからGoに変更した この部分はNixとは関係ないけど、興味深いね。ほとんどの人は気まぐれでプログラミング言語を変えることはないし、通常は最初からゼロから作るつもりのときにやることだよね。私には、RailpacksとNixpacksで別の人たちが作業しているように聞こえる。Nixに不慣れな人が未完成のNixソリューションを扱うことになったときに何が起こるかは見たことがあるから、あまり良い状況じゃないよ。ほとんどの人はNixを理解しようとしないからね。私はこの状況を引き起こすのが怖くて、仕事でNixを使うことはあまりないんだ。

正直、これはrail...自分たちのバージョンを作りたいみたいだから、新しいrailXだね、笑

私はNixを使ってないけど、これは軽視してるように見えるね:> Nixpkgsは素晴らしいリソースだけど、NixはNixpkgsじゃない。もしNixpkgsがデフォルトで、代替手段が追加の調査や努力を必要とするなら、ほとんどのユーザーにとってそれはNixなんだ。> これは意味がわからない。文字通り、好きなように別のレイヤーに分けることができるよ。NixpkgsのDockerツールには、これをサポートする機能もあるし。これは明らかで、簡単で、デフォルトの動作なの?

これはかなり上手く言われてると思う。nixpkgsはNixじゃないけど、nixpkgsは良い部分だと思う。私はNixOSを使っていて、人生で初めて、Linuxカーネルの最新バージョンをリリース日に使ってる。これは素晴らしいことだよ。年を取るにつれてDebian Stableには耐えられるようになったけど、いつも数年前に戻ったような気分になる ;) Nix言語については、何時間でも批判できるけど、仕方ないよね。古いし、彼らはできる限りのことをしたし、変える価値はないと思う。Nixのビルドシステムは、私には非常に原始的に感じるし、必要ないものを再ビルドすることが多い。例えば、私のNixOSインストーラーISOは、カーネルに渡すコマンドラインに依存している部分が多くて、[just console=ttyS2,1500000n8]、シリアルポートの速度を変えるには約3分のビルド時間がかかる。ちょっとおかしくて笑っちゃうけど、これでNixを使うのをやめるつもりはないよ…でも、私のビルドではこんなことは許さないかな。Dockerイメージに関しては、私の意見ではNixが最も苦手なところだと思う。ずっと前、Goでソフトウェアを書いていて、コンテナイメージにPostgresのpg_dumpバイナリを追加する必要があった。インフラチームがNixを使うことを提案したから、そうしたけど、私たちのイメージは圧縮されたGoバイナリの50MBから、何が入ってるかわからない1.5GBに膨れ上がった。pg_dumpは464Kなのに。結局、自分のやり方で、Bazelとrules_debianを使ってaptパッケージをインストールすることにしたら、結果(distrolessの上に)はずっとクリーンでコンパクトだった。実際のNixの経験から言うと、Nixシステムはいつも1.4GBになる。私のインストーラーISOは1.4GBだし、新しくインストールしたマシンも1.4GBだ。理由はわからないけど、そういうものなんだ。最後に、「大きなC++プロジェクトをビルドしたい」という状況は、よくある道だね。s/C++/Rustに変えても、特に何も変わらない。ライブラリの状況をもっと耐えやすくするためのビルドシステムが存在するけど、どれもNixと同じくらい複雑で、でもこのユースケースにはうまく機能するものもある。Nixは他の人のソフトウェアをビルドするためのビルドシステムを目指していて、nixpkgsをサポートしてるけど、非常に一般的な側面に落ち着いている。自分のソフトウェアをビルドするために設計されたビルドシステムは、その仕事をうまくやる傾向がある。個人的にはBazelに満足していて、他のものは使わないと思う(Go専用プロジェクトには「go build」を除いて)、でも他にもたくさんの選択肢があるよ。99%の確率で、Nixの代わりにそれを使うべきだし(そして、他の人がhome-managerで最新バージョンのあなたのものをインストールできるようにフレークを書いたり;もしかしたら、私は日常的に自分のソフトウェアを使う唯一の人で、実際にはそんなことをする必要はないのかもしれない…)

見逃しがちなのは、彼らのユーザーが自分の依存関係やバージョンを指定したい開発者だってことだと思う。nixの動き方とnixpkgsの構造上、任意のパッケージのバージョンを固定するってことは、nixpkgsツリー全体のコミットを固定することを意味する。node/python/rubyパッケージのビルドは、ツリーのパッケージディレクトリの外にあるものに依存しているから、バージョンとコミットのマッピングが必要なんだ。これは漏れがある抽象化でもあるから、ユーザーにそれを見せる必要がある。そうすると、彼らは「新しいファンシーなNode.jsパッケージをyarn addしたいだけなのに、nixpkgsリポジトリのさまざまな状態を合わせる必要がある」って状況に直面することになる。nixpkgsなしでnixを使うのは、より限定的な用途にはいいかもしれないけど、Railwayみたいなプラットフォームには正当化しにくい気がする。

Nix != Nixpkgs FreeBSDのportsに関してFreeBSDを試しているときにこれを言われたことがある。pkgは一般的に私にはうまくいくけど、ある日、道を外れてカスタムUSEフラグ(FreeBSDでは何て呼ぶか忘れた)を使ってvimをコンパイルしようとしたんだ。20以上の依存関係が引っ張られて、make menuconfigのたびに「これらのオプションが欲しいですか?」って聞かれて、合理的に見えるものをいくつか選んだら、23のうち16のパッケージが「これが必要で、あれが必要で、Fubar3はFubar4に非推奨」とかで失敗して、もう諦めた。Core OSの開発者が1万以上のパッケージをサポートできないのは理解できるけど、実際に使おうとすると(つまり、カスタム機能を有効にする、ただのストックコードをコンパイルするだけじゃない)高い確率で動かないってことははっきりさせるべきだと思う。もう一つの選択肢は、コンパイルできないものはportsリストから外して、portsnap fetchに現れる前に、少なくとも標準的な独立ビルドが成功する必要があるってことだね。

Nixの依存関係を別のレイヤーに分ける方法がない nix2container [1] は実際にそれができるよ。イメージに必要な依存関係のサブセットを含むレイヤーを明示的にビルドできるんだ。このセクションに例があるよ:https://github.com/nlewo/nix2container?tab=readme-ov-file#is... 例えば、イメージがbashを使っている場合、bashのクローズャを含むレイヤーを明示的に作成できる。このレイヤーはすべてのイメージで使えるし、bashのクローズャが変更されたときだけ再ビルドして再プッシュされる。> 依存関係を引き込むと、単一の/nix/storeレイヤーで巨大なイメージサイズになることが多い これは基本的なnixpkgs.dockerTools.buildImage関数のケースだけど、nix2containerやnixpkgs.dockerTools.streamLayeredImageでは当てはまらないよ。これらのツールは、Nixストアにレイヤーを書き込むのではなく、既存のストアパスを使ってイメージをプッシュするためのスクリプトをビルドするんだ(このスクリプトのNixランタイム依存関係)。nix2containerの実装については、すべてのレイヤーのNixストアパスを説明するJSONファイルをビルドして、Skopeoを使ってイメージをプッシュするんだ(Dockerデーモン、レジストリ、podmanなどに)。(免責事項:私はnix2containerの作者です) [1] https://github.com/nlewo/nix2container

nix2containerに感謝したい!AWS(ECR)へのデプロイに使ってるんだけど、ビルド間の反復時間が1桁の秒数にまで短縮されたよ。

ヘッドラインを見たときは、彼らが単一のインクリメンタルな変更(Nix)をしているのかと思ったけど、記事を読むと新しいプロジェクト名の下で全体を再構築しているみたいだね。> Nixから移行したので、Nixpacksという名前もRailpackに変更しました。また、Buildkitライブラリの関係でコードベースをRustからGoに変更しました。突然、Nixからの移行がインクリメンタルな変更ではなく、プロジェクト全体の完全なオーバーホールの一部のように感じられる。チームの交代があったのかな?それとも、最初からやり直してプロジェクト全体を再構築したかったのかな?単一のライブラリのために全く異なるプログラミング言語に切り替えるのも変だよね。RustでのライブラリFFIが大きな障害だとは思わないし。

https://github.com/railwayapp/nixpacks

失礼なわけじゃないけど、彼らが求めているものにはguixの方が適していたかもしれないね。guixが優れているとは言わないけど、彼らのパッケージに関する不満を考えると、より良い解決策だったかもしれない。