1 points par GN⁺ 2025-06-09 | 1 commentaires | Partager sur WhatsApp
  • Railway lance Railpack, un nouveau système de build en remplacement de Nixpacks
  • Railpack offre de meilleures capacités que Nixpacks, notamment une gestion des versions plus granulaire, des images plus légères et un caching amélioré
  • Le modèle de gestion des versions basé sur les commits de Nixpacks a montré ses limites en matière de diversité des besoins utilisateurs et de passage à l’échelle
  • Grâce à l’intégration avec BuildKit, à la protection des variables d’environnement secrètes et à la prise en charge de nombreux langages et frameworks, Railpack améliore la stabilité et la flexibilité de l’environnement de build
  • Il prend actuellement en charge Node, Python, Go, PHP et le HTML statique, et continue d’étendre la prise en charge des frameworks et langages

Vue d’ensemble et contexte

  • Railway a dévoilé Railpack, son système de build de nouvelle génération
  • Railpack est un nouvel outil développé à partir de l’expérience acquise en construisant plus de 14 millions d’applications sur la plateforme Railway avec Nixpacks
  • Nixpacks convenait à 80 % des utilisateurs, mais plus de 200 000 d’entre eux se sont heurtés à des limitations qui ont nui à leur expérience
  • Railway a estimé qu’une grosse mise à niveau était nécessaire pour accompagner l’élargissement de sa base d’utilisateurs et bâtir un environnement de build durable

Principales améliorations de Railpack

  • Gestion des versions plus granulaire : prise en charge d’une spécification fine des versions au format major.minor.patch pour chaque package, ce qui permet de dépasser les limites du système de versions peu clair de Nix
  • Images plus légères : réduction de la taille des images de build de base jusqu’à 38 % pour Node et 77 % pour Python, pour des déploiements plus rapides
  • Caching renforcé : intégration directe avec BuildKit afin de contrôler les couches et le système de fichiers, d’améliorer le taux de cache hit et de partager le cache entre environnements
  • Les builds Railpack sont déjà utilisés sur railway.com et dans les services centraux

Problèmes rencontrés avec Nixpacks

  • Le système de gestion des versions des packages de Nix repose sur une structure basée sur les commits : seules les dernières versions majeures sont fournies, et chaque version correspond à un commit précis du dépôt nixpkgs
  • Cela entraîne une inefficacité, car il faut gérer manuellement jusqu’aux petites versions correctives, et même pour les contributeurs, la gestion des versions manque d’intuitivité
  • Pour des langages comme Node ou Python, seules les dernières versions majeures sont en pratique prises en charge
  • Lors d’une mise à jour de version, un changement de hash de commit peut affecter en bloc d’autres versions de packages, ce qui réduit la fiabilité pour les utilisateurs et peut provoquer des échecs de build inattendus
  • Dans Nixpacks, toutes les dépendances sont incluses dans une seule couche /nix/store, ce qui rend difficile le découpage efficace des images ou la réduction de leur taille
  • Le caching est lui aussi limité, car chaque injection de variable d’environnement invalide systématiquement la couche, empêchant une exploitation correcte du cache

Une limite de l’usage de Nix, pas de Nix lui-même

  • Le problème ne vient pas de la conception de Nix en elle-même, mais de la manière dont Railway l’utilisait et l’abstrayait
  • Railway avait voulu concevoir un système dans lequel les utilisateurs n’auraient pas besoin de comprendre le concept de derivation de Nix ni sa structure interne de versions, mais a conclu que cela n’était pas réalistement possible
  • C’est pour résoudre ces problèmes que Railpack a été développé

Architecture technique de Railpack

  • Évolution de la base de code de Rust vers Go : passage à Go pour mieux exploiter BuildKit et renforcer la compatibilité avec l’écosystème
  • BuildKit LLB et frontend : génération directe d’un LLB BuildKit personnalisé et de son frontend pour contrôler avec précision la structure des images de build → les images de base Node et Python sont nettement plus légères qu’avec Nixpacks
  • Gestion des versions avec Mise : utilisation de Mise pour l’installation des packages et la résolution des versions, avec la possibilité future de prendre facilement en charge d’autres sources d’exécutables
  • Lorsqu’un build réussit, les dépendances sont verrouillées à cet instant → même si la version Node par défaut passe de 22 à 24, les builds existants ne cassent pas
  • Exploitation de la fonctionnalité secret de BuildKit pour améliorer la sécurité et la gestion des variables d’environnement

