Entornos de despliegue
La plataforma corre en tres entornos: local (Docker Compose en la máquina de un desarrollador), development (un despliegue real en Railway, usado para staging/pruebas contra infraestructura con forma de producción) y production (Railway, tráfico real). Esta página es el mapa de cómo se relacionan esos tres — qué NODE_ENV/dominio usa cada uno, qué se comparte vs. qué es separado, y cómo cada cliente (backend, ambos frontends de Next.js, docs, iOS) apunta al correcto. Para la referencia exhaustiva variable por variable (qué hace cada clave de .env, dónde obtenerla), consulta Configuración del entorno.
Los tres entornos
| Local | Development | Production | |
|---|---|---|---|
| Dónde corre | Docker Compose, tu máquina | Railway | Railway |
NODE_ENV | dev | development | production |
| Backend | http://localhost:8000 | dev.api.closegram.com | api.closegram.com |
| Docs | http://localhost:3005 (o similar) | dev.docs.closegram.com | docs.closegram.com |
| frontend-nextjs | http://localhost:3000 | dev.closegram.com | closegram.com |
| frontend-admin | http://localhost:3001 | dev.admin.closegram.com | admin.closegram.com |
| Postgres | Contenedor Docker local (sin cifrar) | Gestionado por Railway, DB_NAME=closegram_dev | Gestionado por Railway, DB_NAME=closegram |
| BD de analítica | Contenedor Docker local | ANALYTICS_DB_NAME=closegram_analytics_dev | ANALYTICS_DB_NAME=closegram_analytics |
| Credenciales de terceros (Stripe, Firebase, AWS, Twilio, etc.) | Placeholders de dev/modo de prueba | Igual que producción — solo difieren los dos nombres de BD de arriba | Credenciales reales |
Development reutiliza intencionalmente las llaves de terceros de producción — busca validar contra infraestructura con forma de producción, no ser una copia completamente aislada. Las únicas dos variables de Railway que realmente deberían diferir entre los entornos "development" y "production" de Railway son DB_NAME y ANALYTICS_DB_NAME; todo lo demás (llaves de API, secretos, WEBAUTHN_RP_ID, etc.) tiene el mismo valor en ambos, cambiando solo las variables *_URL y NODE_ENV según la tabla de abajo.
NODE_ENV importa para dos cosas independientes
- Qué archivo
.env.<NODE_ENV>carga el backend (dev→.env.dev,development/production→ variables de entorno reales inyectadas por Railway, sin ningún archivo.envlocal involucrado). - Qué clave de configuración de Sequelize lee
sequelize-clidedatabase/config/config.js—dev,developmentyproductionson tres claves separadas ahí.developmentse agregó específicamente porque el entorno "development" de Railway defineNODE_ENV=development, y sin una clave correspondiente,sequelize-cli db:migrate(ejecutado como elpreDeployCommandde Railway, verpredeploy.sh) lanzaconfig.development is undefinedy aborta el despliegue. Tiene la misma forma queproduction(SSL requerido — una instancia real de Postgres gestionada) en lugar de la dedev(sin SSL — solo Docker local), ya que la base de datos de development en Railway también es una instancia remota real, solo que separada.
Los seeders también se rastrean, no solo las migraciones
Cada clave de entorno en database/config/config.js define seederStorage: 'sequelize' con seederStorageTableName: 'SequelizeSeedMeta'. Sin esto, sequelize-cli usa por defecto un archivo JSON local para rastrear qué seeders ya corrieron — algo sin sentido contra una instancia real de Postgres, ya que el estado de "ya sembrado" viviría en la máquina que ejecutó npm run seed por última vez, no en la base de datos misma. Con esto, npm run seed (db:seed:all) registra cada archivo de seeder que ejecuta exitosamente en SequelizeSeedMeta (creada automáticamente en el primer run, igual que SequelizeMeta para las migraciones) y omite lo que ya esté registrado en la siguiente corrida — así que volver a correr npm run seed contra una base de datos ya sembrada (el entorno development de Railway, sobre todo, ya que sus 37 archivos de seeders incluyen varios bulk-* que insertan bastantes datos de demo) es un no-op en lugar de reinsertar duplicados o fallar por violaciones de restricción única.
db:reset (reset local completo sin tirar el contenedor de Docker) corre seed:undo:all antes de migrate:undo:all — en ese orden, ya que las funciones down() de los seeders necesitan que las tablas de la app todavía existan, y deshacerlos también limpia SequelizeSeedMeta para que el siguiente npm run seed vuelva a sembrar todo en lugar de ver entradas de "ya hecho" obsoletas y no sembrar nada en las tablas recién recreadas y vacías. db:fresh no necesita esto, ya que docker:reset recrea el contenedor (y por lo tanto SequelizeSeedMeta) desde cero.
Los seeders son solo para local y development — nunca production
Los seeders insertan datos de demo/dummy desechables (usuarios falsos, posts/follows/comentarios masivos, una cuenta admin, etc.). database/config/config.js rechaza correr cualquier comando db:seed* (db:seed, db:seed:all, db:seed:undo, db:seed:undo:all) cuando NODE_ENV resuelve a production, sin importar si se invoca vía npm run seed o directamente con sequelize-cli — las migraciones y el arranque normal de la app no se ven afectados, ya que la verificación solo se activa para comandos de seed.
Correr seeders opcionalmente como parte de un deploy
El paso 3 de predeploy.sh corre sequelize-cli db:seed:all cuando la variable de Railway RUN_SEEDS_ON_DEPLOY está en true en ese servicio — sin definir (o cualquier otro valor) lo omite, que es lo que pasa hoy por defecto en todos los servicios. Es opt-in y no incondicional a propósito: predeploy.sh es el mismo script para cada entorno de Railway, así que un paso de seed incondicional también se dispararía en production. Dos cosas independientes lo mantienen seguro:
- Opt-in: define
RUN_SEEDS_ON_DEPLOY=truesolo en el servicio dedevelopment. Nunca lo definas enproduction. - Respaldo: aunque se definiera por error en
production, el guard de producción dedatabase/config/config.js(arriba) rechazaría correr el comando de seed — y un fallo de seed es deliberadamente no fatal enpredeploy.sh(mismo patrón que el paso de migración de analítica), así que nunca abortaría el deploy, solo omitiría el sembrado y registraría por qué.
Como el sembrado se rastrea vía SequelizeSeedMeta (ver arriba), dejar RUN_SEEDS_ON_DEPLOY=true indefinidamente es seguro — cada deploy después del primero es un no-op para los seeders que ya corrieron.
Backend: checklist del dashboard de Railway
Por entorno de Railway (development y production se configuran por separado en el dashboard de Railway — no se leen de ningún archivo versionado):
| Variable | Valor en Development | Valor en Production |
|---|---|---|
NODE_ENV | development | production |
DB_NAME | closegram_dev | closegram |
ANALYTICS_DB_NAME | closegram_analytics_dev | closegram_analytics |
FRONTEND_URL | https://dev.closegram.com | https://closegram.com |
ADMIN_FRONTEND_URL | https://dev.admin.closegram.com | https://admin.closegram.com |
WEBAUTHN_RP_ID | closegram.com | closegram.com |
WEBAUTHN_ORIGIN | https://dev.closegram.com | https://closegram.com |
RUN_SEEDS_ON_DEPLOY (opcional — ver abajo) | true, si quieres que cada deploy (re)siembre datos de demo | Sin definir — nunca lo definas en producción |
Todo lo demás (DB_HOST/DB_USER/DB_PASSWORD, AWS, Stripe, PayPal, Firebase, Apple, APNs, Twilio, BigQuery, etc.) | Mismo valor que Production | Mismo valor que Development |
Un único WEBAUTHN_RP_ID de closegram.com es válido en closegram.com, dev.closegram.com, admin.closegram.com y dev.admin.closegram.com — los relying-party IDs de WebAuthn coinciden con el dominio registrable y sus subdominios, así que las passkeys no necesitan un RP ID por subdominio. WEBAUTHN_ORIGIN, a diferencia del RP ID, sí necesita coincidir exactamente con el origen en el que está el navegador, por eso difiere entre dev y prod.
El estado en vivo de cada una de estas (si está configurada, sin exponer nunca su valor) es visible en /system/environment en el panel de administración — consulta Estado del entorno. Revisa esa página después de configurar un entorno de Railway en lugar de re-derivar la lista a mano.
frontend-nextjs / frontend-admin: checklist del dashboard de Railway
Ambas son apps de Next.js desplegadas vía Docker en Railway. Las variables NEXT_PUBLIC_* se insertan en el bundle de JS en tiempo de build, así que deben declararse como ARG de Docker (ya hecho en ambos Dockerfile) y configurarse como variables de build en Railway, no solo de runtime — un valor configurado solo en runtime nunca llega al bundle.
| Variable | Valor en Development | Valor en Production |
|---|---|---|
NEXT_PUBLIC_GRAPHQL_URL | https://dev.api.closegram.com/web/graphql | https://api.closegram.com/web/graphql |
NEXT_PUBLIC_SITE_URL (solo frontend-nextjs) | https://dev.closegram.com | https://closegram.com |
Cualquier otra NEXT_PUBLIC_* (Firebase, Stripe, PayPal, Google Maps, Giphy, AdSense, GAM, GA, Apple Sign-In) | Mismo valor que Production | Mismo valor que Development |
El CORS del backend (ALLOWED_ORIGINS en apps/backend/api/server.js) se construye a partir de FRONTEND_URL/ADMIN_FRONTEND_URL, así que una vez que esos están bien configurados por entorno en el servicio del backend (ver arriba), ambos frontends pueden alcanzarlo sin configuración de CORS separada.
docs: checklist del dashboard de Railway
apps/docs es un build estático de Docusaurus sin variables específicas por entorno — docs.closegram.com y dev.docs.closegram.com sirven el mismo output de build. No aplica un checklist tipo backend/frontend aquí; la única preocupación por entorno es a qué despliegue enruta Railway cada dominio.
iOS
Config/APIConfig.swift resuelve uno de tres entornos al iniciar, en este orden:
- Flag de compilación de Swift
DEV_ENVIRONMENT— si está definido, siempre resuelve adevelopmentsin importar la configuración de build. Es el único mecanismo que funciona para un build archivado/distribuido (TestFlight, ad hoc), ya que en ese punto no hay un proceso de Xcode que lo lance ni un entorno de shell que leer. Se define víaOTHER_SWIFT_FLAGS="-DDEV_ENVIRONMENT". - Variable de entorno de proceso
API_ENVIRONMENT, leída solo en buildsDEBUG(es decir, cuando se lanza desde Xcode) —"development"o"production"; cualquier otro valor (incluyendo no definida) cae a local. Se define en la pestaña "Arguments" → Environment Variables del Run action del scheme de Xcode, el mismo lugar donde ya viveAPI_HOST. - Fallback: builds
DEBUGsin ninguno de los anteriores definido usan local por defecto; los buildsReleaseusan production por defecto.
Cada entorno resuelve su propia URL de GraphQL HTTP/WS y su URL de LiveKit WS:
| Local | Development | Production | |
|---|---|---|---|
| GraphQL | http://<API_HOST>:8000/web/graphql | https://dev.api.closegram.com/web/graphql | https://api.closegram.com/web/graphql |
| GraphQL WS | ws://<API_HOST>:8000/web/graphql | wss://dev.api.closegram.com/web/graphql | wss://api.closegram.com/web/graphql |
| LiveKit | ws://<API_HOST>:7880 | wss://livekit.closegram.com | wss://livekit.closegram.com |
<API_HOST> es la variable de entorno API_HOST (usa 127.0.0.1 por defecto) — configúrala con la IP local de tu máquina (ipconfig getifaddr en0 en macOS) para que un dispositivo físico en la misma red pueda alcanzar tu backend local; el simulador puede usar 127.0.0.1 directamente.
Development y production actualmente apuntan al mismo despliegue de LiveKit (livekit.closegram.com) — hoy no existe una instancia de LiveKit separada para dev. Si eso cambia, agrega un caso development a APIConfig.liveKitWSUrl.
Fastlane
build_dev, build_release, beta y beta_nogit aceptan una opción de lane env: (que cae a la variable de shell API_ENVIRONMENT si se omite):
# Apunta el build compilado a dev.api.closegram.com
bundle exec fastlane build_dev env:development
# Apunta un build de TestFlight a dev.api.closegram.com en lugar de producción
bundle exec fastlane beta env:development
Cuando env resuelve a "development", el lane pasa xcargs: 'OTHER_SWIFT_FLAGS="-DDEV_ENVIRONMENT"' a build_app, que es exactamente el flag de compilación que APIConfig.swift revisa primero (ver arriba) — así que esta es la única forma confiable de apuntar un build archivado a development, ya que los builds archivados no tienen variables de entorno de runtime que leer.
release (el lane de envío a la App Store) deliberadamente no tiene override de env — un build de App Store siempre apunta a producción, por diseño; no existe un escenario donde enviar un build apuntando a development a la App Store sea correcto.
Alternativamente, define API_ENVIRONMENT=development directamente en fastlane/.env (copiado de .env.default, que documenta esta variable) en lugar de pasar env: en cada invocación.