1 pontos por GN⁺ 2025-06-09 | 1 comentários | Compartilhar no WhatsApp
  • A Railway lançou o Railpack, um novo sistema de build para substituir o Nixpacks
  • O Railpack oferece recursos superiores ao Nixpacks em aspectos como controle de versão mais granular, imagens menores e cache aprimorado
  • O modelo de versionamento baseado em commits do Nixpacks mostrou limitações diante de diferentes necessidades dos usuários e de escalabilidade
  • O Railpack melhora a estabilidade e a flexibilidade do ambiente de build com integração com o BuildKit, proteção de variáveis de ambiente secretas e suporte a diversas linguagens e frameworks
  • Atualmente, há suporte para Node, Python, Go, PHP e HTML estático, e o suporte a frameworks e linguagens continua em expansão

Visão geral e contexto

  • A Railway apresentou o Railpack, seu sistema de build de próxima geração
  • O Railpack é uma nova ferramenta desenvolvida com base na experiência adquirida ao construir mais de 14 milhões de apps com o Nixpacks na plataforma Railway
  • O Nixpacks atendia bem 80% dos usuários, mas mais de 200 mil pessoas enfrentaram limitações que causaram dificuldades
  • A empresa concluiu que era necessário um grande upgrade para expandir a base de usuários e manter um ambiente de build sustentável

Principais melhorias do Railpack

  • Versionamento granular: suporte à especificação detalhada de versões no formato major.minor.patch para cada pacote, superando as limitações do modelo pouco claro de versionamento do Nix
  • Imagens menores: redução do tamanho das imagens base de build em até 38% para Node e 77% para Python, proporcionando implantações mais rápidas
  • Cache aprimorado: integração direta com o BuildKit para controlar camadas e o sistema de arquivos, aumentar a taxa de acerto de cache e compartilhar cache entre ambientes
  • O Railpack já está sendo usado para builds no railway.com e em serviços centrais

Problemas no uso do Nixpacks

  • O modelo de versionamento de pacotes do Nix segue uma estrutura baseada em commits, oferecendo apenas a versão major mais recente, e cada versão corresponde a um commit específico do repositório nixpkgs
  • Há ineficiência por exigir gerenciamento manual até mesmo de pequenas versões de patch, e o versionamento também não é intuitivo para contribuidores, reduzindo a acessibilidade
  • Mesmo para linguagens como Node e Python, no fim das contas só a versão major mais recente é suportada
  • Ao atualizar versões, a mudança do hash do commit pode afetar ao mesmo tempo versões de outros pacotes, reduzindo a confiabilidade para o usuário e podendo causar falhas inesperadas de build
  • No Nixpacks, todas as dependências ficam em uma única camada /nix/store, o que dificulta dividir melhor a imagem ou reduzir seu tamanho de forma eficaz
  • O cache também era prejudicado porque, sempre que variáveis de ambiente eram injetadas, a camada era invalidada, impedindo um bom reaproveitamento

O problema não é o Nix em si, mas os limites da forma de uso

  • O problema não está no design do Nix em si, mas na forma como a Railway o utilizava e abstraía
  • A empresa tentou projetar o sistema para que os usuários não precisassem entender o conceito de derivation do Nix nem sua estrutura interna de versionamento, mas concluiu que isso não era viável na prática
  • Para resolver esses problemas, iniciou o desenvolvimento do Railpack

Arquitetura técnica do Railpack

  • Mudança de Rust para Go no codebase: adoção de Go para aproveitar melhor o BuildKit e fortalecer a adaptação ao ecossistema
  • BuildKit LLB e frontend: geração direta de um LLB customizado e de um frontend do BuildKit para controlar com precisão a estrutura das imagens de build → as imagens base de Node e Python ficaram muito mais leves em comparação com o Nixpacks
  • Versionamento com Mise: uso do Mise para instalação de pacotes e resolução de versões, com possibilidade futura de suportar facilmente outras fontes de executáveis
  • Quando um build é concluído com sucesso, aplica-se um lock-in das dependências naquele momento → mesmo que a versão padrão do Node mude de 22 para 24, builds existentes não quebram
  • Uso do recurso de secrets do BuildKit para melhorar a segurança e o gerenciamento de variáveis de ambiente

