- Railway は、従来の Nixpacks に代わる新しいビルドシステム Railpack をリリース
- Railpack は、バージョン管理の細分化、より小さいイメージサイズ、改善されたキャッシュなどの面で、既存の Nixpacks より優れた機能を提供
- Nixpacks の コミットベースのバージョン管理方式 は、多様なユーザーニーズと拡張性において限界を示していた
- Railpack は、BuildKit 統合、シークレット環境変数の保護、多様な言語およびフレームワーク対応によって、ビルド環境の安定性と柔軟性を改善
- 現在 Node、Python、Go、PHP、静的 HTML をサポートしており、継続的にフレームワークと言語の対応を拡大中
概要と背景
- Railway は次世代ビルドシステム Railpack を公開
- Railpack は、Railway プラットフォーム上で 1,400 万以上のアプリを Nixpacks でビルドして得た経験をもとに新たに開発したツール
- 既存の Nixpacks は全ユーザーの 80% には適していたが、20 万人以上のユーザーが 制約 に突き当たり不便を感じていた
- ユーザーベースの拡大と持続可能なビルド環境のために、大規模なアップグレード が必要だと判断
Railpack の主な改善点
- バージョン管理の細分化: 各パッケージに対して
major.minor.patch 単位の細かなバージョン指定をサポートし、Nix の曖昧なバージョン方式の限界を克服
- 小さいイメージサイズ: Node は 38%、Python は最大 77% まで基本ビルドイメージのサイズを削減し、より高速なデプロイ体験を提供
- キャッシュ強化: BuildKit と直接統合し、レイヤーとファイルシステムを制御して、キャッシュヒット率を向上させ、環境間でのキャッシュ共有も可能
- すでに railway.com と中央サービスでは Railpack ビルドが適用済み
Nixpacks 利用時の問題点
- Nix のパッケージバージョン管理方式は コミットベース の構造で、最新の major バージョンのみを提供し、各バージョンは nixpkgs リポジトリの特定コミットに対応
- 小さなパッチバージョンまで手動で管理しなければならない非効率さがあり、コントリビューターにとってもバージョン管理が直感的でなく、アクセシビリティが低下
- Node や Python のような言語でも、結局は最新の major バージョンのみをサポート
- バージョン更新時にコミットハッシュが変わることで、他のパッケージバージョンまで一度に影響 を受け、ユーザーの信頼性低下や予期しないビルド失敗が発生する可能性
- Nixpacks の場合、
/nix/store という 1 つのレイヤーにすべての依存関係が含まれるため、イメージを効果的に分割したりサイズを削減したりするのが困難
- キャッシュ も、環境変数を注入するたびにレイヤーが常に invalidate されるため、十分に活用できなかった
Nix 自体の問題ではなく、利用方法の限界
- Nix 自体の設計上の問題ではなく、Railway における利用方法と抽象化の仕方が問題として作用
- ユーザーが Nix の derivation の概念や内部のバージョン構造を理解しなくても済むよう設計しようとしたが、現実的には不可能だと判断
- こうした問題を解決するために Railpack の開発を進めた
Railpack の技術アーキテクチャ
- Rust → Go へのコードベース変更: BuildKit の活用とエコシステムへの対応力強化のため、Go 言語へ移行
- BuildKit LLB とフロントエンド: カスタム BuildKit LLB とフロントエンドを直接生成し、ビルドイメージの構造を精密に制御 → Node と Python の基本イメージは Nixpacks 比で大幅に軽量化
- Mise によるバージョン管理: パッケージのインストールとバージョン解決に Mise を使用し、今後は他の実行ファイルソースにも容易に対応可能
- ビルドに成功した場合、その時点の 依存関係 lock-in を適用 → Node のデフォルトバージョンが 22 から 24 に変わっても既存ビルドは壊れない
- BuildKit の secret 機能を活用して、環境変数のセキュリティと管理 を改善
Railpack のビルド段階
- Analyze: コード分析により必要なパッケージ、実行コマンド、起動コマンドを導出
- Plan: JSON でシリアライズ可能な形のビルド計画を作成(複数段階を含み、各段階は前段階の結果またはイメージ全体に依存)
- Generates: BuildKit のビルドグラフを生成(入力/出力を基準に)
BuildKit を活用した戦略的ビルド
- Dockerfile が直列に動作するのに対し、BuildKit は複数のコマンドを並列処理でき、各段階ごとに細かな入力/出力制御が可能
- Railpack はコード分析結果をもとにすべてのビルド段階を定義し、各段階の依存関係を低レベルで詳細に指定
- この計画を BuildKit LLB グラフへ変換して解決
- 環境変数などが変更された場合は、その値のハッシュ値でファイルをマウントし、コードと変数に変化がなければ キャッシュヒット を保証
- 結果として、イメージ生成方式を Railpack が完全にコントロール可能
Railpack 導入で可能になる新機能
- Vite、Astro、CRA、Angular の静的サイトビルド/デプロイを 設定不要でサポート
- Railway UI と ビルドプロセスの緊密な統合
- 言語の最新バージョン への対応が Railpack 自体のリリースなしでも可能
- プロジェクトごとに 環境間キャッシュ最適化 を提供
- 現在 Node、Python、Go、PHP、静的 HTML をサポートしており、フレームワークと言語の対応を継続的に拡大中
オープンソースと今後の計画
- Railpack は Beta 状態で公開中で、有効化するだけですぐに利用可能
- 公式ドキュメント、実際のコード、公開サポート窓口まで railpack.com で提供
- 今後は広く使われる言語への深いサポートを優先し、コア API と抽象化レベルの確立後に対象範囲を拡大する計画
1件のコメント
Hacker Newsのコメント
私はNix愛好家だが、Nixを使わないという判断に感情的に執着しているわけではない、ということは信じてほしい。ただ、この記事の不満点のいくつかはよく理解できず、もう少し説明が必要だと感じる。たとえば「Nix最大の問題はコミットベースのパッケージバージョン管理だ」という話がある。Nixpkgsは素晴らしいリソースだが、NixとNixpkgsは同じではない。ツールチェーンの任意のバージョンを持ってくるにはNixpkgsはかなり不向きだが、Nixには別のやり方もある。たとえばRustの任意バージョンをうまく取得するNixツール群は本当によくできている。また、「Nixの依存関係は別レイヤーに分割できない」という話もあったが、まったく意味が分からない。望むどんな形にでも分割できる。NixpkgsのDockerツールもこれをサポートしている。コードベースをRustからGoに移した部分はNixと直接関係はないが興味深い。普通、言語変更は軽く決めるものではなく、そもそも作り直すつもりがあるときにやることが多い。RailpacksとNixpacksは別の人たちが作業していたのではないかと疑っている。Nixをよく知らない人たちが、仕上がっていないNixソリューションを組織で扱うことになったときに起きることも見たことがある。あまり良い光景ではなく、たいていの人はNixを学ぼうとしない。だから前職では、こういう状況を避けるためにNixをほとんど使わなかった
私はNixを活用するのは好きだが、Nixの基本的な使い勝手の問題について議論するたびに、「回避策はある」(ただし乏しいドキュメント、癖の強い言語、悪いエラーメッセージ、使ったことのある人しか知らない断片的な知識を頼りに、数十〜数百行のコードを追加しなければならない)という答えしか返ってこないのでうんざりしている。Nix関連の問題の大半はチューリング完全性ではなく、直感的なAPIのような基本機能が標準でないことから来ている。どのプロジェクトでも、Nixの活用がだんだんNix自身の問題解決に没頭する方向へ変わっていくなら、よく文書化された主流ツールがあるのに、わざわざNixを使う理由はない。実際、たいていの人はDockerを選ぶ。Nixが開発者体験の現実的な問題を現実的な時間で解決せず、理想的な純粋性ばかり追っているのはとても残念だ。もちろん皆が自発的に貢献しているのだが、こうした技術的努力が、設計の悪いUXのせいで実用にならないまま残っているのを見るのは本当に惜しい
私はNixを使っていないが、「Nix ≠ Nixpkgs」という主張は現実離れしているように感じる。大多数のユーザーにとって、代替案が追加の調査と労力を要求するなら、Nixpkgsが結局Nixそのものになる。「別レイヤーに分割できる」というのも、それが本当に直感的で、簡単で、デフォルトの挙動なのか気になる
重要なのは、Railwayのユーザーはそれぞれ欲しいパッケージのバージョンを指定したがる開発者だという点だ。NixとNixpkgsの構造上、あるパッケージのバージョンを固定するということは、nixpkgs全体ツリーのコミットを固定することを意味する。node/python/rubyパッケージのビルドはツリー外部に依存するものが多いため、バージョンとコミットの対応付けが必要になる。この抽象化は完璧ではないので、ユーザーが単に「yarn add パッケージ」をしたいだけでも、ツリーの状態を合わせる必要が出るかもしれない。NixpkgsなしでNixだけを使うのは限定的な用途には良いが、Railwayのようなプラットフォームには厳しい選択だ
バージョン管理の議論がよく分からない。私はNixを使い始めたばかりだが、特定のコミットから取得したパッケージを確かに持っている
うまく要点を突いて説明していると思う。NixpkgsとNixは違うが、実際のところNixpkgsこそが本当の強みだ。NixOSを使って初めて、Linuxカーネルの最新版をリリース当日に使えた。Debian Stableも悪くはないが、いつも数年前に戻ったような感じがする。ただし、Nix言語には批判点が多い。古い言語だし、ベストを尽くした結果なのだろうが、わざわざ変える必要はないと思う。Nixのビルドシステムは古典的で、不必要な再ビルドが多いと感じる。たとえばNixOSのインストールISOで、カーネルに渡すコマンドライン(例: コンソールポート速度)を1つ変えただけで3分ほどかかる奇妙なビルド現象が起きる。面白くはあるが、それでNixを捨てるわけではない。ただ、自分のビルドシステムでは絶対に許容しない挙動だ。Dockerイメージを作るのにNixを使うのは個人的には最悪だと思う。以前、Goで作ったバイナリにPostgresのpg_dumpバイナリだけを入れたかったのだが、インフラチームがNixを勧めたので使ったところ、圧縮済みのGoバイナリは50MBだったのに、1.5GBの怪物イメージになった。pg_dumpは464KBしかない。結局、Bazelとrules_debian、distrolessの組み合わせでずっときれいにできた。ほとんどのNixシステムは1.4GBがデフォルトのように感じる。大きなC++プロジェクトのビルドも、Nixが特別うまいわけではない。むしろ自前ソフトウェアのビルド用システムは、それぞれの必要にもっとよく合う傾向がある。私はBazelが好きだし、Goプロジェクトなら単純にgo buildだけ使いたい。99%の場合はNixの代わりにこういうツールを使い、ただ更新や配布のためにflakeを書いてhome-managerで使うことはある
バージョン選択が妙に感じる。nixpkgsのバージョンは、システムを運用したりビルドしたりするには確かに妥当だ。ランタイムやコンパイラを提供するプラットフォームなら、devenvのように直接バージョンを提供する必要がある。たとえば nixpkgs-python は「すべてのPythonバージョンを、Nixで時間ごとに最新化」して提供する。RailwayがデプロイID環境変数をすべてのビルドに注入するというのも、インストール後のレイヤーでやってもよかったはずだ。パッケージも複数レイヤーに分割できるし、レイヤー数の調整の自動化も可能だ
DevOps/SRE経験者として、誰かが依存関係管理システムを作ろうとすると、たいてい2つの方向のどちらかに流れるのを見てきた(たとえばPythonなら)。選択肢1: 「モノレポ + 共通環境」。利点は管理しやすく、セキュリティパッチ対応が楽で、統一しやすい。欠点は、誰かが常に特別なバージョンを欲しがること、段階的ロールアウトが難しいこと、スリムなイメージ構築が難しいこと。選択肢2: 「各自でconda/venv」。利点は個別最適でき、不要パッケージを除外でき、段階的アップグレードが可能なこと。欠点は環境が多すぎること、相互互換性が未検証なこと、セキュリティ管理が悪夢になること。結局のところ、「解決策はなく、あるのはトレードオフだけだ」という言葉を、経験を積むほど実感する
「Nixそのものは問題ない。使い方に問題があった」という話は、『適材適所のツールを使え』の良い例だと思う。Nixはある場面では素晴らしいが、別の場面では最悪だ。問題は、学ぶのに時間がかかるので、ある程度慣れて判断を下すころには、すでに投資した時間が惜しくて簡単には方向転換できず、結局もともとの目的に無理やりNixを使い続けてしまう現象だ
バージョンが存在しないところに、無理やりバージョンを持ち込もうとしているように見える。「デフォルトバージョン」のせいで依存関係が壊れるって? Dockerの
:latestタグを使って、変わるたびにサーバーが壊れるのと似たようなものだ。このブログの内容はあまり理解できない。「Nixの依存関係は別レイヤーに分けられない」という点にも共感しない。/nix/storeは望むだけ分割できるし、コンテナとNixをどう使うべきかもあまり分かっていないように見える。この程度の力量なら、提示された代替案でも結局同じ問題を繰り返しそうだ。典型的なNIH(Not Invented Here、自前主義)症候群の例だNixが合わない場所で使わないのは当然だが、すでに動いているシステムを、他の人たちがすでに解決している問題だと少し調べれば分かるのに、最初から最後まで作り直すのは根本的に奇妙だと感じる。nix2containerやflakesで、すべての問題を解決できるはずだ。バージョン管理についても、3年前に書いたflakesが今でも同じようにビルドできて、結果も変わらない。なんとなく、市場進出や資金調達を狙ったプラットフォーム転換の匂いがする。ちなみにnixpacksのGitHubを見るとrustPlatformしか使っておらず、Rustの問題なら rust-overlay が事実上の答えだ
どちらの方法がVC調達をしやすいか考えるなら、nixラッパーより「デプロイプラットフォーム」という肩書きのほうが有利だ
「Nixの依存関係は別レイヤーに分割できない」という話とは違って、nix2container はまさにその分割ができる。たとえばbashが必要なイメージなら、bashを含むレイヤーだけを別に作れるし、そのレイヤーはbashが変わったときだけ再ビルド・再プッシュすればよい。「依存関係のせいで巨大なイメージが単一の/nix/storeレイヤーになる」というのも、nixpkgs.dockerTools.buildImage関数には当てはまるが、nix2containerやnixpkgs.dockerTools.streamLayeredImageには当てはまらない。このツールは実際にはスクリプトを生成し、それを通してイメージをプッシュする。nix2containerはすべてのレイヤーのパスをJSONにし、Skopeoを使ってイメージをDocker、レジストリ、podmanなどへプッシュする。(参考までに、私がnix2containerの作者だ)
nix2containerには本当に感謝している。AWS(ECR)へのデプロイに使っていて、ビルド間の切り替え時間が一桁秒まで減った
うちもDockerイメージのサイズ問題のためにnix2containerを試す予定だった。良いツールを作ってくれてありがとう
ここでの核心的な問題は、言語パッケージマネージャーが助長する「カスタムバージョンスープ」に固執しようとする姿勢そのものだと思う(このやり方は持続可能ではない)。代替案のMiseは、パッケージ間のバージョン制約を理解しておらず、各パッケージのテストもまったくしていない。同レベルの信頼性はまったく期待できない
カスタムバージョンスープが持続可能でないのは事実だが、人々がこれを使い続けるのは、うまく動くからだ。OSレベルのライブラリは非常に保守的に管理されていて簡単には壊れず、miseやasdfのようなツールでその上にカスタムなバージョン組み合わせを載せても、たいてい問題なく動く。壊れても、バージョンや設定をいじればすぐ直る。壊れるのは不快だが、致命的ではない。追加の学習や努力が必要なシステムは時間の無駄だと見なされる。ただ、「壊れない状態」をより重視する人たちは、むしろ学習コストや不便さがあってもNixを好む。Railwayのように多くのユーザーを相手にするところなら、結局は前者のグループ(簡便さ、慣性)をより意識した選択をする
「カスタムバージョンスープ」とは何を意味し、代替は何なのか気になる
どちらも十分可能だ。たとえばRustパッケージはCargo.lockの情報でNixから簡単にビルドできる。Nixpkgsはカスタムなバージョン組み合わせとは相性が悪いが、Nixそのものは十分にこなせる
Nixが保証するのは任意バージョンではなく、コミット単位の保証だ。glibcの変更や共有ライブラリの衝突のようなエッジケースでは苦労するかもしれない。もう手遅れかもしれないが、Nixをもっと上品に使う方法についてコンサルティングすることもできる。製品自体は素晴らしいと思う
nixは共有ライブラリ衝突を非常に強力に防ぐ。ただし、些細な変更(コメント、文書など)でも、関係するすべての下位依存物まで丸ごと再ビルドされる。その結果、非常に大規模な再ビルドが必要になり、開発が苦痛になることがある。nixpkgsのstagingプロセスを見ると分かる
Nixの価値は十分理解している。ただ、「壊れる」という言い方は少し大げさだと思う。Nixに比べればいくつか大きな保証を失うのは事実だが、それでも大半のソフトウェアよりはずっと高い確率でちゃんと動くと思う
自分自身のderivationsを作らずに、なぜわざわざnixpkgsのハッシュに依存したのか分からない
多くのコメントが「実はNixで全部解決できる、ただし私のような専門家である必要がある」という雰囲気で、興味深かった
もしある会社が、あらゆる技術とビジネスをJavaScriptでやっているのに、既存の中核概念(関数、配列など)を理解できないせいでNIH(独自仕様の新言語開発)に走るなら、それは内部の不足により近い問題だ
Nixの話になるといつも繰り返される、よくある空気感だ
これこそがNixの空気感だ。「自分が世界を救う」という典型的な物語と、「自分の望む機能がない」という反応に対して、常に「お前の使い方が悪いだけだ」という答えが返ってくる