Railway pnpm workspace: por qué falla el build
En pocas palabras: Railway buildea desde ws-server/, un directorio que no es la raíz del workspace, así que no ve pnpm-lock.yaml ni pnpm-workspace.yaml. Sin lockfile, Railpack elige npm y falla con “pnpm: not found”. La solución es declarar “packageManager”: “[email protected]” en el package.json de ese directorio.
Un desarrollador (usuario daniel_pertu en dev.to) documentó el 14 de octubre de 2026 por qué un Railway pnpm workspace falla con “pnpm: not found”. Railway buildea desde ws-server/, donde no están pnpm-lock.yaml ni pnpm-workspace.yaml, y Railpack elige npm. Lo resolvió declarando packageManager en el package.json de ese directorio.
Un pnpm workspace es un conjunto de paquetes dentro de un mismo repositorio que pnpm instala juntos con un solo lockfile, y se define con un pnpm-workspace.yaml en la raíz. Railpack es el builder de Railway: inspecciona el directorio que le das, detecta el lenguaje y el gestor de paquetes, instala dependencias y compila. El problema aparece cuando ese directorio no es la raíz del workspace.
En este artículo:
- En 30 segundos
- ¿Por qué el build de Railway falla con “pnpm: not found” en un subdirectorio?
- ¿Cómo detecta Railpack el gestor de paquetes en un Railway pnpm workspace?
- ¿Cómo arreglarlo: packageManager con versión exacta y Node compatible?
- ¿Qué hace allowBuilds en pnpm-workspace.yaml y por qué esbuild no se instala?
- ¿Cómo queda la configuración de build y health check en Railway?
- ¿Qué está confirmado y qué no?
- ¿Cómo comprobar qué ve el builder antes de desplegar?
- Errores comunes
- Preguntas Frecuentes
- Conclusión
- Fuentes
En 30 segundos
- Railway buildea desde el directorio configurado (ws-server/) y desde ahí no se ven el lockfile ni el pnpm-workspace.yaml, que están un nivel arriba.
- Sin lockfile, Railpack elige npm y el comando “pnpm install && pnpm build” termina en “pnpm: not found”.
- El arreglo del autor: “packageManager”: “[email protected]” con versión exacta en ws-server/package.json y engines.node en “>=24”.
- El costo: sin pnpm-workspace.yaml la lista allowBuilds no aplica, esbuild no corre su postinstall y el autor sacó tsx del paquete.
¿Por qué el build de Railway falla con “pnpm: not found” en un subdirectorio?
Falla porque Railway solo ve el directorio que configurás como origen del servicio. Si es ws-server/ y los archivos del workspace están un nivel arriba, Railpack no encuentra ningún lockfile, asume npm y después ejecuta un comando que arranca con pnpm install. El resultado es “pnpm: not found”.
El caso es PubTrivia, que se despliega en dos destinos desde un mismo repo: la app Next.js va a Vercel y un proceso Node de larga vida, con los WebSockets y los timers del juego, va a Railway. El workspace tiene un solo miembro (ws-server) y, según el autor, en local es cómodo: un install, un lockfile, un node_modules. Esto es el relato de un caso, no un bug que Railway haya reconocido. Te puede servir nuestra guía para desplegar en Railway.
El autor lo resume así (traducción nuestra): “Railway buildea desde el directorio que le das, y ese directorio no es la raíz del workspace”.
Ponele que migraste un repo suelto a workspace hace meses y nunca tocaste el deploy. Todo anda hasta que alguien apunta el servicio a un subdirectorio, y de golpe el builder trabaja con una carpeta que no sabe que pertenece a nada. Ojo con esto: el error que ves habla de pnpm, pero la causa es de visibilidad de archivos.
¿Cómo detecta Railpack el gestor de paquetes en un Railway pnpm workspace?