Etapas de build do Railpack

  • Analyze: análise do código para identificar os pacotes necessários, comandos de execução e comando de inicialização
  • Plan: criação de um plano de build serializável em JSON (com várias etapas, cada uma dependendo do resultado da etapa anterior ou da imagem inteira)
  • Generates: geração do grafo de build do BuildKit com base em entradas e saídas

Estratégia de build com BuildKit

  • Enquanto o Dockerfile funciona de forma serial, o BuildKit processa vários comandos em paralelo e permite controle detalhado de entradas e saídas em cada etapa
  • O Railpack define todas as etapas do build com base na análise do código e especifica em baixo nível as dependências entre essas etapas
  • Esse plano é então convertido e resolvido como um grafo LLB do BuildKit
  • Quando há mudanças em variáveis de ambiente e afins, esses valores são montados como arquivos com hash; se o código e as variáveis não mudarem, o acerto de cache é garantido
  • Como resultado, o Railpack consegue controlar completamente a forma como a imagem é gerada

Novos recursos viabilizados pelo Railpack

  • Suporte sem configuração para build e deploy de sites estáticos com Vite, Astro, CRA e Angular
  • Integração estreita do processo de build com a interface da Railway
  • Suporte às versões mais recentes das linguagens sem exigir um novo release do próprio Railpack
  • Otimização de cache entre ambientes por projeto
  • Atualmente, há suporte para Node, Python, Go, PHP e HTML estático, e o suporte a frameworks e linguagens continua sendo expandido

Open source e planos futuros

  • O Railpack está disponível em Beta e pode ser usado imediatamente apenas ativando o recurso
  • Documentação oficial, código real e canais públicos de suporte estão disponíveis em railpack.com
  • No futuro, a prioridade será oferecer suporte aprofundado às linguagens mais usadas e, depois de consolidar a API central e o nível de abstração, expandir o escopo

