|

Deployment de un juego multiplayer en Railway con Socket.io

Si querés hacer el deployment de un juego multiplayer en Railway, la respuesta corta es que sí funciona y por una razón concreta: Railway corre tu servicio como un proceso normal de larga duración, así que Socket.io se conecta y listo. El desarrollador de Old Light, un juego de estrategia en tiempo real que corre en el navegador, lo documentó el 9 de julio de 2026: descartó Vercel y Netlify justamente porque un socket abierto no encaja en serverless.

El deployment de un juego multiplayer en Railway es el proceso de publicar un backend en tiempo real (típicamente Node.js con Socket.io sobre WebSocket) en la plataforma Railway, que ejecuta el servidor como un proceso persistente. A diferencia del hosting serverless, mantiene la conexión abierta mientras haya jugadores conectados, sin reinicio por request ni timeout por inactividad. Es la arquitectura estándar para partidas en vivo, chats y sincronización de estado.

En 30 segundos

  • Serverless no sirve para sockets: Vercel y Netlify bootean por request y después congelan el proceso, según el análisis de dev.to. Socket.io necesita un proceso que viva toda la sesión.
  • Railway usa procesos de larga duración: el servidor arranca una vez y corre indefinidamente, así que la conexión WebSocket queda abierta sin timeout por inactividad.
  • Monorepo con dos servicios: Old Light es un monorepo de npm workspaces con paquete de tipos, API (Express + TypeORM + Socket.io) y web app en Vite. En Railway son dos servicios sobre el mismo repo.
  • Variables de build, no de runtime: Vite hornea la URL de la API en el build, así que va como build variable. Postgres se agrega como plugin e inyecta DATABASE_URL.
  • Para escalar a varias instancias: Socket.io por defecto guarda el estado en memoria del proceso; con más de una instancia necesitás el adapter de Redis para el pub/sub entre procesos.

¿Por qué los hosts serverless no sirven para juegos multiplayer?

Ponele que armaste un juego donde, apenas termina un build o aterriza una flota, el servidor tiene que empujar el cambio a todos los clientes conectados. Eso es un socket que se queda abierto. Y ahí es donde el modelo serverless se cae.

Serverless brilla cuando tu backend son funciones sin estado. Es la forma equivocada en el momento en que necesitás un socket que permanezca abierto. Como lo dice el propio autor en el artículo de dev.to, estos hosts “bootean por request y después congelan” (boot per request and then freeze). Socket.io quiere un proceso que viva toda la sesión, no uno que se despierta, responde y se apaga.

¿Y si igual insistís con serverless? Podés colgarle un servicio de WebSocket administrado aparte. El problema es que ahora tenés un segundo sistema para correr y pagar. Más piezas, más cosas que se rompen. Sobre eso hablamos en alternativas para desplegar tu aplicación gratis.

¿Qué es un servidor de larga duración (long-lived process)?

Un servidor de larga duración es un proceso que se inicia una vez y corre indefinidamente, sin reiniciarse entre requests. Mantiene estado en memoria (conexiones, salas, sesiones) mientras esté vivo. Es lo opuesto a una función serverless, que nace y muere con cada invocación.

Railway corre tu servicio así, como un proceso normal. Fly.io hace lo mismo, con más perillas para tocar. El autor de Old Light lo resume sin vueltas: quería salir a producción rápido, así que Railway ganó. No es que Fly esté mal (para nada), es que a veces menos configuración es exactamente lo que necesitás para no perder el fin de semana.

¿Por qué Socket.io necesita un servidor persistente?

Socket.io mantiene una conexión abierta por cada cliente, rutea mensajes por sala y maneja la reconexión automática cuando la red se corta. Todo eso vive en la memoria de un proceso. Si el proceso termina apenas responde, como pasa en serverless, el socket se cae y con él el estado de la partida.

Acá viene lo bueno: en un proceso de larga duración, Socket.io se conecta y listo, sin adaptadores raros ni servicios extra. El servidor arranca, abre el socket, y ese socket sigue vivo mientras el jugador esté ahí. Nada de despertar la función cada vez que alguien mueve una flota. Complementá con configurar tu pipeline de integración continua.

Comparación: Vercel, Netlify y Railway para WebSocket

Para que quede claro dónde encaja cada uno, esta tabla resume el modelo de ejecución. Ojo: son datos objetivos de contexto técnico, no una recomendación de contratar a ninguno de estos servicios de deploy.

CriterioVercelNetlifyRailway
Modelo de ejecuciónServerless (por request)Serverless (por request)Proceso de larga duración
WebSocket persistente nativoNo (requiere servicio aparte)No (requiere servicio aparte)Sí, directo
Estado en memoria del procesoSe congela entre requestsSe congela entre requestsSe mantiene mientras corre
Encaje con Socket.ioForzadoForzadoNativo
Postgres integradoVía integración externaVía integración externaPlugin que inyecta DATABASE_URL
deployment juego multiplayer railway diagrama explicativo

La lectura es simple. Para funciones sin estado, cualquiera de los tres zafa. Para un socket que tiene que quedarse abierto horas, el proceso de larga duración es el que no te pelea.

¿Cómo se arma un monorepo para un juego multiplayer?

Old Light es un monorepo de npm workspaces con tres paquetes:

  • Paquete de tipos: los tipos de TypeScript compartidos entre cliente y servidor. Se buildea primero, porque los otros dos dependen de él.
  • API backend: Express más TypeORM más Socket.io. Es el proceso de larga duración que sostiene los sockets y habla con la base.
  • Web app: una app de Vite servida por un pequeño servidor Express propio.

¿Por qué un monorepo y no dos repos sueltos? Porque compartís los tipos entre cliente y servidor sin duplicar nada. Definís una vez cómo es un mensaje de “flota enviada” y lo usás en las dos puntas. En Railway eso se traduce a dos servicios sobre el mismo repo, cada uno con su propio root directory y su build command.

