Files
task-stream/docs/07-despliegue.md
2026-10-03 13:40:26 +02:00

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 ci del workspace. @pi/ui viene del Nexus privado; el token se pasa como build secret (secrets.npmrc → ~/.npmrc en docker-compose.yml) y nunca queda en una capa.
  • api: nx run api:prune genera el bundle con un package.json y un lockfile podados a partir de apps/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 a apps/api/package.json.
  • web: build de producción de Angular servido por nginx (apps/web/nginx.conf): fallback de rutas a index.html, caché larga para los bundles con hash y sin caché para index.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 LOCKED evita duplicados.
  • Proyecciones: el EventsBridge procesa 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_SYNCHRONIZE por 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:

  1. Para la API (o desactiva el bridge en todas las instancias).
  2. Borra la base de lectura (taskstream_read) salvo la colección users, que no sale de los eventos sino de Pireon.
  3. Marca los eventos como no publicados: UPDATE events SET published_at = NULL;
  4. 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.