1 comentários

 
GN⁺ 2025-06-09
Comentários no Hacker News
  • Sou fã do Nix, mas espero que acreditem que não tenho apego emocional à decisão de não usá-lo. Ainda assim, sinto que algumas reclamações deste texto não são muito fáceis de entender e precisariam de mais explicação. Por exemplo, há a afirmação de que “o maior problema do Nix é o gerenciamento de versões de pacotes baseado em commit”. O Nixpkgs é um recurso excelente, mas Nix e Nixpkgs não são a mesma coisa. O Nixpkgs realmente não é muito adequado para trazer versões arbitrárias de uma toolchain, mas existem outras formas de fazer isso com Nix. Por exemplo, as ferramentas de Nix para obter versões arbitrárias de Rust são realmente muito boas. Também ouvi a afirmação de que “não dá para dividir dependências do Nix em camadas separadas”, e isso, para mim, não faz sentido algum. Dá para dividir de qualquer maneira que você quiser. As ferramentas de Docker do Nixpkgs também suportam isso. A parte em que a base de código foi migrada de Rust para Go não está diretamente relacionada ao Nix, mas achei interessante. Normalmente, não se decide mudar de linguagem de forma leviana; isso costuma acontecer quando já existe a intenção de reconstruir do zero. Suspeito que Railpacks e Nixpacks tenham sido trabalho de pessoas diferentes. Também já vi o que acontece quando pessoas que não conhecem bem Nix acabam tendo que lidar, dentro de uma organização, com uma solução em Nix ainda inacabada. Não fica bonito, e a maioria das pessoas não quer aprender Nix. Por isso, no meu trabalho original, quase não usamos Nix para evitar esse tipo de situação

    • Gosto de usar Nix, mas fico frustrado porque, sempre que se discute problemas básicos de usabilidade do Nix, a resposta é sempre “há uma forma de contornar” (mas com documentação insuficiente, uma linguagem esquisita, mensagens de erro ruins e dezenas ou centenas de linhas extras de código baseadas em informação escassa que só quem já usou conhece). A maioria dos problemas do Nix não vem de ele ser Turing-completo, mas da falta de recursos básicos embutidos, como APIs intuitivas. Se, em todo projeto, usar Nix vai se transformando cada vez mais em uma imersão para resolver problemas do próprio Nix, então não há motivo para usá-lo quando já existem ferramentas principais bem documentadas. Na prática, é por isso que a maioria das pessoas escolhe Docker. É muito decepcionante ver o Nix insistir em pureza idealizada sem resolver, em tempo razoável, os problemas reais da experiência de desenvolvedor. Claro, todo mundo contribui voluntariamente, mas é uma pena ver esse esforço técnico acabar num estado praticamente inutilizável por causa de uma UX mal projetada

    • Eu não uso Nix, mas essa afirmação de que “Nix ≠ Nixpkgs” me parece desconectada da realidade. Para a maioria dos usuários, se a alternativa exige pesquisa e esforço adicionais, no fim Nixpkgs vira o próprio Nix. E quanto a “dá para dividir em camadas separadas”, eu gostaria de saber se isso é realmente intuitivo, simples e o comportamento padrão

    • O importante é que os usuários da Railway são desenvolvedores que querem especificar as versões exatas dos pacotes que desejam. Pela estrutura de Nix e Nixpkgs, fixar a versão de um pacote significa fixar o commit da árvore inteira do nixpkgs. Como os builds de pacotes de node/python/ruby dependem muito de coisas fora da árvore, é necessário mapear versão e commit. Essa abstração não é perfeita, então o usuário pode acabar precisando alinhar o estado da árvore mesmo para simplesmente fazer yarn add pacote. Usar apenas Nix sem Nixpkgs pode funcionar em casos limitados, mas é uma escolha difícil para uma plataforma como a Railway

    • Não entendo bem a polêmica sobre versionamento. Estou usando Nix pela primeira vez, mas claramente tenho pacotes obtidos de um commit específico

    • Acho que o texto apontou isso muito bem. Nixpkgs e Nix são diferentes, mas, na prática, o Nixpkgs é a verdadeira vantagem. Usando NixOS, foi a primeira vez que consegui usar uma versão novíssima do kernel Linux no próprio dia do lançamento. Debian Stable também é bom, mas sempre dá a sensação de estar alguns anos no passado. Ainda assim, a linguagem Nix merece muitas críticas. É uma linguagem antiga e, embora seja o melhor que se conseguiu, não acho que valha a pena trocá-la. O sistema de build do Nix também parece clássico demais e causa rebuilds desnecessários. Por exemplo, se você mudar só uma linha de comando passada ao kernel na ISO de instalação do NixOS (como a velocidade da porta de console), acontece aquele fenômeno bizarro de um build levar uns 3 minutos. É engraçado, mas eu não desisto do Nix por isso. Só que isso é algo que eu jamais aceitaria no meu próprio sistema de build. Pessoalmente, acho péssimo usar Nix para criar imagens Docker. Uma vez eu só queria colocar o binário pg_dump do Postgres junto com um binário feito em Go, e o time de infraestrutura recomendou Nix. Meu binário Go compactado tinha 50 MB, mas acabou virando uma monstruosidade de 1,5 GB. O pg_dump tem só 464 KB. No fim, fiz com Bazel + rules_debian + distroless e ficou muito mais limpo. A maioria dos sistemas com Nix parece ter 1,4 GB como padrão. Nix também não é especialmente bom para builds grandes de projetos C++. Sistemas voltados ao build do próprio software geralmente tendem a se adaptar melhor às necessidades de cada um. Eu gosto de Bazel e, em projetos Go, quero simplesmente usar go build. Em 99% dos casos uso ferramentas assim em vez de Nix, embora eu ainda possa escrever um flake para atualização ou distribuição e usá-lo com home-manager

  • A escolha de versão parece estranha. A versão do nixpkgs faz bastante sentido quando você está operando ou buildando um sistema. Se for uma plataforma que fornece runtime/compilador, então é preciso fornecer diretamente as versões, como faz o devenv. Por exemplo, o nixpkgs-python oferece “todas as versões de Python, atualizadas no Nix a cada hora”. O fato de a Railway injetar uma variável de ambiente com o ID da implantação em todos os builds também poderia ter sido feito numa camada posterior à instalação. E os pacotes também podem ser separados em várias camadas, com automação para ajustar a quantidade de camadas

  • Como alguém com experiência em DevOps/SRE, vejo que, quando alguém tenta criar um sistema de gerenciamento de dependências, quase sempre acaba indo por um de dois caminhos (por exemplo, no caso de Python). Opção 1: “monorepo + ambiente compartilhado”. Vantagens: facilidade de gestão, aplicação de patches de segurança, padronização. Desvantagens: sempre vai haver alguém querendo uma versão especial, é difícil fazer rollout gradual e há problemas para construir imagens enxutas. Opção 2: “cada um com seu conda/venv”. Vantagens: personalização individual, exclusão de pacotes desnecessários, upgrades graduais. Desvantagens: ambientes demais, compatibilidade entre eles não verificada, segurança vira um pesadelo. No fim, quanto mais experiência se ganha, mais real parece a frase “não existem soluções, só trade-offs”

  • Acho que a frase “não havia nada de errado com o Nix em si; o problema foi a forma de usá-lo” é um bom exemplo de “use a ferramenta certa para o trabalho certo”. O Nix é excelente em alguns contextos, mas péssimo em outros. O problema é que leva muito tempo para aprender e, quando você já está familiarizado o bastante para tomar uma decisão, acaba sentindo que investiu tempo demais para mudar de direção. No fim, continua forçando o uso de Nix para o objetivo original

    • Sinto algo parecido. Em certos aspectos, acho que o Nix é um paradigma de programação mais intuitivo do que outros sistemas operacionais. Só ainda não estou acostumado. As expressões Nix têm uma estrutura de entrada (repositório de pacotes, chave-valor etc.) e saída (sistema Linux). Talvez, em alguns anos, isso fique mais natural. Por exemplo, a IA conseguir gerar shell.nix ou configuration.nix conforme uma especificação também se deve a essa estrutura. Eu mesmo costumo montar ambientes por repositório de forma totalmente encapsulada, e com flakes talvez desse para fazer ambientes ainda mais reproduzíveis. (flake.nix é parecido com shell.nix, mas também suporta fixação de versões...)
  • Parece que estão tentando enfiar versionamento onde ele não existe. Dependências quebram por causa de “versões padrão”? Isso é parecido com usar a tag :latest no Docker e derrubar o servidor toda vez que algo muda. Não entendi muito bem o conteúdo desse blog. Também não concordo com “não dá para dividir dependências do Nix em camadas separadas”. Dá para dividir /nix/store tanto quanto você quiser, e parece que a pessoa também não entende muito bem como usar contêineres e Nix juntos. Se a capacidade técnica for esse o problema, então a alternativa proposta provavelmente acabará repetindo os mesmos problemas. Parece um exemplo clássico de síndrome NIH (fazer sua própria ferramenta)

    • É claro que não usar Nix onde ele não se encaixa é normal, mas ainda assim me parece profundamente estranho reconstruir tudo do início ao fim quando o sistema já funcionava e os problemas já foram resolvidos por outras pessoas, bastando pesquisar um pouco. nix2container e flakes provavelmente resolveriam tudo. Também na parte de versionamento: flakes que escrevi há 3 anos ainda buildam exatamente igual e geram o mesmo resultado. Isso me cheira mais a uma mudança de plataforma para tentar entrar no mercado ou captar investimento. Aliás, dei uma olhada no GitHub do nixpacks e vi que ele usa só rustPlatform; se o problema era Rust, então o rust-overlay é, na prática, a resposta certa

    • Se você pensar em qual abordagem facilita mais atrair VC, o título de “plataforma de deploy” é mais vantajoso do que um wrapper de Nix

  • Ao contrário da afirmação de que “não dá para dividir dependências do Nix em camadas separadas”, o nix2container permite exatamente essa divisão. Por exemplo, se você precisa de uma imagem com bash, pode criar separadamente só a camada que contém bash, e essa camada só precisa ser rebuildada ou enviada de novo quando o bash mudar. A afirmação de que “as dependências geram uma imagem gigante numa única camada /nix/store” vale para nixpkgs.dockerTools.buildImage, mas não para nix2container ou nixpkgs.dockerTools.streamLayeredImage. Na prática, essa ferramenta gera scripts e usa esses scripts para enviar a imagem. O nix2container cria um JSON com os caminhos de todas as camadas e usa o Skopeo para enviar a imagem para Docker, registries, podman etc. (aliás, eu sou o autor do nix2container)

    • Só queria agradecer de verdade pelo nix2container. Uso para deploy na AWS (ECR), e o tempo de troca entre builds caiu para segundos de um dígito

    • Nós também estávamos planejando testar o nix2container por causa do problema de tamanho das imagens Docker. Obrigado por fazer uma ferramenta tão boa

  • Acho que o problema central aqui é a insistência em manter essa “sopa de versões customizadas” incentivada por gerenciadores de pacotes de linguagem (o que é insustentável). A alternativa, Mise, não entende restrições de versão entre pacotes e também não testa nada dos pacotes. Não dá para esperar o mesmo nível de confiabilidade

    • É verdade que essa sopa de versões customizadas é insustentável, mas as pessoas continuam usando porque funciona bem. Bibliotecas em nível de sistema operacional são geridas de forma muito conservadora e não quebram com facilidade, e ferramentas como mise ou asdf geralmente conseguem empilhar combinações customizadas de versão em cima disso sem grandes problemas. E, se quebrar, normalmente basta ajustar a versão ou a configuração. É irritante quando quebra, mas não é algo importante. Sistemas que exigem aprendizado ou esforço adicional acabam sendo vistos como perda de tempo. Já quem valoriza mais o estado de “não quebrar” tende a preferir Nix mesmo com curva de aprendizado e incômodo. Numa empresa como a Railway, que mira muitos usuários, faz sentido que a escolha favoreça mais o primeiro grupo (simplicidade e inércia)

    • Fiquei curioso sobre o que significa “sopa de versões customizadas” e qual seria a alternativa

    • As duas coisas são perfeitamente possíveis. Por exemplo, pacotes Rust podem ser buildados com Nix facilmente a partir das informações do Cargo.lock. O Nixpkgs entra em conflito com combinações customizadas de versões, mas o Nix em si faz isso muito bem

  • O Nix não garante versões arbitrárias; ele garante commits. Você ainda pode sofrer em casos de borda como mudança de glibc ou conflito de bibliotecas compartilhadas. Talvez já seja tarde agora, mas eu até poderia prestar consultoria sobre como usar Nix de forma mais elegante. Acho o produto em si muito legal

    • O nix previne conflitos de bibliotecas compartilhadas de forma muito forte. Mas até mudanças triviais (comentários, documentação etc.) acabam forçando rebuild de todas as dependências relacionadas. O resultado é que rebuilds muito grandes passam a ser necessários, e o desenvolvimento pode virar um sofrimento. Dá para ver isso no processo de staging do nixpkgs

    • Entendo muito bem o valor do Nix. Só acho que dizer que “vai quebrar” é um pouco exagerado. É verdade que você perde algumas garantias importantes em comparação com Nix, mas ainda assim acredito que a chance de continuar funcionando bem é maior do que na maior parte do software

  • Não entendi por que depender do hash do nixpkgs em vez de simplesmente criar suas próprias derivations

  • Achei interessante como muitos comentários têm essa vibe de “na verdade, tudo dá para resolver com Nix, desde que você seja um especialista como eu”

    • Se uma empresa fizesse toda a tecnologia e o negócio em JavaScript e depois, por não entender conceitos centrais já existentes (funções, arrays etc.), criasse uma nova linguagem no estilo NIH (uma especificação própria), isso pareceria mais um problema interno de limitação deles

    • Esse é sempre o clima de sempre quando o assunto é Nix

    • Essa é exatamente a vibe do Nix. A narrativa clássica do tipo “eu vou salvar o mundo” e, toda vez que alguém diz “o recurso que eu quero não funciona”, a resposta é sempre “isso é porque você não está usando direito”