Se deployan como orígenes separados. Por eso la web app lee la URL de la API desde una variable de entorno. Y acá hay un detalle que a más de uno lo hizo renegar: Vite hornea esa URL en el momento del build, así que es una build variable, no una de runtime. Si la cambiás después de compilar, no pasa nada hasta que rebuildees. En elegir la herramienta de CI/CD adecuada profundizamos sobre esto.

Configuración de Socket.io y Express: el deployment del juego multiplayer en Railway

El armado del backend sigue un patrón conocido para cualquiera que haya tocado Node en tiempo real. Creás un servidor HTTP con Express, le enganchás Socket.io encima y definís los eventos: conexión, desconexión y los tuyos propios (mover flota, reclamar estrella, lo que sea tu juego).

  • CORS bien configurado: el backend tiene que aceptar el origen del frontend. En Railway, la URL pública de cada servicio queda disponible como variable de entorno, así que apuntás el CORS a la del servicio web.
  • Postgres como plugin: Railway lo provisiona e inyecta DATABASE_URL. TypeORM la levanta y en producción se corren las migraciones al arrancar.
  • Health check: un endpoint simple que responda 200 ayuda a que la plataforma sepa que tu proceso está vivo y sano.

Si esto lo pensás para infraestructura propia más adelante (un VPS, un servidor dedicado para la base o para archivos), en Argentina tenés donweb.com para hosting y dominios sin irte a pagar en dólares afuera.

¿Cómo escalar a varias instancias con el adapter de Redis?

Mientras corras una sola instancia, Socket.io guarda todo en la memoria de ese proceso y no hay drama. El problema aparece cuando escalás horizontal.

Imaginate dos instancias del backend detrás de un balanceador. El jugador A cae en la instancia 1, el jugador B en la 2. A manda un mensaje a su sala, pero B no lo recibe nunca, porque cada instancia tiene su propia memoria y no se hablan entre sí. Clientes fantasma, salas partidas, sincronización rota.

La solución es el adapter de Redis. Redis hace de pub/sub central: cada instancia publica los eventos y las demás los reciben, así todos los procesos ven la misma foto. En Socket.io se engancha con io.adapter(createAdapter(pubClient, subClient)). Railway te provisiona Redis como otro servicio, y recién ahí tu juego escala más allá de una instancia sin perder mensajes. Tema relacionado: consideraciones de seguridad para tu repositorio.

Errores comunes en el deployment y cómo evitarlos

  • Intentar meter el socket en serverless: síntoma clásico, la conexión se corta sola a los pocos segundos porque la función alcanza su tope de ejecución. Fix: usá un proceso de larga duración, no una función.
  • CORS mal seteado: el navegador bloquea el handshake y el cliente no conecta nunca. Fix: apuntá el origen permitido a la URL pública real del frontend, no a localhost olvidado de la etapa de desarrollo.
  • Escalar sin Redis: con más de una instancia aparecen los clientes fantasma y los mensajes que no llegan. Fix: agregá el adapter de Redis antes de subir la segunda instancia, no después de que se rompa.
  • Confundir la variable de build con la de runtime: cambiás la URL de la API y el frontend sigue apuntando a la vieja. Fix: acordate de que Vite la hornea en el build, así que rebuildeá tras cambiarla.

Preguntas Frecuentes

¿Por qué falla Vercel o Netlify con Socket.io?

Fallan porque son serverless: bootean por cada request y después congelan el proceso, según el análisis de dev.to publicado el 9 de julio de 2026. Socket.io necesita un proceso que viva toda la sesión para sostener el socket abierto, y una función que muere tras responder no puede hacerlo.

¿Qué plataforma sirve para conexiones WebSocket de larga duración?

Cualquiera que corra tu backend como proceso de larga duración. Railway lo hace de forma directa y sin configuración extra, y Fly.io también con más opciones para ajustar. Ambas mantienen el socket abierto sin timeout por inactividad, a diferencia de los hosts serverless.

¿Cómo se arquitectura un monorepo con backend multiplayer?

Con npm workspaces y paquetes separados. Old Light usa un paquete de tipos compartido, una API con Express, TypeORM y Socket.io, y una web app en Vite. El paquete de tipos se buildea primero porque los otros dependen de él, y en Railway backend y frontend van como dos servicios distintos sobre el mismo repo.

¿Cuándo necesito el adapter de Redis en Socket.io?

Lo necesitás apenas corrés más de una instancia del backend. Con una sola, la memoria del proceso alcanza. Con varias, cada instancia tiene su propia memoria y no se comunican, así que sin Redis los clientes en instancias distintas no reciben los mensajes de los demás.

¿La URL de la API en Vite es variable de build o de runtime?

Es de build. Vite hornea la URL en el momento de compilar, así que la configurás como build variable en Railway. Si la cambiás después del build, el frontend sigue apuntando a la anterior hasta que vuelvas a compilar.

Conclusión

Lo que cambia acá es el criterio para elegir dónde deployás. Si tu juego empuja estado en tiempo real por WebSocket, el modelo serverless te obliga a inventar un segundo sistema; un proceso de larga duración como el de Railway te deja conectar Socket.io y seguir. El caso de Old Light lo muestra en concreto: monorepo de npm workspaces, dos servicios, tipos compartidos y Postgres como plugin.

¿Qué hacer si estás por arrancar? Definí primero si necesitás sockets persistentes. Si la respuesta es sí, elegí un host de proceso largo desde el día cero, configurá bien el CORS y dejá el adapter de Redis listo para cuando escales. Te ahorrás la migración de apuro cuando el juego crezca.

Fuentes

Te puede interesar...