1 điểm bởi GN⁺ 2025-06-09 | 1 bình luận | Chia sẻ qua WhatsApp
  • Railway đã ra mắt Railpack, hệ thống build mới thay thế cho Nixpacks trước đây
  • Railpack mang lại các tính năng vượt trội hơn Nixpacks trước đây ở các mặt như quản lý phiên bản chi tiết hơn, kích thước image nhỏ hơn và cải thiện caching
  • Cách quản lý phiên bản dựa trên commit của Nixpacks đã bộc lộ giới hạn về khả năng mở rộng và đáp ứng nhiều nhu cầu người dùng khác nhau
  • Railpack cải thiện tính ổn định và linh hoạt của môi trường build nhờ tích hợp BuildKit, bảo vệ biến môi trường bí mật và hỗ trợ nhiều ngôn ngữ cũng như framework
  • Hiện tại hỗ trợ Node, Python, Go, PHP, HTML tĩnh và đang tiếp tục mở rộng hỗ trợ framework và ngôn ngữ

Tổng quan và bối cảnh

  • Railway đã công bố Railpack, hệ thống build thế hệ tiếp theo
  • Railpack là công cụ mới được phát triển dựa trên kinh nghiệm build hơn 14 triệu ứng dụng bằng Nixpacks trên nền tảng Railway
  • Nixpacks trước đây phù hợp với 80% người dùng, nhưng hơn 200.000 người dùng đã gặp phải các ràng buộc gây bất tiện
  • Họ đánh giá rằng cần một nâng cấp lớn để mở rộng tệp người dùng và xây dựng môi trường build bền vững

Những cải tiến chính của Railpack

  • Quản lý phiên bản chi tiết: hỗ trợ chỉ định phiên bản chi tiết theo đơn vị major.minor.patch cho từng package, khắc phục hạn chế từ cách quản lý phiên bản thiếu rõ ràng của Nix
  • Kích thước image nhỏ hơn: với Node giảm 38% và Python giảm tới 77% kích thước image build mặc định, mang lại trải nghiệm triển khai nhanh hơn
  • Tăng cường caching: tích hợp trực tiếp với BuildKit để kiểm soát layer và file system, nâng cao tỷ lệ cache hit và cho phép chia sẻ cache giữa các môi trường
  • Build Railpack đã được áp dụng cho railway.com và các dịch vụ trung tâm

Các vấn đề khi dùng Nixpacks

  • Cách quản lý phiên bản package của Nix có cấu trúc dựa trên commit, chỉ cung cấp major version mới nhất, và mỗi phiên bản tương ứng với một commit cụ thể trong kho nixpkgs
  • Tồn tại sự kém hiệu quả khi phải quản lý thủ công cả các phiên bản patch nhỏ; ngay cả với contributor thì việc quản lý phiên bản cũng không trực quan, làm giảm khả năng tiếp cận
  • Với các ngôn ngữ như Node hay Python, rốt cuộc cũng chỉ hỗ trợ major version mới nhất
  • Khi cập nhật phiên bản, việc thay đổi commit hash có thể ảnh hưởng đồng loạt đến cả các phiên bản package khác, làm giảm độ tin cậy với người dùng và có thể gây lỗi build ngoài dự kiến
  • Trong Nixpacks, mọi dependency đều nằm trong một layer duy nhất là /nix/store, nên rất khó tách image hiệu quả hoặc giảm kích thước
  • Caching cũng không hiệu quả vì mỗi lần inject biến môi trường thì layer luôn bị invalidate, khiến cache không được tận dụng đúng mức

Không phải vấn đề của chính Nix mà là giới hạn trong cách sử dụng

  • Đây không phải vấn đề trong thiết kế của Nix, mà là cách Railway sử dụng và trừu tượng hóa nó đã tạo ra các điểm yếu
  • Họ từng cố thiết kế để người dùng không cần hiểu khái niệm derivation hay cấu trúc phiên bản nội bộ của Nix, nhưng kết luận rằng điều đó trên thực tế là không thể
  • Vì những vấn đề trên, họ đã phát triển Railpack

