2P by GN⁺ 1일전 | ★ favorite | 댓글 1개
  • Railway는 기존 Nixpacks 대신 새로운 빌드 시스템인 Railpack을 출시함
  • Railpack은 버전 관리의 세분화, 더 작은 이미지 크기, 향상된 캐싱 등에서 기존 Nixpacks보다 우수한 기능을 제공함
  • Nixpacks의 커밋 기반 버전 관리 방식이 다양한 사용자 요구와 확장성에 한계를 드러냈음
  • Railpack은 BuildKit 통합, 비밀 환경 변수 보호, 다양한 언어 및 프레임워크 지원 등으로 빌드 환경의 안정성과 유연성을 개선함
  • 현재 Node, Python, Go, PHP, 정적 HTML을 지원하며, 계속해서 프레임워크 및 언어 지원을 확장 중임

개요 및 배경

  • Railway는 Railpack이라는 차세대 빌드 시스템을 공개함
  • Railpack은 Railway 플랫폼에서 1천4백만 개 이상의 앱을 Nixpacks로 빌드하면서 얻은 경험을 바탕으로 새롭게 개발한 도구임
  • 기존 Nixpacks는 전체 사용자의 80%에게 적합했지만, 20만 명 이상의 사용자가 제약 사항에 부딪혀 불편을 겪음
  • 사용자 기반 확장 및 지속가능한 빌드 환경을 위해 빅 업그레이드가 필요하다고 판단함

Railpack 주요 개선점

  • 버전 관리 세분화: 각 패키지에 대하여 major.minor.patch 단위의 세밀한 버전 지정을 지원하여, Nix의 불분명한 버전 방식의 한계를 극복함
  • 작은 이미지 크기: Node는 38%, Python은 77%까지 기본 빌드 이미지 크기를 줄여, 더 빠른 배포 경험 제공
  • 캐싱 강화: BuildKit과 직접적으로 통합하여, 레이어와 파일 시스템을 제어, 캐시 적중률 향상 및 환경별 캐시 공유 가능
  • 이미 railway.com과 중앙 서비스에 Railpack 빌드가 적용된 상태임

Nixpacks 사용상 문제점

  • Nix의 패키지 버전 관리 방식은 커밋 기반 구조로, 최신 major 버전만 제공하며, 각 버전은 nixpkgs 저장소의 특정 커밋에 대응됨
  • 작은 패치 버전까지 모두 수동 관리해야 하는 비효율성 존재, 기여자도 버전 관리가 직관적이지 않아 접근성 저하
  • Node나 Python과 같은 언어의 경우도 결국 최신 major 버전만 지원
  • 버전 업데이트 시 커밋 해시 변경으로 다른 패키지 버전까지 한꺼번에 영향을 받아, 사용자의 신뢰성 저하 및 예기치 않은 빌드 실패 발생 가능
  • Nixpacks의 경우 /nix/store 하나의 레이어에 모든 의존성이 포함되어, 이미지를 효과적으로 쪼개거나 크기를 줄이기가 어려움
  • 캐싱도 환경 변수 주입 시마다 레이어가 항상 invalidate되어, 캐시를 제대로 활용하지 못함

Nix 자체의 문제가 아닌 사용 방식의 한계

  • Nix 자체의 설계 문제가 아닌, Railway의 사용 및 추상화 방식이 문제점으로 작용함
  • 사용자가 Nix의 derivation 개념이나 내부 버전 구조를 이해하지 않아도 되도록 설계하려 했지만, 현실적으로 불가능하다고 판단함
  • 위와 같은 문제를 해결하기 위해 Railpack 개발을 진행함

Railpack의 기술적 아키텍처

  • Rust → Go로 코드베이스 변화: BuildKit 활용 및 생태계 대응력 강화를 위해 Go 언어로 전환함
  • BuildKit LLB 및 프론트엔드: 커스텀 BuildKit LLB와 프론트엔드를 직접 생성하여, 빌드 이미지의 구조를 정밀하게 통제함 → Node와 Python 기본 이미지가 Nixpacks 대비 대폭 경량화됨
  • Mise로 버전 관리: 패키지 설치 및 버전 해석에 Mise를 사용, 향후 다른 실행 파일 소스도 용이하게 지원 가능
  • 성공적으로 빌드한 경우, 해당 시점의 의존성 lock-in 적용 → Node 기본 버전이 22에서 24로 바뀌어도 기존 빌드는 깨지지 않음
  • BuildKit의 secret 기능을 활용하여, 환경 변수 보안/관리를 개선

