1 puntos por GN⁺ 2025-06-09 | 1 comentarios | Compartir por WhatsApp
  • Railway lanzó Railpack, un nuevo sistema de compilación que reemplaza a Nixpacks
  • Railpack ofrece mejoras frente a Nixpacks en granularidad del versionado, imágenes más pequeñas y mejor caché
  • El modelo de versionado basado en commits de Nixpacks mostró limitaciones para distintas necesidades de usuarios y de escalabilidad
  • Railpack mejora la estabilidad y flexibilidad del entorno de compilación con integración con BuildKit, protección de variables de entorno secretas y compatibilidad con varios lenguajes y frameworks
  • Actualmente es compatible con Node, Python, Go, PHP y HTML estático, y sigue ampliando el soporte para frameworks y lenguajes

Resumen y contexto

  • Railway presentó Railpack, su sistema de compilación de próxima generación
  • Railpack es una herramienta nueva desarrollada a partir de la experiencia obtenida al compilar más de 14 millones de apps con Nixpacks en la plataforma Railway
  • Nixpacks funcionaba bien para el 80% de los usuarios, pero más de 200 mil se toparon con limitaciones que les generaron fricción
  • Consideraron necesario un gran upgrade para escalar la base de usuarios y mantener un entorno de compilación sostenible

Principales mejoras de Railpack

  • Granularidad del versionado: permite especificar versiones detalladas en formato major.minor.patch para cada paquete, superando las limitaciones del esquema poco claro de versiones en Nix
  • Imágenes más pequeñas: reduce el tamaño base de las imágenes de compilación hasta en 38% para Node y 77% para Python, ofreciendo despliegues más rápidos
  • Mejor caché: se integra directamente con BuildKit para controlar capas y sistema de archivos, mejorar el acierto de caché y compartir caché entre entornos
  • Las compilaciones con Railpack ya se están usando en railway.com y en servicios centrales

Problemas al usar Nixpacks

  • El sistema de versionado de paquetes de Nix tiene una estructura basada en commits, ofrece solo la versión major más reciente y cada versión corresponde a un commit específico del repositorio nixpkgs
  • Existe la ineficiencia de tener que gestionar manualmente incluso versiones pequeñas de parche, y para quienes contribuyen el versionado tampoco resulta intuitivo, lo que reduce la accesibilidad
  • Incluso en lenguajes como Node o Python, al final solo se admite la versión major más reciente
  • Al actualizar una versión, cambiar el hash del commit afecta al mismo tiempo otras versiones de paquetes, lo que reduce la confiabilidad para el usuario y puede provocar fallas inesperadas de compilación
  • En Nixpacks, todas las dependencias están incluidas en una sola capa de /nix/store, por lo que es difícil dividir la imagen de forma efectiva o reducir su tamaño
  • La caché tampoco se aprovecha bien, porque cada vez que se inyectan variables de entorno la capa siempre se invalida

No es un problema de Nix en sí, sino una limitación en la forma de usarlo

  • El problema no está en el diseño de Nix en sí, sino en la forma en que Railway lo usaba y abstraía
  • Intentaron diseñarlo para que el usuario no tuviera que entender el concepto de derivation de Nix ni su estructura interna de versiones, pero concluyeron que en la práctica eso no era posible
  • Para resolver estos problemas avanzaron con el desarrollo de Railpack

Arquitectura técnica de Railpack

  • Cambio de base de código de Rust a Go: se pasó a Go para aprovechar BuildKit y fortalecer la capacidad de adaptación al ecosistema
  • LLB y frontend de BuildKit: generan directamente un LLB y frontend personalizados de BuildKit para controlar con precisión la estructura de las imágenes de compilación → las imágenes base de Node y Python quedaron mucho más ligeras que con Nixpacks
  • Versionado con Mise: usan Mise para la instalación de paquetes y la resolución de versiones, lo que también facilita admitir otras fuentes de ejecutables en el futuro
  • Si una compilación se completa con éxito, se aplica un lock-in de dependencias en ese momento → aunque la versión base de Node cambie de 22 a 24, la compilación existente no se rompe
  • Aprovechan la función de secretos de BuildKit para mejorar la seguridad y administración de variables de entorno