Kiến trúc kỹ thuật của Railpack

  • Thay đổi codebase từ Rust → Go: chuyển sang Go để tận dụng BuildKit và tăng khả năng thích ứng với hệ sinh thái
  • BuildKit LLB và frontend: tự tạo custom BuildKit LLB và frontend để kiểm soát chính xác cấu trúc của image build → image mặc định cho Node và Python nhẹ hơn đáng kể so với Nixpacks
  • Quản lý phiên bản bằng Mise: sử dụng Mise để cài package và phân giải phiên bản, giúp sau này dễ hỗ trợ thêm các nguồn executable khác
  • Khi build thành công, áp dụng khóa dependency tại thời điểm đó → dù phiên bản mặc định của Node đổi từ 22 sang 24 thì các bản build cũ vẫn không bị hỏng
  • Tận dụng tính năng secret của BuildKit để cải thiện bảo mật/quản lý biến môi trường

Các bước build của Railpack

  • Analyze: phân tích code để xác định package cần thiết, command chạy và lệnh khởi động
  • Plan: tạo kế hoạch build ở dạng có thể serialize thành JSON (gồm nhiều bước, mỗi bước phụ thuộc vào kết quả bước trước hoặc toàn bộ image)
  • Generates: tạo đồ thị build của BuildKit (dựa trên input/output)

Chiến lược build với BuildKit

  • Trong khi Dockerfile hoạt động theo kiểu tuần tự, BuildKit có thể xử lý nhiều lệnh song song và kiểm soát chi tiết input/output của từng bước
  • Railpack định nghĩa toàn bộ các bước build từ kết quả phân tích code, đồng thời mô tả chi tiết phụ thuộc của từng bước ở mức thấp
  • Kế hoạch này sau đó được chuyển thành đồ thị BuildKit LLB để giải quyết
  • Khi các giá trị như biến môi trường thay đổi, hệ thống sẽ mount file bằng giá trị hash của chúng; nếu code và biến không đổi thì cache hit được đảm bảo
  • Kết quả là Railpack có thể kiểm soát hoàn toàn cách image được tạo ra

Những tính năng mới có thể có nhờ Railpack

  • Hỗ trợ không cần cấu hình cho build/triển khai các website tĩnh dùng Vite, Astro, CRA, Angular
  • Tích hợp chặt chẽ quy trình build với Railway UI
  • Có thể hỗ trợ phiên bản ngôn ngữ mới nhất mà không cần phát hành lại chính Railpack
  • Cung cấp tối ưu caching giữa các môi trường theo từng project
  • Hiện tại hỗ trợ Node, Python, Go, PHP, HTML tĩnh và đang tiếp tục mở rộng hỗ trợ framework và ngôn ngữ

Mã nguồn mở và kế hoạch tương lai

  • Railpack hiện được công bố ở trạng thái Beta và có thể dùng ngay chỉ bằng cách kích hoạt
  • Tài liệu chính thức, mã nguồn thực tế và kênh hỗ trợ công khai đều được cung cấp tại railpack.com
  • Trong tương lai, họ sẽ ưu tiên hỗ trợ sâu cho các ngôn ngữ phổ biến, rồi mở rộng phạm vi sau khi xác lập API lõi và mức độ trừu tượng hóa

