Why We're Moving on From Nix (Introducing Railpack)

Bài tóm tắt Railway 2025 3. Quy mô lớn Độ khó: Trung bình

Railway · 2025 · 3. Quy mô lớn · Độ khó: Trung bình — bài gốc

Bài ra mắt sản phẩm; mọi số liệu là theo Railway. Hệ được nói tới là builder biến code của người dùng thành image, tức là "CI" mà Railway chạy cho khách.

Bối cảnh

  • Nixpacks (ra mắt khoảng 2022) là builder mặc định của Railway, đã build hơn 14 triệu app. Theo Railway, nó chạy tốt cho 80% người dùng, nhưng 20% còn lại là khoảng 200.000 người gặp giới hạn hằng ngày (2025); họ muốn đi từ 1 triệu lên 100 triệu người dùng.
  • Vấn đề với cách họ dùng Nix (theo Railway, không phải lỗi của Nix):
    • Phiên bản gắn với commit của nixpkgs: chỉ có bản major mới nhất của mỗi gói; muốn hỗ trợ từng bản patch thì phải giữ bảng "phiên bản → commit SHA". Node và Python rốt cuộc chỉ hỗ trợ bản major mới nhất. Tệ hơn, cập nhật một commit để có bản mới của một gói làm mọi gói khác đổi theo; đổi phiên bản mặc định thì build đang chạy được bỗng hỏng.
    • Image lớn: mọi phụ thuộc build lẫn runtime nằm chung một layer /nix/store, không tách được.
    • Cache kém: Railway tiêm biến môi trường deployment ID vào mọi build, nên mọi layer sau chỗ biến đó trong Dockerfile luôn bị invalidate.

Thay đổi chính

  • Railpack viết lại từ đầu, đổi từ Rust sang Go (để dùng thư viện BuildKit). Ba bước: Analyze (xem code, quyết định gói, lệnh, lệnh start) → Plan (build plan dạng JSON, mỗi bước khai báo rõ đầu vào là bước nào hay image nào) → Generate (dựng đồ thị BuildKit LLB và frontend riêng).
  • Dockerfile tuyến tính, còn đồ thị BuildKit song song: mỗi lệnh là một stage riêng, kiểm soát chính xác layer đầu vào và cách ghép filesystem cuối. BuildKit đi ngược từ đích, lấy từ cache nếu được.
  • Cache theo biến thật sự dùng: Railpack băm giá trị các biến môi trường mà một bước dùng, mount file chứa hash vào đầu vào của bước đó; code và biến dùng không đổi thì trúng cache. Cache dùng chung được giữa các environment.
  • Khoá phụ thuộc của lần build thành công (dùng Mise để phân giải phiên bản), nên đổi mặc định Node 22 → 24 không làm hỏng build cũ; hỗ trợ major.minor.patch. Secret đi qua BuildKit secrets để không lộ trong log hay image.

Kết quả (theo Railway)

  • Image nền nhỏ hơn 38% (Node) và 77% (Python); nhiều lần trúng cache hơn. Bản beta lúc ra mắt, đã build cho railway.com.

Vì sao

Bài học cho SWE

  • Tách phiên bản từng phụ thuộc khỏi nhau; một con trỏ chung cho mọi gói biến mỗi lần nâng cấp thành thay đổi toàn cục.
  • Khoá phụ thuộc của lần build thành công để thay đổi mặc định không phá người dùng cũ.
  • Mô hình build dạng đồ thị với đầu vào khai báo rõ thì cache và song song hoá được; dạng tuyến tính thì một dòng đổi làm mất mọi thứ phía sau.

Liên quan