Railpack 빌드 단계

  • Analyze: 코드 분석을 통해 필요한 패키지, 실행 커맨드, 시작 명령어 도출
  • Plan: JSON 직렬화 가능한 형태의 빌드 계획 생성 (여러 단계 포함, 각 단계는 이전 단계 결과나 전체 이미지에 의존함)
  • Generates: BuildKit의 빌드 그래프 생성 (입력/출력을 기준으로)

BuildKit을 활용한 전략적 빌드

  • Dockerfile이 직렬로 동작하는 반면, BuildKit은 여러 명령을 병렬적으로 처리, 각 단계별로 세밀한 입력/출력 통제
  • Railpack은 코드 분석 결과 모든 빌드 단계를 정의하고, 각 단계의 의존관계를 낮은 수준으로 상세 지정함
  • 이 계획을 BuildKit LLB 그래프로 변환 및 해결
  • 환경 변수 등 변경 시에는 해당 값의 해시값으로 파일을 마운트, 코드와 변수에 변화가 없으면 캐시 적중 보장
  • 결과적으로 이미지 생성 방식을 완벽하게 Railpack이 컨트롤 가능

Railpack 도입으로 가능한 새로운 기능

  • Vite, Astro, CRA, Angular 정적 사이트 빌드/배포를 무설정으로 지원
  • Railway UI와 빌드 과정의 긴밀한 통합
  • 언어 최신 버전 지원이 Railpack 자체 릴리스 없이도 가능함
  • 프로젝트별로 환경 간 캐싱 최적화 제공
  • 현재 Node, Python, Go, PHP, 정적 HTML을 지원하며, 프레임워크 및 언어 지원을 계속 확장 중임

오픈소스 및 미래 계획

  • Railpack은 Beta 상태로 공개 중이며, 활성화만 하면 즉시 활용 가능
  • 공식 문서 및 실제 코드, 공개 지원 창구까지 railpack.com에서 제공
  • 향후 널리 쓰이는 언어에 대한 심층 지원을 우선하며, 코어 API 및 추상화 수준 확립 이후 범위 확장 계획 있음