Etapas de compilación de Railpack

  • Analyze: analiza el código para determinar los paquetes necesarios, comandos de ejecución y comando de inicio
  • Plan: genera un plan de compilación en un formato serializable a JSON (incluye varias etapas, y cada etapa depende del resultado de la anterior o de la imagen completa)
  • Generates: genera el grafo de compilación de BuildKit (con base en entradas y salidas)

Compilación estratégica con BuildKit

  • Mientras que Dockerfile funciona en serie, BuildKit procesa varios comandos en paralelo y permite un control fino de entradas y salidas por etapa
  • Railpack define todas las etapas de compilación a partir del análisis del código y especifica con detalle de bajo nivel las dependencias entre etapas
  • Luego convierte ese plan en un grafo LLB de BuildKit y lo resuelve
  • Cuando cambian variables de entorno u otros valores, monta un archivo con el hash de ese valor; si no hay cambios en el código ni en las variables, se garantiza el acierto de caché
  • Como resultado, Railpack puede controlar por completo la forma en que se generan las imágenes

Nuevas funciones posibles con Railpack

  • Compatibilidad sin configuración para compilar y desplegar sitios estáticos con Vite, Astro, CRA y Angular
  • Integración estrecha del proceso de compilación con la UI de Railway
  • Soporte para versiones más recientes de los lenguajes sin necesidad de una nueva release de Railpack
  • Optimización de caché entre entornos por proyecto
  • Actualmente es compatible con Node, Python, Go, PHP y HTML estático, y sigue ampliando el soporte para frameworks y lenguajes

Código abierto y planes futuros

  • Railpack se publica en estado Beta y puede usarse de inmediato con solo activarlo
  • En railpack.com están disponibles la documentación oficial, el código real y canales públicos de soporte
  • A futuro, planean priorizar soporte profundo para lenguajes ampliamente usados y luego ampliar el alcance una vez establecidos la API central y el nivel de abstracción

