Entornos
Los tres ambientes
Sección titulada «Los tres ambientes»| Ambiente | Quién lo usa | AQUAZAKU_ENV |
Schema de Postgres | Cuándo nace | Cuándo muere |
|---|---|---|---|---|---|
| Local | quien desarrolla | development |
public |
docker compose up |
cuando se apaga la máquina |
| Preview | quien revisa una PR antes de mergear | preview |
preview |
push a una PR | merge a main (resetea), PR cerrada sin merge (queda hasta el próximo push) |
| Producción | la planta | production |
public |
primer deploy de prod | nunca, mientras el negocio siga |
Las variables
Sección titulada «Las variables»AQUAZAKU_ENV es la única variable nueva del sistema. El resto es lo que ya
existía — las cadenas de Supabase, el secreto de Better Auth, el WEB_PUBLIC_URL,
etc.
En despliegue/.env (copiar de despliegue/.env.ejemplo):
AQUAZAKU_ENV=developmentdocker compose la propaga a los servicios api y migraciones. El pool
de Postgres arranca con search_path=public y los queries resuelven a las
tablas de siempre.
Preview
Sección titulada «Preview»Cada preview es una URL de Vercel apuntando al environment staging de
Railway. Los dos lados setean AQUAZAKU_ENV=preview:
| Plataforma | Variable | Valor |
|---|---|---|
Railway (staging) |
AQUAZAKU_ENV |
preview |
Vercel (Preview) |
API_INTERNAL_URL |
URL pública del environment staging de Railway |
Vercel (Preview) |
WEB_PUBLIC_URL |
https://${VERCEL_URL} |
Vercel (Preview) |
NODE_ENV |
preview |
*.vercel.app tiene que estar en los trustedOrigins de Better Auth — si no,
el login falla con 403. Cambio aplicado en T6.
Producción
Sección titulada «Producción»| Plataforma | Variable | Valor |
|---|---|---|
Railway (production) |
AQUAZAKU_ENV |
production |
Vercel (Production) |
API_INTERNAL_URL |
https://api.aquazaku.com |
Vercel (Production) |
WEB_PUBLIC_URL |
https://app.aquazaku.com |
Vercel (Production) |
NODE_ENV |
production |
AQUAZAKU_ENV no se setea explícitamente en producción; vale por su
default (production). Esto es deliberado: si alguien borra la variable por
accidente, el proceso no rompe.
Cómo se deploya cada ambiente
Sección titulada «Cómo se deploya cada ambiente»# Una vez por máquinacp despliegue/.env.ejemplo despliegue/.env
# Cada vez que se arrancadocker compose --profile local up -d postgresdocker compose --profile tareas run --rm migraciones # corre las migracionesdocker compose up -dpnpm dev desde api/ también funciona contra el Postgres del compose, con
la variable AQUAZAKU_ENV=development en .env.
Preview
Sección titulada «Preview»Cada push a una PR dispara dos cosas:
- Vercel redesplega la URL del preview (
xxx-git-feature-usuario.vercel.app). - Railway (
staging) redesplega, y sustartCommandcorre:Ventana de terminal pnpm db:sync-preview && pnpm db:seed && pnpm startdb:sync-previewaplica las migraciones al schemapreview(el migrador leeAQUAZAKU_ENVdel entorno, así que sabe que va contrapreviewy no contrapublic) y corre la auditoría de permisos. Si la auditoría falla, el deploy de staging aborta —y queremos que aborte, porque un preview con el schema roto no debería pasar—. Después,db:seedsiembra el catálogo y el administrador inicial. El guard de T5 rechazadb:seedsiAQUAZAKU_ENV=production, así que es seguro incluirlo en elstartCommandde staging.
Cómo se entra al preview
Sección titulada «Cómo se entra al preview»Dos portones, uno detrás del otro, y el primero no es de Aquazaku.
1 · Vercel. Los deployments de preview están protegidos: la URL redirige a
vercel.com/sso-api antes de servir nada. No importa qué usuario de Aquazaku se
intente — no se llega ni a la pantalla de login.
Se pasa entrando a vercel.com con la cuenta del proyecto, en el mismo navegador, y volviendo a abrir el enlace. La protección se puede apagar en Project Settings → Deployment Protection, pero conviene dejarla: un preview con datos de prueba abierto a internet es superficie que no hace falta.
2 · Aquazaku. Recién ahí aparece el login de la app, y la contraseña de
producción no sirve: el preview vive en el schema preview, que tiene su
propia tabla users.
| Usuario | SEED_ADMIN_EMAIL del environment staging de Railway |
| Contraseña | SEED_ADMIN_PASSWORD del mismo environment |
| Primer ingreso | pide cambiarla: el seed deja mustChangePassword en true |
Y como el preview acumula, si alguien ya entró y la cambió, vale la que puso: nada la resetea salvo tirar el schema.
Producción
Sección titulada «Producción»El startCommand de Railway para producción es vacío: el container
arranca con el CMD ["pnpm", "start"] del Dockerfile. Las migraciones a
producción son un paso deliberado y separado.
El flujo de un deploy de producción es:
- Merge a
main. Vercel redesplegaapp.aquazaku.com; Railway redesplegaapi.aquazaku.com. - Railway arranca el server con el último build mergeado. El server
responde 200 en
/healthapenas el pool conecta. - Después del deploy, alguien corre las migraciones a mano:
El script anuncia a qué base y a qué schema va a apuntar antes de tocarlos. Si hay algo mal, el rollback es
Ventana de terminal DATABASE_MIGRATION_URL=... pnpm db:migrate:prodgit revertdel commit —las migraciones son idempotentes, así que re-aplicar no rompe nada.
Qué pasa cuando algo falla
Sección titulada «Qué pasa cuando algo falla»| Falla | Comportamiento | Qué hacer |
|---|---|---|
| Migración falla en deploy de staging | El deploy de Railway staging aborta. La URL del preview queda con la versión anterior. |
Corregir la migración, pushear, esperar el redespliegue. |
Migración falla en deploy de prod (pnpm db:migrate:prod) |
El comando aborta. Producción queda con el schema anterior; el server sigue corriendo con el código mergeado. | Revisar el log del comando. Si la migración nueva tiene un bug, revertir el commit. La app puede quedar sirviendo errores 500 hasta que se corrija. |
| Auditoría de permisos falla en staging | El deploy de staging aborta. | El log dice qué GRANT/REVOKE falta replicar. La causa típica es una migración nueva que olvidó su REVOKE. Se arregla en una migración siguiente. |
db:sync-preview falla en staging |
El deploy de staging aborta. Producción no se ve afectada —sus migraciones son separadas—. | Corregir y pushear. La próxima corrida del sync reintenta. |
| Seed falla en staging | El deploy de staging aborta. | El seed no toca prod — el problema es del script, no del schema. Corregir y pushear. |
Vercel preview levanta pero el login devuelve 403 |
Better Auth rechaza el Origin del preview. |
Confirmar que *.vercel.app está en trustedOrigins (ver T6 del plan). |
Pool de Postgres setea search_path incorrecto |
Los queries del preview leen datos de public. |
Verificar que la variable AQUAZAKU_ENV está bien seteada en Railway. Si está bien, revisar que el código del pool use connection: { search_path } — no options: '-c ...', que es libpq-style y falla silenciosamente con postgres.js. |
Cómo se hace un rollback
Sección titulada «Cómo se hace un rollback»Revert del commit. Railway y Vercel detectan el push y redesplegan. Las migraciones son idempotentes, así que re-aplicar el commit no rompe nada.
Si la migración nueva que vino en el commit revertido ya se corrió a
producción, revertir el código no la deshace. La app va a fallar al
acceder a la columna o tabla que la migración nueva creó. En ese caso, se
escribe una migración explícita que deshaga el cambio (ALTER TABLE ... DROP COLUMN ...) y se corre con pnpm db:migrate:prod.
DROP SCHEMA preview CASCADE borra el schema y todo lo que tenga adentro. Lo
que sigue funcionando:
publicqueda intacto.- El próximo deploy de staging lo recrea con
CREATE SCHEMA IF NOT EXISTSy vuelve a correr las migraciones desde cero.
Lo que se pierde: todo lo acumulado, no solo lo del último seed — el preview no se vacía en cada push. Eso es justamente lo que queremos: un preview roto se arregla tirándolo a la basura.
Permisos
Sección titulada «Permisos»Si una migración nueva rompe los REVOKE del schema preview, la auditoría
lo detecta y el deploy de staging aborta. La fix es una migración nueva con
los GRANT/REVOKE que faltan — siguiendo el patrón de 0015_audit_revoke,
que arregló un agujero pre-existente de 0001.
Cómo se verifica que funciona
Sección titulada «Cómo se verifica que funciona»Después de cualquier cambio al flujo de preview environments, una PR de prueba ejercita todo:
- Abrir una PR con un cambio trivial contra
main. - Esperar a que Vercel levante el preview y Railway termine el deploy de
staging. - Abrir la URL del preview. El log de Railway (
staging) tiene que mostrar la auditoría de permisos pasando (✓ audit_log en "preview" es append-only para aquazaku_app.). - Login con el admin del seed (
SEED_ADMIN_EMAIL). - Navegar un módulo. Si Better Auth rechaza con
403, falta el wildcard entrustedOrigins. - Mergear la PR. Vercel redesplega producción; Railway redesplega
api.aquazaku.comcon elstartCommandvacío — solo arranca el server. - Correr las migraciones a mano:
Ventana de terminal DATABASE_MIGRATION_URL=... pnpm db:migrate:prod - Desde Railway
staging, correrpnpm tsx scripts/auditar-permisos-preview.ts --schema=preview. Tiene que pasar.
Si los ocho pasos pasan, el ambiente entero está sano.
Lo que NO se hace acá
Sección titulada «Lo que NO se hace acá»- No se crean archivos por ambiente en el repo. Las variables viven en
los paneles de Railway, Vercel y
.envlocal. - No se mantiene un script que limpie el schema
previewperiódicamente. El reseteo con cada merge es suficiente. Si en algún momento deja de serlo, se agrega un cron — no se cambia la estrategia. - No se configura un dominio custom para los previews. La URL
*.vercel.appes fea, pero la wildcard entrustedOriginsla hace funcional sin agregar infra.