Hacker News 의견
  • 나는 Nix 애호가지만, Nix를 쓰지 않기로 한 것에 대해 감정적으로 집착하지 않는 입장임을 믿어줬으면 함. 그런데 이 글의 몇몇 불만 사항이 잘 이해되지 않고, 좀 더 설명이 필요하다고 느낌. 예를 들어 “Nix의 가장 큰 문제는 커밋 기반 패키지 버전 관리”라는 이야기가 있음. Nixpkgs는 훌륭한 리소스이지만, Nix와 Nixpkgs는 동일하지 않음. 도구체인의 임의 버전을 가져오려면 Nixpkgs가 매우 부적합하지만, Nix로 다른 방법도 있음. 예를 들면 Rust의 임의 버전을 잘 뽑아오는 Nix 도구들이 정말 잘 되어 있음. 또, “Nix 의존성을 별도의 레이어로 분할할 수 없다”라는 이야기도 들었는데, 전혀 말이 안 된다고 생각함. 원하는 어떤 방식으로든 분할이 가능. Nixpkgs의 Docker 툴도 이를 지원함. 코드베이스를 Rust에서 Go로 옮긴 부분은 Nix와 직접 관련이 없지만 흥미롭게 느낌. 보통 언어 변경을 가볍게 결정하지 않고, 애초에 새로 만들 계획이 있을 때 하는 경우가 많음. Railpacks와 Nixpacks는 다른 사람들이 작업한 것 아닌지 의심됨. Nix를 잘 모르는 사람들이 마감되지 않은 Nix 솔루션을 조직에서 다루게 되었을 때 일어나는 일도 본 적 있음. 별로 좋지 않은 모습이고, 대부분의 사람들이 Nix를 배우려고 하지 않음. 그래서 원래 직장에서는 이런 상황을 피하려고 Nix를 거의 쓰지 않음

    • Nix를 활용하는 걸 좋아하지만, Nix의 기초적인 사용 문제에 대해 토의할 때마다 “회피 방법이 있다”(하지만 부족한 문서, 특이한 언어, 나쁜 에러 메시지, 써본 사람들만 아는 부족한 정보로 수십~수백 줄의 코드를 추가해야 함)는 답변만 돌려받게 되어 지침이 큼. 대부분의 Nix 관련 문제는 튜링 완전성이 아니라, 직관적 API 같은 기본 제공 기능이 없는 문제에서 옴. 모든 프로젝트에서 Nix 활용이 점점 Nix 자체 문제 해결에 몰입되는 방식으로 바뀐다면, 잘 문서화된 주류 도구가 있는데 굳이 Nix를 써야 할 이유가 없음. 실제로도 사람들이 Docker를 선택하는 경우가 대부분임. Nix가 개발자 경험의 실제적 문제를 현실적 시간 내에 해결하지 않고 이상적 순수성만 고집하는 것이 많이 실망스러움. 물론 모두가 자발적으로 기여하지만, 이런 기술적 노력이 잘못 설계된 UX 때문에 실질적으로 사용할 수 없는 상태로 남는 걸 보는 게 너무 아쉬움

    • 나는 Nix를 사용하지 않지만, “Nix ≠ Nixpkgs”라는 주장이 현실을 벗어난 듯 느껴짐. 대다수 사용자는 대안이 추가 연구와 노력을 요구한다면 Nixpkgs가 결국 Nix 자체인 상황. “별도 레이어로 분할할 수 있다”도, 이게 정말 직관적이고, 간단하고, 기본 동작인지 궁금함

    • 중요한 것은 Railway의 사용자들은 각자 원하는 패키지의 버전을 지정하고 싶어하는 개발자들이라는 점임. Nix와 Nixpkgs 구조상 어떤 패키지의 버전을 고정하면 nixpkgs 전체 트리의 커밋을 고정하는 것을 의미. node/python/ruby 패키지 빌드가 트리 외부에 의존하는 게 많아서, 버전과 커밋 매핑이 필요해짐. 이 추상화가 완벽하지 않아서, 사용자가 단순히 “yarn add 패키지” 하려 해도 트리 상태를 맞춰야 할 수 있음. Nixpkgs 없이 Nix만 쓰는 건 한정적 사용에는 괜찮지만 Railway 같은 플랫폼엔 힘든 선택임

    • 버전 관리 논란이 잘 이해되지 않음. 나는 Nix를 처음 써보지만, 특정 커밋에서 가져온 패키지를 분명히 가지고 있음

    • 잘 집어서 설명했다고 생각함. Nixpkgs와 Nix는 다르지만, 사실상 Nixpkgs가 진짜 장점임. NixOS를 쓰면서 처음으로 리눅스 커널 최신 버전을 릴리즈 당일에 사용해봄. Debian Stable도 괜찮지만 항상 과거로 몇 년 돌아가는 느낌. 다만, Nix 언어는 비판거리가 많음. 오래된 언어이고, 최선을 다한 결과긴 하지만 굳이 바꿀 필요는 없다고 생각함. Nix 빌드 시스템은 고전적이라 불필요하게 재빌드가 많다고 느낌. 예를 들면 NixOS 설치 ISO에 커널에 넘기는 커맨드라인(예: 콘솔 포트 속도) 하나만 바꿔도 3분 정도 걸리는 괴상한 빌드 현상이 벌어짐. 웃기긴 한데 Nix를 포기하진 않음. 다만 내 빌드시스템에선 절대 허용하지 않을 현상임. Docker 이미지를 만들 때 Nix를 쓰는 건 개인적으로 최악이라 생각함. 예전에 Go로 만든 바이너리에 Postgres의 pg_dump 바이너리만 넣으려 했는데, 인프라팀이 Nix를 추천해서 썼더니, 압축된 Go 바이너리가 50MB였는데 1.5GB짜리 괴물 이미지가 됐음. pg_dump는 464KB에 불과함. 결국 Bazel과 rules_debian, distroless 조합으로 훨씬 깔끔하게 작업함. 대부분의 Nix 시스템은 1.4GB가 기본값처럼 느껴짐. 큰 C++ 프로젝트 빌드도 Nix가 특출나게 잘하는 건 아님. 오히려 자신만의 소프트웨어 빌드를 위한 시스템은 더 각자의 필요에 잘 맞는 경향. 나는 Bazel을 좋아하고, Go 프로젝트에는 단순히 go build만 쓰고 싶음. 99%의 경우 Nix 대신 이런 툴을 쓰고, 단 최신화나 배포를 위해 flake를 작성해 home-manager에서 쓸 수도 있음

  • 버전 선택이 이상하게 느껴짐. nixpkgs의 버전은 시스템을 운영하거나 빌드할 때 분명히 타당함. 런타임/컴파일러를 제공하는 플랫폼이라면 devenv처럼 직접 버전을 제공하는 게 필요. 예를 들면 nixpkgs-python은 “모든 Python 버전, Nix로 시간마다 최신화”를 제공함. Railway가 배포 ID 환경 변수를 모든 빌드에 주입한다는 것도 설치 이후 레이어에서 했어도 됨. 패키지도 여러 레이어로 분리 가능하며, 레이어 개수 조절 자동화도 가능함

  • DevOps/SRE 경험자로서, 누군가가 의존성 관리 시스템을 만들려고 할 때 대개 두 가지 방향 중 하나로 흘러가는 걸 목격함(예: Python 기준). 옵션 1: “모노리포 + 공용 환경”, 장점은 관리 용이, 보안패치 편의, 일원화. 단점은 누군가는 항상 특별한 버전을 원하며, 단계적 롤아웃 어려움, 슬림 이미지 빌드 문제. 옵션 2: “각자 conda/venv”, 장점은 개별 맞춤, 불필요한 패키지 제외, 단계적 업그레이드 가능. 단점은 너무 많은 환경, 상호 호환 미검증, 보안 관리가 악몽. 결론적으로 “해결책이란 없고 트레이드오프만 있다”는 말이 경력이 쌓일수록 실감됨

  • “Nix 자체는 문제 없다. 활용법에 문제가 있었음”이란 말이 ‘적재적소에 맞는 도구를 쓰라’는 좋은 예시라고 생각함. Nix는 일부에선 훌륭하지만, 어떤 곳에선 최악임. 문제는 배우는 데 시간이 많이 들어서, 어느 정도 익숙해져 결정을 내릴 때쯤엔 이미 투자한 시간이 아까워 쉽게 방향 전환 못하고, 결국 억지로 기존 목적에 계속 Nix를 사용하게 되는 현상임

    • 비슷하게 느끼는 바가 있음. 어떤 면에선 Nix가 다른 OS보다 더 직관적인 프로그래밍 패러다임이라고 생각함. 아직 익숙치 않을 뿐. Nix 표현식은 입력(패키지 저장소, 키-값 등)과 출력(리눅스 시스템) 구조임. 몇 년 안에 더 익숙해질 수도 있을 듯. 예를 들어 AI가 shell.nix나 configuration.nix를 스펙에 맞게 생성하는 것도 이런 구조 덕분임. 나도 종종 저장소별 env가 완전히 포함된 상태로 만들고, flakes로 하면 더 재현성 있는 환경을 만들 수 있을 듯. (flake.nix는 shell.nix랑 비슷하지만 버전 고정까지 지원…)
  • 버전이 없는 곳에 억지로 버전을 도입하려는 것처럼 보임. “디폴트 버전” 때문에 의존성이 깨진다고? docker의 :latest 태그 쓰고, 바뀔 때마다 서버가 망가지는 상황과 비슷함. 이 블로그 내용은 이해가 잘 안 됨. “Nix 의존성을 별도 레이어로 나눌 수 없다”에도 공감하지 않음. 원하는 만큼 /nix/store를 분할 가능하며, 컨테이너와 Nix를 어떻게 써야 하는지도 잘 모르는 느낌. 이렇게 능력이 부족하다면, 제시된 대안도 결국 같은 문제를 반복할 것 같음. 고전적인 NIH(자기만의 툴 만들기) 증후군의 예시임

    • Nix가 맞지 않는 곳에서 안 쓰는 건 당연하지만, 이미 돌아가는 시스템을, 다른 사람들도 이미 해결한 문제를 조금만 찾아보면 알 수 있음에도, 처음부터 끝까지 새로 만드는 건 근본적으로 이상하다고 느낌. nix2container나 flakes가 모든 문제를 해결할 수 있을 듯. 버전 관리도, 3년 전에 작성한 flakes가 지금도 똑같이 빌드되고 결과도 변함없음. 왠지 시장 진출이나 투자 유치를 노리는 플랫폼 전환 냄새가 남. 참고로 nixpacks GitHub를 봤더니 rustPlatform만 쓰고있고, rust 문제라면 rust-overlay가 사실상 정답임

    • 어떤 방법이 VC를 더 쉽게 유치하는지 고민해본다면, nix 래퍼보단 “배포 플랫폼”이란 타이틀이 유리함

  • “Nix 의존성을 별도 레이어로 분할할 수 없다”는 말과 달리, nix2container는 정확히 그 분할이 가능함. 예를 들면 bash가 필요한 이미지라면, bash가 포함된 레이어만 따로 만들 수 있고, 이 레이어는 bash가 바뀔 때만 다시 빌드/푸시하면 됨. “의존성 때문에 거대한 이미지가 단일 /nix/store 레이어로 생긴다” 역시, nixpkgs.dockerTools.buildImage 함수엔 해당하지만 nix2container나 nixpkgs.dockerTools.streamLayeredImage에는 해당되지 않음. 이 도구는 실제로는 스크립트를 생성해 이를 통해 이미지를 푸시하도록 함. nix2container는 모든 레이어의 경로를 JSON으로 만들고, Skopeo를 이용해 이미지를 Docker, 레지스트리, podman 등에 푸시함. (참고로 내가 nix2container 저자임)

    • nix2container에 정말 감사하다고 말하고 싶음. AWS(ECR) 배포에 쓰는데, 빌드 간 전환 시간이 한 자리수 초로 줄어듦

    • 우리도 Docker 이미지 크기 문제 때문에 nix2container를 테스트할 예정이었음. 좋은 툴 만들어줘서 고마움

  • 여기 핵심 문제는 언어 패키지 매니저가 조장하는 “맞춤 버전 스프”를 고수하려는 태도 자체라고 생각함(이 방식은 지속 불가능함). 대안인 Mise는 패키지 간 버전 제약을 이해하지 못하고, 각 패키지의 테스트도 전혀 하지 않음. 같은 수준의 신뢰성은 전혀 기대할 수 없음

    • 맞춤 버전 스프가 지속 불가능한 건 사실이지만, 사람들이 이걸 계속 쓰는 이유는 잘 작동하기 때문임. OS 수준 라이브러리는 매우 보수적으로 관리해서 쉽게 깨지지 않고, mise나 asdf 같은 툴로 그 위에 맞춤형 버전 조합을 올려도 대체로 멀쩡함. 깨져도 버전/설정만 만지면 바로 해결됨. 깨진 건 짜증나긴 해도, 중요치 않음. 추가 학습이나 노력이 들어가는 시스템은 시간 낭비로 여겨짐. 다만 “깨지지 않는 상태”를 더 중요하게 여기는 사람들은 오히려 러닝 커브와 불편이 있어도 Nix를 선호함. Railway처럼 많은 사용자를 겨냥하는 곳이라면 결국 첫 번째 그룹(간편성, 관성)을 더 신경 쓴 선택을 함

    • “맞춤 버전 스프”가 무슨 의미이고, 대안은 뭔지 궁금함

    • 둘 다 충분히 가능함. 예를 들어 Rust 패키지는 Cargo.lock 정보로 Nix로 쉽게 빌드 가능. Nixpkgs가 커스텀 버전 조합과는 상충하지만, Nix 자체는 충분히 잘 함

  • Nix는 임의 버전이 아니라 커밋 단위 보장임. glibc 변경이나 공유 라이브러리 충돌 같은 엣지 케이스에는 고생할 수 있음. 지금은 이미 늦었을지 몰라도, Nix로 더 우아하게 쓰는 방법에 대해 컨설팅도 가능함. 제품 자체는 멋지다고 생각함

    • nix는 공유 라이브러리 충돌을 아주 강하게 방지함. 하지만 사소한 변경(코멘트, 문서 등)에도 관련 모든 하위 의존물까지 싹 다 재빌드됨. 그 결과 매우 방대한 재빌드가 필요하게 되고, 개발이 고통스러워질 수 있음. nixpkgs의 staging 과정을 보면 알 수 있음

    • Nix의 가치를 충분히 이해함. 다만 “망한다”는 말은 조금 과장이 있다고 생각함. Nix에 비해 큰 보장 일부를 잃는 것은 사실이지만, 그래도 대부분의 소프트웨어보단 훨씬 더 잘 돌아갈 확률이 높다고 생각함

  • 자신만의 derivations를 만들지 않고 왜 굳이 nixpkgs 해시에 의존했는지 모르겠음

  • 많은 댓글이 “사실 Nix로 다 해결된다, 단 나처럼 전문가여야 한다”는 분위기라 흥미롭게 봄

    • 만약 한 회사가 모든 기술과 비즈니스를 JavaScript로 하고 있다가, 기존 핵심 개념(함수, 배열 등)을 이해 못해서 NIH(독자 규격의 새 언어 개발)를 한다면, 그건 내부 모자람에 더 가까운 문제임

    • Nix 얘기만 나오면 항상 반복되는 평소 분위기임

    • 이게 바로 Nix가 가진 분위기임. “내가 세상을 구할 것이다”식의 전형적인 서사와 “내가 원하는 기능이 안 된다”라는 반응에 대해 항상 “니가 제대로 안 써서 그럼”이라는 답이 돌아옴