Étapes de build de Railpack

  • Analyze : analyse du code pour déterminer les packages nécessaires, les commandes à exécuter et la commande de démarrage
  • Plan : génération d’un plan de build sérialisable en JSON (incluant plusieurs étapes, chacune dépendant du résultat de l’étape précédente ou de l’image complète)
  • Generates : génération du graphe de build de BuildKit (sur la base des entrées/sorties)

Stratégie de build avec BuildKit

  • Alors qu’un Dockerfile fonctionne de manière sérielle, BuildKit peut traiter plusieurs commandes en parallèle et contrôler finement les entrées/sorties à chaque étape
  • Railpack définit l’ensemble des étapes de build à partir des résultats de l’analyse du code et spécifie en détail, à bas niveau, les dépendances entre ces étapes
  • Ce plan est ensuite converti en graphe LLB BuildKit puis résolu
  • Lorsqu’une valeur comme une variable d’environnement change, Railpack monte un fichier à partir du hash de cette valeur ; si ni le code ni les variables ne changent, le cache hit est garanti
  • En conséquence, Railpack peut contrôler totalement la manière dont les images sont générées

Nouvelles fonctionnalités rendues possibles par Railpack

  • Prise en charge sans configuration du build et du déploiement de sites statiques Vite, Astro, CRA et Angular
  • Intégration étroite du processus de build avec l’interface Railway
  • Possibilité de prendre en charge les dernières versions des langages sans nouvelle release de Railpack
  • Optimisation du caching entre environnements pour chaque projet
  • Il prend actuellement en charge Node, Python, Go, PHP et le HTML statique, et continue d’étendre la prise en charge des frameworks et langages

Open source et feuille de route

  • Railpack est publié en version Beta et peut être utilisé immédiatement après activation
  • La documentation officielle, le code réel et les canaux publics de support sont disponibles sur railpack.com
  • À l’avenir, la priorité sera donnée à une prise en charge approfondie des langages les plus largement utilisés, avant un élargissement du périmètre une fois l’API cœur et le niveau d’abstraction établis