Según la documentación de Railpack, el gestor se detecta en un orden fijo, y si ningún paso da resultado el default es npm. Eso explica el caso: dentro de ws-server/ no había campo packageManager ni lockfile, así que cayó al último escalón.
- Campo packageManager: Railpack lo lee de package.json.
- Archivos idiomáticos de Mise: si Mise resuelve exactamente un gestor desde devEngines.packageManager o packageManager, se usa ese.
- Lockfiles: pnpm-lock.yaml para pnpm, bun.lock o bun.lockb para Bun, yarn.lock para Yarn 1.
- Campo engines: engines.pnpm, engines.bun o engines.yarn como último recurso.
- Default: npm.
Ahora bien, ¿qué tipo de monorepo es este? Railway distingue dos. En el aislado definís un root directory y Railway solo baja archivos de esa carpeta. En el compartido se buildea desde la raíz y cada servicio usa comandos propios, por ejemplo “pnpm –filter backend build”. El caso de PubTrivia usa root directory sobre un miembro de un workspace, o sea que cae en el medio de los dos modelos. Por eso se rompe.
Dos detalles de la documentación que conviene tener presentes: el archivo de config de Railway no sigue al root directory (hay que indicar la ruta absoluta, como /backend/railway.toml) y Railpack, según su propia doc, espera los scripts de build y start en el package.json raíz cuando hay que construir un paquete puntual del workspace. La alternativa del monorepo compartido con –filter es una inferencia nuestra a partir de la doc de Railway. El autor no la probó. Más contexto en cómo se organiza un workspace de scripts.
¿Cómo arreglarlo: packageManager con versión exacta y Node compatible?
Declarás packageManager dentro de ws-server/package.json, el único archivo que el builder sí ve, y subís engines.node a “>=24”. El autor aclara que build platforms leen ese campo como declaración y no como sugerencia, y que la raíz no alcanza porque el builder nunca llega a ella.
{
"name": "ws-server",
"private": true,
"type": "module",
"packageManager": "[email protected]",
"engines": { "node": ">=24" }
}- Versión exacta: “pnpm@11” dejaría que el builder resuelva un patch distinto del que generó el lockfile, según el autor.
- Node 24: el autor cita dos razones independientes. pnpm 11 declara engines node “>=22.13”, así que Node 20 no puede correrlo, y Node 20 ya salió de soporte.
- Dónde se lee: Railpack toma la versión de Node de RAILPACK_NODE_VERSION, luego de devEngines.runtime y luego de engines.node, según su documentación.
¿Qué hace allowBuilds en pnpm-workspace.yaml y por qué esbuild no se instala?
allowBuilds es la lista de dependencias a las que pnpm deja correr scripts de instalación. Según el autor, desde pnpm 10 esos scripts no corren salvo que el paquete esté autorizado, y en pnpm 11 la lista se llama allowBuilds. La documentación de pnpm la ubica en Build Settings y aclara que casi toda la configuración se lee de pnpm-workspace.yaml, salvo auth y registry, que siguen en .npmrc.
En este repo la lista autoriza @sentry/cli, core-js, esbuild, sharp y unrs-resolver. El tema es que vive en pnpm-workspace.yaml, un nivel arriba, y para el build de Railway ese archivo no existe. tsx depende de esbuild, esbuild baja un binario en su postinstall, y sin la autorización esa pieza no llega al deploy.
La salida del autor fue sacar tsx de ws-server/package.json. Producción compila con tsc y arranca con node dist/index.js; el script dev sigue invocando tsx, que resuelve desde la instalación de la raíz. Es un trade-off asumido: un script de desarrollo que depende de una herramienta no declarada, a cambio de no meter un postinstall en el deploy. Mover la configuración para que el build la vea es otra vía posible, pero la fuente no la prueba. En otras alternativas a Vercel para desplegar profundizamos sobre esto.
¿Cómo queda la configuración de build y health check en Railway?
Queda más corta. El autor pasó de “pnpm install && pnpm build” a “pnpm build”, porque Railpack ya instala dependencias (su doc lo confirma) y la segunda instalación era justo la que no encontraba pnpm. A veces el arreglo de un paso que falla es borrarlo.
- Build: buildCommand “pnpm build”.
- Start: startCommand “node dist/index.js”.
- Health check: healthcheckPath /health con healthcheckTimeout de 30.
- Reinicios: restartPolicyType ON_FAILURE con restartPolicyMaxRetries en 10.
El /health es un handler de node:http que responde 200 con el estado, la cantidad de conexiones y un booleano que dice si Redis está conectado. Es un chequeo de liveness, no de readiness, y el autor lo dice sin vueltas: si Redis cae, el servidor sigue atendiendo la sala que tiene, y reiniciarlo por eso sería castigar a un proceso que está haciendo su trabajo. Los 10 reintentos son el número del que él mismo está menos seguro, porque son diez oportunidades para que una dependencia vuelva y también diez ciclos antes de que la plataforma se rinda. Y los imports con extensión .js dentro de archivos .ts no son un error: es “module”: “NodeNext”, donde el especificador que escribís es el que termina en el JavaScript emitido.
¿Qué está confirmado y qué no?
- Confirmado por el autor: el síntoma, la causa (lockfile y workspace un nivel arriba), los tres cambios y la config final de deploy.
- Confirmado por la documentación: el orden de detección de Railpack, el comportamiento del root directory y que la config de Railway no sigue esa ruta.
- No confirmado: que Railway lo considere un bug. El texto es un caso individual.
- No confirmado: que mover allowBuilds o usar el monorepo compartido con –filter funcione mejor. Nadie lo midió ahí.
- Sin discutir en la fuente: con ws-server/ aislado, las dependencias se instalan sin el lockfile de la raíz, así que las versiones podrían diferir de las de tu máquina. Es una inferencia nuestra.
¿Cómo comprobar qué ve el builder antes de desplegar?
Propuesta editorial, no probada por nosotros ni sacada de la fuente: copiá solo el directorio del servicio a una carpeta temporal fuera del repo y corré ahí la misma secuencia que Railway. Si falla, reproduciste lo que ve el builder.
- Aislá el directorio: copiá ws-server/ solo, sin la raíz.
- Verificá el gestor: con Corepack activo, pnpm –version tiene que devolver la versión de packageManager.
- Corré el build: pnpm install y después pnpm build, sin nada de afuera.
- Arrancá el servidor: node dist/index.js y pedí /health.
Errores comunes
- Declarar packageManager solo en la raíz. El builder no llega a ese archivo. Ponelo en el package.json del directorio que despliega Railway.
- Usar “pnpm@11” sin patch. Un rango abre la puerta a otra versión que la del lockfile. Fijá la exacta.
- Dejar “pnpm install” en el buildCommand. Railpack ya instala, y el segundo install puede ser el que rompa.
- Esperar que railway.json siga el root directory. No lo sigue: la doc de Railway pide la ruta absoluta.
- Dejar engines.node en 20 con pnpm 11. Según el autor, pnpm 11 pide Node “>=22.13”.
Preguntas Frecuentes
¿Por qué Railway dice “pnpm: not found” en el build?
Porque Railpack no detectó pnpm y eligió npm, y después ejecutó un comando que empieza con pnpm install. En el caso documentado, el lockfile y el pnpm-workspace.yaml estaban fuera del directorio que Railway buildea. Lo explicamos a fondo en un deploy verde que igual falla.
¿Cómo le indico a Railpack que use pnpm y no npm?
Declarando “packageManager”: “[email protected]” en el package.json del directorio que se despliega. Según la doc de Railpack, ese campo es lo primero que se mira, antes de los lockfiles.
¿Qué hace allowBuilds en pnpm-workspace.yaml y por qué esbuild no se instala?
allowBuilds es la lista de paquetes autorizados a correr scripts de instalación. Si el archivo no está a la vista del build, esbuild no corre su postinstall y no baja su binario.
¿Qué diferencia hay entre un monorepo aislado y uno compartido en Railway?
En el aislado, Railway baja solo los archivos del root directory y los componentes no comparten código. En el compartido se buildea desde la raíz y cada servicio define sus propios comandos de build y start.
¿Cómo despliego un servidor Node de un monorepo pnpm en Railway?
Hay dos caminos documentados. Uno es root directory con packageManager y dependencias autocontenidas en el paquete, como hizo el autor. El otro es buildear desde la raíz con comandos como “pnpm –filter backend build”, que según la doc de Railway es el modelo del monorepo compartido.
Conclusión
Si desplegás un Railway pnpm workspace desde un subdirectorio, tratá ese directorio como un proyecto aparte: tiene que declarar su gestor, su versión exacta de pnpm y un Node que la soporte, sin depender de archivos de la raíz. Lo que el autor ganó fue un deploy que anda; lo que perdió fue tsx como dependencia del paquete y, probablemente, el lockfile compartido en el build. Antes de adoptar su receta, corré la verificación de arriba y decidí si te conviene más el monorepo compartido con –filter. Y si estás evaluando dónde correr un proceso así y priorizás infraestructura en Argentina, mirá también donweb.com.





