7.0 KiB
7.0 KiB
7. Despliegue
7.1 Docker
docker compose up -d --build # todo: web en http://localhost:8080
docker compose up -d postgres mongo redis # solo infraestructura
docker compose logs -f api
docker compose down # parar (-v borra los datos)
| Servicio | Imagen | Puerto | Función |
|---|---|---|---|
web |
task-stream-web (nginx) |
8080 | SPA y proxy de /api y /graphql (incluido el WebSocket) hacia la API |
api |
task-stream-api (Node 22) |
3000 | API NestJS |
postgres |
postgres:17-alpine | 5432 | Event store y lookups |
mongo |
mongo:8 | 27017 | Read model |
redis |
redis:7-alpine (AOF) | 6379 | Stream de eventos, pub/sub y caché de sesiones |
Todos tienen healthcheck; web espera a que api esté sana, y api a las tres bases de datos.
Imágenes
El Dockerfile tiene dos destinos que comparten la instalación de dependencias:
- Instalación:
npm cidel workspace.@pi/uiviene del Nexus privado; el token se pasa como build secret (secrets.npmrc→~/.npmrcendocker-compose.yml) y nunca queda en una capa. api:nx run api:prunegenera el bundle con unpackage.jsony un lockfile podados a partir deapps/api/package.json. La imagen final solo instala esas dependencias (sin Angular ni paquetes privados). Si la API empieza a usar un paquete nuevo en tiempo de ejecución, hay que añadirlo aapps/api/package.json.web: build de producción de Angular servido por nginx (apps/web/nginx.conf): fallback de rutas aindex.html, caché larga para los bundles con hash y sin caché paraindex.html.
7.2 Variables de entorno de la API
| Variable | Por defecto | Descripción |
|---|---|---|
PORT |
3000 |
Puerto HTTP |
NODE_ENV |
development |
En production no se devuelven stacktraces |
POSTGRES_URL |
— (obligatoria) | Event store y lookups |
POSTGRES_SYNCHRONIZE |
false |
Crea las tablas desde las entidades. Cómodo en desarrollo; en producción, migraciones |
MONGO_URL |
— (obligatoria) | Read model |
REDIS_URL |
— (obligatoria) | Stream, pub/sub y caché |
EVENT_STREAM_KEY |
task-stream:events |
Clave del Redis Stream |
EVENT_STREAM_MAX_LENGTH |
1000000 |
Longitud aproximada que se conserva en el stream |
EVENTS_BRIDGE_ENABLED |
true |
Si esta instancia proyecta eventos hacia Mongo (ver escalado) |
PIREON_BASE_URL |
https://oauth.pireon.dev |
Servidor de identidad |
PIREON_CLIENT_ID / PIREON_CLIENT_SECRET |
— | Cliente OAuth para el login desde la web |
PIREON_SCOPES |
users:read |
Scopes que se piden al iniciar sesión |
PIREON_PASSWORD_GRANT |
true |
Permite el login con email y contraseña |
PIREON_WEBHOOK_SECRET |
— | Activa y firma los webhooks de Pireon |
PIREON_TIMEOUT_MS |
5000 |
Timeout de las llamadas a Pireon |
AUTH_SESSION_TTL_SECONDS |
60 |
Cuánto se confía en un token validado antes de volver a preguntar a Pireon |
GRAPHQL_PLAYGROUND |
true |
GraphiQL e introspección |
GRAPHQL_SCHEMA_FILE |
apps/api/schema.gql |
Dónde escribir el SDL (vacío = no escribir; así está en Docker) |
La web no tiene configuración de entorno: llama a la API en su mismo origen (/api, /graphql).
7.3 Escalado
- API: es stateless (sesiones en Redis, subscriptions por Redis pub/sub), así que admite varias réplicas detrás
del balanceador. El WebSocket necesita que el balanceador permita
Upgrade. - Relay: cada instancia publica eventos al stream;
FOR UPDATE SKIP LOCKEDevita duplicados. - Proyecciones: el
EventsBridgeprocesa los eventos en orden. Lo más sencillo es dejarlo activo en una sola instancia (EVENTS_BRIDGE_ENABLED=true) y desactivarlo en las demás. Varias instancias en el mismo consumer group se reparten los eventos y pierden el orden entre ellos.
7.4 Antes de producción
- Sustituir
POSTGRES_SYNCHRONIZEpor migraciones de TypeORM. - Registrar el cliente OAuth y las redirect URIs de producción en Pireon.
- Servir por HTTPS (la web usa
wss://automáticamente cuando la página va por HTTPS). - Revisar
GRAPHQL_PLAYGROUND(introspección) según el entorno. - Copias de seguridad de PostgreSQL: es la fuente de verdad.
7.5 Reconstruir el read model
MongoDB solo contiene proyecciones, así que se puede regenerar desde el event store:
- Para la API (o desactiva el bridge en todas las instancias).
- Borra la base de lectura (
taskstream_read) salvo la colecciónusers, que no sale de los eventos sino de Pireon. - Marca los eventos como no publicados:
UPDATE events SET published_at = NULL; - Arranca la API: el relay vuelve a enviar todos los eventos, en orden, y las proyecciones reconstruyen las vistas. Las notificaciones en tiempo real se reenvían también, sin efecto si no hay clientes conectados.