1 comentarios

 
GN⁺ 2025-06-09
Comentarios en Hacker News
  • Soy fan de Nix, pero espero que crean que no estoy emocionalmente aferrado a la decisión de no usar Nix. Aun así, hay varias quejas de este texto que no termino de entender y siento que necesitan más explicación. Por ejemplo, eso de que “el mayor problema de Nix es el versionado de paquetes basado en commits”. Nixpkgs es un recurso excelente, pero Nix y Nixpkgs no son lo mismo. Si quieres traer una versión arbitraria de un toolchain, Nixpkgs sí es bastante poco adecuado, pero con Nix hay otras formas. Por ejemplo, existen herramientas de Nix realmente bien hechas para obtener versiones arbitrarias de Rust. También escuché eso de que “no se pueden dividir las dependencias de Nix en capas separadas”, y sinceramente me parece que no tiene sentido. Se puede dividir de la forma que quieras. Las herramientas de Docker de Nixpkgs también lo soportan. La parte donde movieron el codebase de Rust a Go no está directamente relacionada con Nix, pero me pareció interesante. Normalmente uno no decide cambiar de lenguaje a la ligera; suele pasar cuando ya planeabas rehacerlo desde cero. Sospecho que Railpacks y Nixpacks no fueron trabajo de las mismas personas. También he visto qué pasa cuando gente que no conoce bien Nix termina lidiando en una organización con una solución de Nix que ni siquiera estaba terminada. No se ve nada bien, y la mayoría de la gente no intenta aprender Nix. Por eso en mi trabajo original casi no usamos Nix, para evitar justo esa situación

    • Me gusta aprovechar Nix, pero cada vez que se discuten problemas básicos de uso de Nix, siempre me responden con “hay una forma de rodearlo” (pero con documentación deficiente, un lenguaje raro, malos mensajes de error y decenas o cientos de líneas de código extra basadas en conocimiento escaso que solo tiene la gente que ya lo usó), y eso ya me tiene harto. La mayoría de los problemas relacionados con Nix no vienen de que sea Turing completo, sino de que carece de cosas básicas incluidas, como APIs intuitivas. Si en todos los proyectos usar Nix termina convirtiéndose poco a poco en una obsesión por resolver los propios problemas de Nix, no hay razón para usarlo cuando ya existen herramientas mainstream bien documentadas. De hecho, por eso la mayoría de la gente termina eligiendo Docker. Me decepciona mucho que Nix insista en una pureza ideal en lugar de resolver problemas reales de experiencia de desarrollador en tiempos razonables. Claro, todo el mundo contribuye de forma voluntaria, pero da mucha pena ver que todo ese esfuerzo técnico termina siendo prácticamente inutilizable por un UX mal diseñado

    • Yo no uso Nix, pero esa afirmación de “Nix ≠ Nixpkgs” me suena desconectada de la realidad. Para la gran mayoría de los usuarios, si la alternativa exige investigación y esfuerzo extra, entonces Nixpkgs termina siendo Nix en la práctica. Y sobre “se puede dividir en capas separadas”, me pregunto si eso realmente es intuitivo, simple y el comportamiento por defecto

    • Lo importante es que los usuarios de Railway son desarrolladores que quieren especificar la versión exacta de los paquetes que quieren. Por la estructura de Nix y Nixpkgs, fijar la versión de un paquete significa fijar el commit de todo el árbol de nixpkgs. Como muchos builds de paquetes de node/python/ruby dependen de cosas fuera del árbol, terminas necesitando un mapeo entre versión y commit. Esa abstracción no es perfecta, así que incluso si un usuario solo quiere hacer yarn add paquete, podría terminar teniendo que alinear el estado del árbol. Usar solo Nix sin Nixpkgs está bien para usos limitados, pero para una plataforma como Railway es una decisión difícil

    • No entiendo bien la controversia sobre el versionado. Apenas estoy empezando con Nix, pero claramente sí tengo paquetes traídos desde commits específicos

    • Creo que lo explicaste muy bien. Nixpkgs y Nix son distintos, pero en la práctica Nixpkgs es la ventaja real. Usando NixOS fue la primera vez que pude usar una versión nueva del kernel de Linux el mismo día de su lanzamiento. Debian Stable está bien, pero siempre se siente como volver varios años al pasado. Dicho eso, el lenguaje de Nix tiene muchas cosas criticables. Es un lenguaje viejo, y aunque es el mejor resultado que pudieron sacar, no creo que valga la pena cambiarlo. El sistema de build de Nix me parece clásico hasta el punto de provocar demasiados rebuilds innecesarios. Por ejemplo, si en el ISO de instalación de NixOS cambias una sola línea del command line que se pasa al kernel (por ejemplo, la velocidad del puerto de consola), ocurre el fenómeno extraño de un build que tarda como 3 minutos. Es chistoso, pero no por eso voy a abandonar Nix. Aun así, es algo que jamás permitiría en mi propio sistema de builds. Personalmente, me parece terrible usar Nix para construir imágenes Docker. Una vez quise meter solo el binario pg_dump de Postgres en un binario hecho en Go, y como el equipo de infraestructura me recomendó Nix, lo usé; el binario comprimido de Go pesaba 50 MB, pero la imagen terminó siendo un monstruo de 1.5 GB. pg_dump apenas pesa 464 KB. Al final lo resolví mucho mejor con una combinación de Bazel, rules_debian y distroless. La mayoría de los sistemas con Nix se sienten como si 1.4 GB fuera el valor por defecto. Nix tampoco es especialmente bueno construyendo grandes proyectos de C++. De hecho, los sistemas diseñados para compilar tu propio software suelen ajustarse mejor a las necesidades concretas. A mí me gusta Bazel, y para proyectos Go simplemente quiero usar go build. En el 99% de los casos uso esas herramientas en lugar de Nix, aunque sí podría escribir un flake para actualización o despliegue y usarlo con home-manager

  • La selección de versiones se siente rara. La versión de nixpkgs claramente tiene sentido cuando operas o construyes un sistema. Pero si eres una plataforma que provee runtimes/compiladores, necesitas ofrecer las versiones directamente, como hace devenv. Por ejemplo, nixpkgs-python ofrece “todas las versiones de Python, actualizadas cada hora con Nix”. El hecho de que Railway inyecte una variable de entorno con el ID de despliegue en todos los builds también podría haberse hecho en una capa posterior a la instalación. Los paquetes también se pueden dividir en varias capas, y hasta se puede automatizar el ajuste de cuántas capas usar

  • Como alguien con experiencia en DevOps/SRE, he visto que cuando alguien intenta construir un sistema de gestión de dependencias, normalmente termina yéndose por una de dos direcciones (por ejemplo, en Python). Opción 1: “monorepo + entorno compartido”; ventajas: administración fácil, parches de seguridad simples, centralización. Desventajas: siempre habrá alguien que quiere una versión especial, es difícil hacer rollouts graduales y hay problemas para construir imágenes slim. Opción 2: “cada quien con su conda/venv”; ventajas: personalización individual, exclusión de paquetes innecesarios, upgrades graduales. Desventajas: demasiados entornos, compatibilidad mutua sin validar, y la seguridad se vuelve una pesadilla. Al final, con los años de experiencia se vuelve cada vez más real esa frase de que “no hay soluciones, solo trade-offs”

  • Creo que eso de “Nix en sí no tenía problema. El problema fue cómo se usó” es un buen ejemplo de “usa la herramienta adecuada para el trabajo adecuado”. Nix es excelente en algunos casos, pero en otros es de lo peor. El problema es que aprenderlo toma mucho tiempo, así que para cuando ya sabes lo suficiente como para tomar una decisión, ya te pesa la inversión de tiempo y te cuesta cambiar de rumbo, y al final terminas forzando Nix para seguir usándolo en el objetivo original

    • Me pasa algo parecido. En cierto sentido, creo que Nix es un paradigma de programación más intuitivo que otros OS. Simplemente todavía no estoy acostumbrado. Las expresiones de Nix tienen una estructura de entradas (repositorio de paquetes, clave-valor, etc.) y salidas (sistema Linux). Quizá en unos años se vuelva más natural. Por ejemplo, que la IA genere shell.nix o configuration.nix de acuerdo con una especificación también se beneficia de esa estructura. Yo también a veces creo entornos por repositorio completamente encapsulados, y con flakes probablemente se puedan hacer entornos aún más reproducibles. (flake.nix es parecido a shell.nix, pero también soporta fijar versiones...)
  • Parece que están intentando introducir versiones a la fuerza donde no las hay. ¿Que una “versión por defecto” rompe dependencias? Eso se parece a usar la etiqueta :latest de Docker y que el servidor se rompa cada vez que cambia. No entiendo bien el contenido de ese blog. Tampoco coincido con eso de que “no se pueden separar las dependencias de Nix en capas distintas”. Puedes dividir /nix/store tanto como quieras, y da la impresión de que tampoco entienden bien cómo usar contenedores con Nix. Si el nivel técnico es tan bajo, me parece que la alternativa que proponen solo va a repetir los mismos problemas. Es un ejemplo clásico del síndrome NIH (hacer tu propia herramienta)

    • Claro, no usar Nix donde no encaja es totalmente válido, pero me parece fundamentalmente raro reconstruir todo un sistema de punta a punta cuando son problemas que otras personas ya resolvieron y que podrías descubrir investigando un poco. nix2container o flakes probablemente habrían resuelto todos esos problemas. Incluso con el versionado: flakes que escribí hace 3 años todavía construyen igual hoy y producen el mismo resultado. También me deja cierta impresión de que esto huele a un cambio de plataforma para salir al mercado o atraer inversión. Por cierto, revisé el GitHub de nixpacks y solo usan rustPlatform; si el problema era Rust, entonces rust-overlay es prácticamente la respuesta correcta

    • Si lo piensas desde el ángulo de qué método ayuda más a conseguir capital de riesgo, el título de “plataforma de despliegue” vende mejor que un wrapper de Nix

  • Contrario a eso de que “no se pueden separar las dependencias de Nix en capas distintas”, nix2container justamente permite esa separación. Por ejemplo, si necesitas una imagen con bash, puedes construir por separado una capa que incluya bash, y esa capa solo se rebuild/pushea cuando cambia bash. Eso de que “por las dependencias se genera una imagen gigante en una sola capa de /nix/store” sí aplica a la función nixpkgs.dockerTools.buildImage, pero no a nix2container ni a nixpkgs.dockerTools.streamLayeredImage. En la práctica, esta herramienta genera un script y a través de él hace el push de la imagen. nix2container convierte las rutas de todas las capas a JSON y usa Skopeo para hacer push de la imagen a Docker, registries, podman, etc. (Por cierto, yo soy el autor de nix2container)

    • De verdad quiero agradecerte por nix2container. Lo usamos para despliegues en AWS (ECR), y el tiempo de cambio entre builds bajó a segundos de un solo dígito

    • Nosotros también pensábamos probar nix2container por el tema del tamaño de las imágenes Docker. Gracias por crear una herramienta tan buena

  • Creo que el problema central aquí es la insistencia en mantener esa “sopa de versiones personalizadas” fomentada por los package managers de lenguaje (y ese enfoque no es sostenible). La alternativa, Mise, no entiende las restricciones de versión entre paquetes ni prueba absolutamente nada entre ellos. No puedes esperar ni remotamente el mismo nivel de confiabilidad

    • Es cierto que esa sopa de versiones personalizadas no es sostenible, pero la razón por la que la gente la sigue usando es que funciona bastante bien. Las librerías a nivel OS se gestionan de forma muy conservadora y no se rompen fácilmente, y sobre eso puedes montar combinaciones de versiones personalizadas con herramientas como mise o asdf y, por lo general, todo sigue funcionando. Si algo se rompe, normalmente se arregla de inmediato tocando la versión o la configuración. Que se rompa es molesto, sí, pero no suele ser importante. Cualquier sistema que implique aprendizaje o esfuerzo adicional se percibe como pérdida de tiempo. En cambio, la gente que valora más “que no se rompa” sí tiende a preferir Nix, incluso si tiene una curva de aprendizaje pesada y resulta incómodo. Para un servicio como Railway, que apunta a muchos usuarios, al final es lógico que prioricen más al primer grupo (facilidad e inercia)

    • Me pregunto qué significa exactamente “sopa de versiones personalizadas” y cuál sería la alternativa

    • Ambas cosas son totalmente posibles. Por ejemplo, los paquetes de Rust se pueden construir fácilmente con Nix usando la información de Cargo.lock. Nixpkgs sí choca con combinaciones personalizadas de versiones, pero Nix por sí mismo lo hace bastante bien

  • Nix no garantiza versiones arbitrarias, sino garantías a nivel de commit. Puede hacerte sufrir en casos límite como cambios de glibc o conflictos de librerías compartidas. Quizá ya sea demasiado tarde ahora, pero incluso podría dar consultoría sobre formas más elegantes de usar Nix. El producto en sí me parece muy bueno

    • Nix evita con muchísima fuerza los conflictos de librerías compartidas. Pero también hace que hasta cambios triviales (comentarios, documentación, etc.) provoquen rebuild completo de todas las dependencias aguas abajo relacionadas. El resultado es que puedes terminar necesitando rebuilds enormes, y eso puede volver doloroso el desarrollo. Se ve claramente en el proceso de staging de nixpkgs

    • Entiendo perfectamente el valor de Nix. Solo creo que decir que “todo se rompe” es un poco exagerado. Sí, pierdes algunas garantías grandes frente a Nix, pero aun así probablemente funcione mucho mejor que la mayoría del software

  • No entiendo por qué dependieron del hash de nixpkgs en lugar de crear sus propias derivations

  • Me pareció interesante que muchos comentarios tenían ese tono de “en realidad Nix sí resuelve todo, pero solo si eres un experto como yo”

    • Si una empresa hiciera toda su tecnología y su negocio en JavaScript, y luego por no entender conceptos centrales ya existentes (funciones, arreglos, etc.) terminara cayendo en NIH (crear un lenguaje nuevo con especificación propia), eso se parecería más a una carencia interna

    • Ese es el ambiente de siempre cada vez que sale el tema de Nix

    • Justamente esa es la vibra de Nix. La narrativa típica de “yo voy a salvar el mundo” y, cuando alguien responde “la función que necesito no existe”, siempre le contestan “es que no lo estás usando bien”