1 commentaires

 
GN⁺ 2025-06-09
Commentaires sur Hacker News
  • J’aime beaucoup Nix, mais j’aimerais qu’on me croie quand je dis que je ne suis pas émotionnellement attaché au fait de ne pas l’utiliser. Cela dit, je comprends mal certains reproches de cet article et j’ai l’impression qu’ils méritent davantage d’explications. Par exemple, il est dit que « le plus gros problème de Nix, c’est la gestion des versions des paquets basée sur les commits ». Nixpkgs est une ressource formidable, mais Nix et Nixpkgs ne sont pas la même chose. Si l’on veut récupérer une version arbitraire d’une toolchain, Nixpkgs est très mal adapté, mais il existe d’autres approches avec Nix. Par exemple, les outils Nix pour récupérer précisément la version voulue de Rust sont vraiment très bons. J’ai aussi vu passer l’idée que « les dépendances Nix ne peuvent pas être séparées en couches distinctes », ce qui, à mon avis, n’a aucun sens. On peut les découper comme on veut. Les outils Docker de Nixpkgs le prennent aussi en charge. Le passage du codebase de Rust à Go n’a pas de lien direct avec Nix, mais c’est intéressant. En général, on ne change pas de langage à la légère ; on le fait plutôt quand on avait déjà prévu de reconstruire quelque chose. Je me demande aussi si Railpacks et Nixpacks n’ont pas été développés par des équipes différentes. J’ai déjà vu ce qui arrive quand des gens qui connaissent mal Nix se retrouvent à devoir gérer en entreprise une solution Nix inachevée. Ce n’est pas beau à voir, et la plupart des gens n’essaient pas vraiment d’apprendre Nix. C’est d’ailleurs pour éviter ce genre de situation qu’à mon ancien travail on utilisait très peu Nix

    • J’aime utiliser Nix, mais à chaque fois qu’on discute de ses problèmes d’usage les plus élémentaires, on me répond seulement qu’« il existe un contournement » — au prix de dizaines ou centaines de lignes de code supplémentaires, avec une documentation insuffisante, un langage particulier, de mauvais messages d’erreur et des informations lacunaires connues uniquement de ceux qui l’ont déjà pratiqué — et c’est épuisant. La plupart des problèmes liés à Nix ne viennent pas de sa complétude de Turing, mais de l’absence de fonctionnalités de base comme des API intuitives. Si, dans tous les projets, l’usage de Nix finit par se transformer en immersion totale dans la résolution des problèmes de Nix lui-même, il n’y a plus vraiment de raison de l’utiliser alors qu’il existe des outils grand public bien documentés. En pratique, la plupart des gens choisissent Docker. Il est très décevant de voir Nix s’accrocher à une pureté idéale sans résoudre dans des délais réalistes les vrais problèmes d’expérience développeur. Bien sûr, tout le monde contribue bénévolement, mais c’est vraiment dommage de voir tant d’efforts techniques rester concrètement inutilisables à cause d’une UX mal conçue

    • Je n’utilise pas Nix, mais l’argument « Nix ≠ Nixpkgs » me semble déconnecté de la réalité. Pour l’immense majorité des utilisateurs, si l’alternative demande plus de recherche et d’effort, alors Nixpkgs finit de fait par être Nix lui-même. Et pour « on peut séparer en couches distinctes », je me demande si c’est vraiment intuitif, simple, et le comportement par défaut

    • L’important, c’est que les utilisateurs de Railway sont des développeurs qui veulent pouvoir choisir eux-mêmes les versions exactes des paquets qu’ils utilisent. Avec la structure de Nix et Nixpkgs, figer la version d’un paquet revient à figer le commit de tout l’arbre nixpkgs. Or les builds de paquets node/python/ruby dépendent souvent de beaucoup d’éléments extérieurs à cet arbre, ce qui oblige à maintenir une correspondance entre versions et commits. Cette abstraction n’est pas parfaite, si bien qu’un utilisateur peut devoir aligner l’état de l’arbre alors qu’il voulait simplement faire un yarn add paquet. Utiliser uniquement Nix sans Nixpkgs peut convenir à des usages limités, mais pour une plateforme comme Railway, c’est un choix difficile

    • Je ne comprends pas bien la polémique sur la gestion des versions. Je débute avec Nix, mais j’ai clairement des paquets récupérés depuis un commit précis

    • Je trouve que c’est très bien expliqué. Nixpkgs et Nix sont différents, mais en pratique Nixpkgs est le vrai atout. Avec NixOS, j’ai pu pour la première fois utiliser la toute dernière version du noyau Linux le jour même de sa sortie. Debian Stable est très bien aussi, mais on a toujours l’impression de remonter de plusieurs années dans le passé. En revanche, le langage Nix mérite beaucoup de critiques. C’est un vieux langage et, même s’il résulte d’un vrai effort, je ne pense pas qu’il y ait urgence à le changer. Le système de build Nix me paraît assez archaïque et provoque trop de recompilations inutiles. Par exemple, si l’on modifie juste une ligne de commande passée au noyau dans l’ISO d’installation de NixOS — comme la vitesse du port console — on se retrouve avec un phénomène absurde : il faut environ trois minutes de build. C’est amusant, mais pas au point de me faire abandonner Nix. En revanche, c’est quelque chose que je n’accepterais jamais dans mon propre système de build. Utiliser Nix pour fabriquer des images Docker me semble personnellement être le pire cas d’usage. Une fois, je voulais simplement inclure le binaire pg_dump de Postgres avec un binaire Go, et l’équipe infra m’a recommandé Nix. Résultat : mon binaire Go compressé faisait 50 MB, et j’ai fini avec une image monstre de 1,5 GB. pg_dump ne fait pourtant que 464 KB. Au final, j’ai fait quelque chose de bien plus propre avec Bazel, rules_debian et distroless. La plupart des systèmes Nix donnent l’impression que 1,4 GB est la valeur par défaut. Nix n’est pas non plus particulièrement brillant pour compiler de gros projets C++. Au contraire, des systèmes de build faits pour un logiciel donné sont souvent mieux adaptés à leurs besoins. J’aime Bazel, et pour les projets Go j’aimerais simplement utiliser go build. Dans 99 % des cas, j’utilise ce genre d’outils à la place de Nix, sauf éventuellement pour écrire un flake à des fins de mise à jour ou de déploiement via home-manager

  • Cette histoire de choix de version me semble étrange. La version de nixpkgs est évidemment pertinente quand on exploite ou construit un système. Pour une plateforme qui fournit un runtime ou un compilateur, il faut proposer directement les versions, comme le fait devenv. Par exemple, nixpkgs-python fournit « toutes les versions de Python, mises à jour heure par heure avec Nix ». Le fait que Railway injecte une variable d’environnement d’ID de déploiement dans tous les builds aurait aussi pu être géré dans une couche après l’installation. On peut également séparer les paquets en plusieurs couches et automatiser l’ajustement du nombre de couches

  • Avec une expérience en DevOps/SRE, j’ai souvent vu que lorsqu’on essaie de construire un système de gestion des dépendances, on finit généralement dans l’une de deux directions (en prenant Python comme exemple). Option 1 : « monorepo + environnement commun », avec comme avantages la facilité d’administration, l’application des correctifs de sécurité, l’uniformité. Les inconvénients : quelqu’un veut toujours une version spéciale, les déploiements progressifs sont compliqués, et il y a des problèmes pour construire des images légères. Option 2 : « chacun son conda/venv », avec comme avantages l’adaptation individuelle, l’exclusion des paquets inutiles et la possibilité de mises à niveau progressives. Les inconvénients : trop d’environnements, compatibilité mutuelle jamais vraiment vérifiée, gestion de la sécurité cauchemardesque. Au final, plus ma carrière avance, plus je trouve juste la formule : « il n’y a pas de solution, seulement des compromis »

  • Le fameux « Nix lui-même n’a pas de problème ; c’est la manière dont il a été utilisé qui posait problème » me semble être un très bon exemple de l’idée qu’il faut choisir l’outil adapté au contexte. Nix est formidable dans certains cas, et catastrophique dans d’autres. Le problème, c’est qu’il demande tellement de temps d’apprentissage qu’au moment où l’on est enfin assez à l’aise pour juger, on hésite à changer de direction parce qu’on a déjà trop investi. On finit alors par continuer à utiliser Nix de force pour l’objectif initial

    • Je ressens un peu la même chose. Sous certains aspects, Nix me semble être un paradigme de programmation plus intuitif que les autres OS. Je n’y suis simplement pas encore habitué. Une expression Nix, c’est une structure avec des entrées (dépôt de paquets, clés-valeurs, etc.) et une sortie (un système Linux). Peut-être que cela deviendra plus naturel d’ici quelques années. Par exemple, le fait que l’IA puisse générer des shell.nix ou configuration.nix conformes à des spécifications tient aussi à cette structure. Moi aussi, je me crée souvent des environnements par dépôt qui embarquent tout, et j’imagine qu’avec les flakes on pourrait rendre cela encore plus reproductible. (flake.nix est proche de shell.nix, mais permet aussi de figer les versions…)
  • On a l’impression qu’ils essaient d’introduire de force la notion de version là où il n’y en avait pas. Les dépendances cassent à cause d’une « version par défaut » ? C’est un peu comme utiliser le tag :latest de Docker puis voir son serveur casser à chaque changement. J’ai du mal à comprendre le contenu de ce billet de blog. Je ne suis pas non plus d’accord avec « les dépendances Nix ne peuvent pas être réparties en couches distinctes ». On peut découper /nix/store autant qu’on veut, et j’ai l’impression qu’ils ne savent pas très bien comment utiliser ensemble les conteneurs et Nix. Avec un tel manque de compétence, j’ai l’impression que l’alternative proposée finira elle aussi par reproduire les mêmes problèmes. C’est un exemple classique de syndrome NIH

    • Ne pas utiliser Nix là où il n’est pas adapté est évidemment normal, mais reconstruire de A à Z un système qui fonctionne déjà, pour des problèmes que d’autres ont déjà résolus et qu’on pourrait découvrir avec un minimum de recherche, me paraît fondamentalement étrange. nix2container ou les flakes semblent pouvoir résoudre tous les problèmes. Pour la gestion des versions aussi : j’ai des flakes écrits il y a trois ans qui se buildent encore aujourd’hui exactement de la même façon et produisent les mêmes résultats. Cela sent un peu la transition de plateforme pensée pour faciliter l’accès au marché ou la levée de fonds. D’ailleurs, en regardant le GitHub de nixpacks, on voit qu’il n’utilise que rustPlatform, et si le problème concerne Rust, alors rust-overlay est pratiquement la bonne réponse évidente

    • Si l’on réfléchit à ce qui facilite le plus la levée de fonds auprès des VC, le titre de « plateforme de déploiement » est probablement plus avantageux qu’un wrapper autour de Nix

  • Contrairement à l’affirmation « les dépendances Nix ne peuvent pas être séparées en couches distinctes », nix2container permet exactement ce découpage. Par exemple, si une image a besoin de bash, on peut créer séparément une couche contenant bash, et cette couche n’a besoin d’être rebuildée ou repoussée que lorsque bash change. De même, l’idée que « les dépendances produisent une énorme image sous la forme d’une unique couche /nix/store » s’applique à nixpkgs.dockerTools.buildImage, mais pas à nix2container ni à nixpkgs.dockerTools.streamLayeredImage. En pratique, cet outil génère un script qui pousse ensuite l’image. nix2container produit un JSON contenant les chemins de toutes les couches, puis utilise Skopeo pour pousser l’image vers Docker, un registre, podman, etc. (Pour information, je suis l’auteur de nix2container)

    • Je veux vraiment remercier nix2container. Je l’utilise pour des déploiements AWS (ECR), et le temps de transition entre deux builds est tombé à quelques secondes

    • Nous aussi, nous comptions tester nix2container à cause des problèmes de taille des images Docker. Merci d’avoir créé un si bon outil

  • Je pense que le vrai problème ici, c’est cette volonté de préserver la « soupe de versions sur mesure » encouragée par les gestionnaires de paquets de langage, alors que cette approche n’est pas durable. L’alternative, Mise, ne comprend pas les contraintes de version entre paquets et ne teste absolument rien entre eux. On ne peut donc pas espérer le même niveau de fiabilité

    • Le fait que cette « soupe de versions sur mesure » ne soit pas durable est vrai, mais si les gens continuent à l’utiliser, c’est parce que cela fonctionne bien. Les bibliothèques au niveau OS sont gérées de manière très conservatrice et cassent rarement, et on peut généralement superposer dessus des combinaisons de versions personnalisées avec des outils comme mise ou asdf sans trop de problèmes. Et même quand ça casse, il suffit souvent d’ajuster la version ou la configuration pour régler le problème immédiatement. C’est agaçant, mais rarement grave. Les systèmes qui demandent un apprentissage ou un effort supplémentaires sont perçus comme une perte de temps. En revanche, ceux qui accordent plus d’importance à l’idée que « rien ne doit casser » préfèrent souvent Nix malgré sa courbe d’apprentissage et ses contraintes. Pour un service comme Railway, qui vise un grand nombre d’utilisateurs, le choix se fait finalement davantage en fonction du premier groupe : simplicité et inertie

    • Je me demande ce que signifie exactement cette « soupe de versions sur mesure » et quelle serait l’alternative

    • Les deux sont tout à fait possibles. Par exemple, les paquets Rust peuvent facilement être buildés avec Nix à partir des informations de Cargo.lock. Nixpkgs entre en conflit avec les combinaisons de versions personnalisées, mais Nix lui-même s’en sort très bien

  • Nix ne garantit pas des versions arbitraires, mais des commits. On peut souffrir sur certains cas limites, comme un changement dans glibc ou un conflit de bibliothèques partagées. Il est peut-être déjà trop tard maintenant, mais je peux aussi faire du conseil sur des façons plus élégantes d’utiliser Nix. Je trouve le produit lui-même très chouette

    • Nix évite très efficacement les conflits de bibliothèques partagées. En revanche, le moindre petit changement — commentaire, documentation, etc. — entraîne la reconstruction complète de toutes les dépendances en aval concernées. Cela peut conduire à des rebuilds massifs et rendre le développement pénible. On le voit bien en regardant le processus de staging de nixpkgs

    • Je comprends parfaitement la valeur de Nix. En revanche, je pense que dire que « tout va casser » est un peu exagéré. Il est vrai qu’on perd certaines garanties importantes par rapport à Nix, mais cela a tout de même de fortes chances de mieux fonctionner que la plupart des autres logiciels

  • Je ne comprends pas pourquoi ils ont choisi de dépendre d’un hash nixpkgs au lieu de créer leurs propres derivations

  • Beaucoup de commentaires sont intéressants parce qu’ils donnent tous l’impression de dire : « en réalité, Nix peut tout résoudre, à condition d’être un expert comme moi »

    • Si une entreprise faisait toute sa technique et tout son business en JavaScript puis, faute de comprendre des concepts fondamentaux existants comme les fonctions ou les tableaux, finissait par faire du NIH en développant un nouveau langage propriétaire, ce serait davantage un problème de lacunes internes

    • C’est toujours l’ambiance habituelle dès qu’on parle de Nix

    • C’est exactement l’ambiance que dégage Nix. Une narration typique du style « je vais sauver le monde », et à toute réaction du type « la fonctionnalité que je veux n’existe pas », on répond invariablement : « c’est parce que tu ne l’utilises pas correctement »