1 bình luận

 
GN⁺ 2025-06-09
Bình luận trên Hacker News
  • Tôi là người yêu thích Nix, nhưng mong mọi người tin rằng tôi không hề bám chấp cảm tính vào việc phải dùng Nix. Tuy vậy, tôi thấy một số lời phàn nàn trong bài này khá khó hiểu và cần được giải thích thêm. Ví dụ có câu “vấn đề lớn nhất của Nix là quản lý phiên bản gói dựa trên commit”. Nixpkgs là một tài nguyên tuyệt vời, nhưng Nix và Nixpkgs không phải là một. Nếu muốn lấy một phiên bản tùy ý của toolchain thì Nixpkgs quả thật rất không phù hợp, nhưng với Nix vẫn có cách khác. Chẳng hạn, các công cụ Nix để lấy đúng phiên bản Rust tùy ý làm việc rất tốt. Tôi cũng thấy có ý “không thể tách dependency của Nix thành các layer riêng”, điều đó theo tôi hoàn toàn vô lý. Bạn có thể tách theo bất kỳ cách nào mình muốn. Công cụ Docker của Nixpkgs cũng hỗ trợ việc này. Việc họ chuyển codebase từ Rust sang Go không liên quan trực tiếp đến Nix, nhưng tôi thấy khá thú vị. Thông thường người ta không nhẹ nhàng quyết định đổi ngôn ngữ, mà thường chỉ làm vậy khi vốn đã định xây lại từ đầu. Tôi nghi ngờ Railpacks và Nixpacks là do những nhóm người khác nhau làm. Tôi cũng từng thấy chuyện gì xảy ra khi những người không hiểu rõ Nix phải xử lý một giải pháp Nix chưa hoàn thiện trong tổ chức. Trông không hề ổn, và đa số mọi người không muốn học Nix. Vì vậy ở chỗ làm cũ của tôi, chúng tôi gần như không dùng Nix để tránh rơi vào tình huống đó

    • Tôi thích tận dụng Nix, nhưng mỗi lần bàn về những vấn đề cơ bản khi sử dụng Nix thì câu trả lời tôi luôn nhận được chỉ là “có cách lách” (nhưng phải thêm hàng chục đến hàng trăm dòng mã, với tài liệu thiếu thốn, ngôn ngữ kỳ quặc, thông báo lỗi tệ và những thông tin rời rạc chỉ người từng dùng mới biết), nên tôi rất mệt mỏi. Phần lớn vấn đề của Nix không nằm ở tính Turing-complete, mà ở chỗ thiếu những thứ có sẵn cơ bản như API trực quan. Nếu trong mọi dự án, việc dùng Nix dần biến thành việc chìm đắm trong giải quyết chính các vấn đề của Nix, thì chẳng có lý do gì phải dùng Nix khi đã có các công cụ phổ biến, được tài liệu hóa tốt hơn. Thực tế đa số mọi người cũng chọn Docker. Điều khiến tôi thất vọng là Nix cứ khăng khăng giữ sự thuần khiết lý tưởng thay vì giải quyết các vấn đề thực tế của trải nghiệm lập trình viên trong một khung thời gian hợp lý. Tất nhiên mọi người đều đóng góp tự nguyện, nhưng thật đáng tiếc khi thấy nỗ lực kỹ thuật như vậy rốt cuộc lại không thể dùng được trên thực tế chỉ vì UX được thiết kế quá tệ

    • Tôi không dùng Nix, nhưng lập luận “Nix ≠ Nixpkgs” nghe có vẻ tách rời thực tế. Với đa số người dùng, nếu phương án thay thế đòi hỏi nghiên cứu và nỗ lực bổ sung, thì rốt cuộc Nixpkgs chính là Nix. Còn chuyện “có thể tách thành layer riêng” thì tôi muốn biết liệu điều đó có thật sự trực quan, đơn giản và là hành vi mặc định hay không

    • Điều quan trọng là người dùng của Railway là các lập trình viên muốn chỉ định đúng phiên bản các gói họ cần. Với cấu trúc của Nix và Nixpkgs, việc ghim phiên bản của một gói nào đó có nghĩa là ghim commit của toàn bộ cây nixpkgs. Vì build của các gói node/python/ruby phụ thuộc khá nhiều vào các thành phần bên ngoài cây, nên phải có ánh xạ giữa phiên bản và commit. Lớp trừu tượng này không hoàn hảo, nên chỉ riêng việc người dùng muốn đơn giản yarn add package thôi cũng có thể buộc phải khớp trạng thái của cả cây. Dùng riêng Nix mà không có Nixpkgs có thể ổn với các trường hợp hạn chế, nhưng với một nền tảng như Railway thì đó là lựa chọn khó khăn

    • Tôi không hiểu lắm tranh cãi về quản lý phiên bản. Tôi mới dùng Nix lần đầu, nhưng rõ ràng tôi đang có các gói lấy từ một commit cụ thể

    • Tôi nghĩ bài viết đã chỉ ra đúng vấn đề. Nixpkgs và Nix là khác nhau, nhưng trên thực tế Nixpkgs mới là lợi điểm thật sự. Dùng NixOS là lần đầu tiên tôi được dùng phiên bản Linux kernel mới nhất ngay trong ngày phát hành. Debian Stable cũng ổn, nhưng luôn tạo cảm giác bị quay ngược về vài năm trước. Dù vậy, ngôn ngữ Nix có rất nhiều điểm đáng phê bình. Nó là ngôn ngữ cũ, và dù kết quả hiện tại là tốt nhất có thể thì tôi cũng không nghĩ cần phải thay đổi. Hệ thống build của Nix mang màu sắc cổ điển nên tôi thấy có khá nhiều lần rebuild không cần thiết. Ví dụ, chỉ cần đổi một dòng lệnh truyền cho kernel trong ISO cài NixOS thôi (chẳng hạn tốc độ cổng console) mà lại xảy ra kiểu build kỳ quặc mất khoảng 3 phút. Buồn cười thật, nhưng tôi không vì thế mà bỏ Nix. Chỉ là đây là hiện tượng tôi sẽ không bao giờ chấp nhận trong hệ thống build của mình. Cá nhân tôi cho rằng dùng Nix để tạo Docker image là lựa chọn tệ nhất. Trước đây tôi chỉ muốn đưa thêm binary pg_dump của Postgres vào một binary viết bằng Go, nhưng đội hạ tầng gợi ý dùng Nix, và kết quả là binary Go đã nén vốn chỉ 50MB lại biến thành một quái vật 1.5GB. pg_dump chỉ có 464KB. Cuối cùng tôi dùng Bazel với rules_debian và distroless, sạch sẽ hơn nhiều. Hầu hết các hệ thống Nix khiến tôi có cảm giác 1.4GB là mặc định. Ngay cả khi build các dự án C++ lớn, Nix cũng không hẳn nổi trội. Ngược lại, các hệ thống phục vụ build phần mềm riêng thường hợp với nhu cầu cụ thể hơn. Tôi thích Bazel, còn với dự án Go thì tôi chỉ muốn dùng go build. Trong 99% trường hợp tôi sẽ dùng các công cụ như vậy thay vì Nix, còn nếu cần cập nhật hoặc triển khai thì có thể viết flake để dùng với home-manager

  • Việc chọn phiên bản nghe khá lạ. Phiên bản của nixpkgs rõ ràng hợp lý khi vận hành hay build hệ thống. Nếu là một nền tảng cung cấp runtime/compiler thì cần tự cung cấp phiên bản như devenv. Ví dụ nixpkgs-python cung cấp “mọi phiên bản Python, được cập nhật theo giờ bằng Nix”. Việc Railway tiêm biến môi trường deployment ID vào mọi bản build cũng hoàn toàn có thể làm ở một layer sau khi cài đặt. Gói cũng có thể tách thành nhiều layer, và số lượng layer cũng có thể được tự động điều chỉnh

  • Với kinh nghiệm DevOps/SRE, tôi thường thấy khi ai đó định xây một hệ thống quản lý dependency thì thường sẽ trôi về một trong hai hướng (ví dụ với Python). Lựa chọn 1: “monorepo + môi trường dùng chung”, ưu điểm là dễ quản lý, vá bảo mật thuận tiện, tập trung hóa. Nhược điểm là luôn có ai đó muốn phiên bản đặc biệt, khó rollout theo từng giai đoạn, và gặp vấn đề khi build image mỏng. Lựa chọn 2: “mỗi bên tự dùng conda/venv”, ưu điểm là tùy biến riêng lẻ, loại bỏ gói không cần, có thể nâng cấp theo từng bước. Nhược điểm là quá nhiều môi trường, không kiểm chứng tương thích chéo, và quản lý bảo mật thành cơn ác mộng. Kết luận là câu “không có giải pháp, chỉ có đánh đổi” càng làm lâu trong nghề càng thấy đúng

  • Tôi nghĩ câu “bản thân Nix không có vấn đề gì, vấn đề là ở cách dùng” là ví dụ điển hình cho việc nên dùng đúng công cụ đúng chỗ. Nix rất tuyệt ở một số nơi, nhưng ở nơi khác lại tệ nhất. Vấn đề là nó tốn quá nhiều thời gian để học, nên đến lúc đủ quen để đưa ra quyết định thì bạn đã tiếc công sức bỏ ra và khó mà đổi hướng, cuối cùng lại cố nhét Nix vào mục đích ban đầu

    • Tôi cũng có cảm giác tương tự. Ở vài khía cạnh, tôi thấy Nix là một mô hình lập trình còn trực quan hơn các OS khác. Chỉ là tôi chưa quen mà thôi. Nix expression có cấu trúc đầu vào (kho gói, key-value, v.v.) và đầu ra (hệ thống Linux). Có lẽ vài năm nữa tôi sẽ thấy quen hơn. Ví dụ AI có thể tạo shell.nix hay configuration.nix theo đúng đặc tả cũng nhờ cấu trúc đó. Tôi cũng hay tạo môi trường theo từng repo sao cho được bao bọc hoàn toàn, và nếu dùng flakes thì có lẽ còn tạo được môi trường tái lập tốt hơn. (flake.nix khá giống shell.nix nhưng còn hỗ trợ ghim phiên bản...)
  • Có vẻ như họ đang cố áp đặt khái niệm phiên bản vào nơi vốn không có nó. Dependency bị hỏng vì “phiên bản mặc định”? Điều đó giống như dùng tag :latest của Docker rồi mỗi lần nó đổi là server lại sập. Tôi không hiểu lắm nội dung blog này. Tôi cũng không đồng tình với chuyện “không thể tách dependency của Nix thành layer riêng”. Bạn có thể chia /nix/store ra bao nhiêu phần tùy ý, và có cảm giác tác giả cũng không thực sự hiểu cách dùng container cùng với Nix. Nếu năng lực đến mức đó thì phương án thay thế được đưa ra rồi cũng sẽ lặp lại cùng một vấn đề. Đây là ví dụ kinh điển của hội chứng NIH (tự làm công cụ riêng)

    • Không dùng Nix ở nơi nó không phù hợp thì là điều đương nhiên, nhưng việc xây lại từ đầu đến cuối một hệ thống đang chạy được, cho một vấn đề mà người khác đã giải quyết rồi chỉ cần tìm hiểu một chút là biết, thì về cơ bản nghe rất kỳ. Có vẻ nix2container hay flakes có thể giải quyết mọi vấn đề này. Cả về quản lý phiên bản nữa, các flakes tôi viết từ 3 năm trước đến giờ vẫn build y hệt và kết quả không đổi. Tôi còn ngửi thấy mùi chuyển hướng nền tảng để dễ ra thị trường hay gọi vốn. Nhân tiện, tôi xem GitHub của nixpacks thì thấy chỉ dùng rustPlatform, mà nếu vấn đề là Rust thì rust-overlay gần như là đáp án mặc định

    • Nếu nghĩ xem cách nào giúp gọi vốn VC dễ hơn, thì danh xưng “nền tảng triển khai” rõ ràng có lợi hơn là một wrapper Nix

  • Trái với câu “không thể tách dependency của Nix thành layer riêng”, nix2container cho phép chính xác kiểu phân tách đó. Ví dụ nếu cần một image có bash, bạn có thể tạo riêng layer chứa bash, và layer này chỉ cần build/push lại khi bash thay đổi. Còn ý “vì dependency mà cả một image khổng lồ bị dồn vào một layer /nix/store duy nhất” thì có thể đúng với hàm nixpkgs.dockerTools.buildImage, nhưng không đúng với nix2container hay nixpkgs.dockerTools.streamLayeredImage. Trên thực tế công cụ này tạo ra script rồi dùng nó để push image. nix2container tạo JSON chứa đường dẫn của mọi layer và dùng Skopeo để push image lên Docker, registry, podman, v.v. (Nhân tiện, tôi là tác giả của nix2container)

    • Tôi thật sự muốn cảm ơn về nix2container. Tôi dùng nó để triển khai lên AWS (ECR), và thời gian chuyển đổi giữa các bản build đã giảm xuống còn mức vài giây

    • Bên tôi cũng định thử nix2container vì vấn đề kích thước Docker image. Cảm ơn vì đã tạo ra công cụ hay

  • Tôi nghĩ vấn đề cốt lõi ở đây là thái độ cố giữ “món súp phiên bản tùy biến” do các package manager theo ngôn ngữ khuyến khích tạo ra (cách này không bền vững). Giải pháp thay thế là Mise thì lại không hiểu các ràng buộc phiên bản giữa các gói, cũng chẳng hề test từng gói. Không thể kỳ vọng mức độ tin cậy tương đương được

    • Đúng là “món súp phiên bản tùy biến” không bền vững, nhưng lý do người ta vẫn tiếp tục dùng là vì nó hoạt động khá tốt. Thư viện cấp OS được quản lý rất bảo thủ nên ít khi gãy, và khi chồng thêm các tổ hợp phiên bản tùy biến bằng các công cụ như mise hay asdf lên trên thì phần lớn vẫn ổn. Nếu có gãy thì chỉ cần chỉnh phiên bản/cấu hình là xử lý được ngay. Bị hỏng thì khó chịu đấy, nhưng không quá quan trọng. Những hệ thống đòi hỏi học thêm hay tốn thêm công sức bị xem là lãng phí thời gian. Ngược lại, những ai coi trọng trạng thái “không bị hỏng” hơn thì lại sẵn sàng chấp nhận đường cong học tập và bất tiện để chọn Nix. Với một nơi nhắm tới nhiều người dùng như Railway, cuối cùng họ sẽ ưu tiên nhóm đầu tiên hơn: sự đơn giản và quán tính

    • Tôi tò mò “món súp phiên bản tùy biến” nghĩa là gì, và lựa chọn thay thế là gì

    • Cả hai đều hoàn toàn khả thi. Ví dụ gói Rust có thể dễ dàng build bằng Nix từ thông tin trong Cargo.lock. Nixpkgs có thể xung đột với các tổ hợp phiên bản tùy biến, nhưng bản thân Nix thì xử lý rất ổn

  • Nix không đảm bảo theo phiên bản tùy ý mà đảm bảo theo commit. Bạn có thể gặp khó ở những ca góc cạnh như thay đổi glibc hay xung đột shared library. Có thể bây giờ đã muộn rồi, nhưng tôi vẫn có thể tư vấn cách dùng Nix thanh lịch hơn. Tôi nghĩ sản phẩm này tự thân rất hay

    • Nix ngăn xung đột shared library cực kỳ mạnh. Nhưng chỉ cần những thay đổi nhỏ như comment, tài liệu, v.v. thì mọi dependency phía dưới liên quan cũng bị rebuild sạch. Kết quả là cần những đợt rebuild rất lớn, và việc phát triển có thể trở nên đau đớn. Nhìn vào quy trình staging của nixpkgs là thấy

    • Tôi hiểu rất rõ giá trị của Nix. Chỉ là tôi thấy nói “sẽ hỏng to” thì hơi cường điệu. Đúng là sẽ mất đi một số đảm bảo lớn so với Nix, nhưng tôi vẫn nghĩ khả năng nó chạy ổn còn cao hơn phần lớn phần mềm khác rất nhiều

  • Tôi không hiểu vì sao họ lại phụ thuộc vào hash của nixpkgs thay vì tự tạo derivation của riêng mình

  • Thú vị là rất nhiều bình luận có không khí kiểu “thật ra Nix giải quyết được hết, chỉ là bạn phải là chuyên gia như tôi”

    • Nếu một công ty làm toàn bộ kỹ thuật lẫn kinh doanh bằng JavaScript, rồi vì không hiểu được các khái niệm cốt lõi sẵn có (hàm, mảng, v.v.) mà đi làm NIH (tự phát triển một ngôn ngữ mới theo chuẩn riêng), thì đó đúng hơn là vấn đề thiếu năng lực nội bộ

    • Đây luôn là bầu không khí quen thuộc mỗi khi nhắc đến Nix

    • Đây chính là không khí mà Nix tạo ra. Một kiểu tự sự rất điển hình “tôi sẽ cứu thế giới”, và mỗi khi ai đó phản ứng rằng “nó không làm được điều tôi muốn” thì câu trả lời luôn là “vì bạn chưa dùng đúng thôi”