# Application stack for the Jorge Cuadros platform: the NestJS API + the Next.js # web front-end. The two images are built + pushed by .gitea/workflows/build.yml: # git.mancinas.io/rmancinas/jorgecuadros-api # git.mancinas.io/rmancinas/jorgecuadros-web # # This stack does NOT ship MySQL or MinIO — those are their own stacks # (deploy/jorgecuadros-db.stack.yml, deploy/jorgecuadros-minio.stack.yml). The # API reaches them over the network via DATABASE_URL / S3_ENDPOINT, which point # at the db + minio stacks' published ingress ports on the swarm host. # # Target: Portainer local endpoint on cubex (3-node Swarm). PROD only. # Deploy with a stack env that supplies every ${VAR:?...} below — see # deploy/jorgecuadros-app.env.example for the full list. # # Statefulness: the API keeps uploaded Access files (ingest) and DB backups on # named volumes, which are node-local. So the API is pinned to the same node as # the db/minio stacks (node label jorgecuadros_db == true) — a reschedule would # otherwise start against empty ingest/backup volumes. The web tier is # stateless and floats freely. # # The web image is NOT URL-baked: the browser's API origin is injected at # runtime from API_ORIGIN (see apps/web/src/app/layout.tsx), so this same image # works for any deployment — set the URL here, not at build time. version: "3.8" services: api: image: git.mancinas.io/rmancinas/jorgecuadros-api:${APP_TAG:-latest} # Container label (not `deploy.labels`, which labels the swarm SERVICE). # deploy/scripts/pre-migrate-backup.mjs finds the container by this label to # run its pre-migrate mysqldump into the backup volume. labels: io.jorgecuadros.role: "api" environment: DATABASE_URL: ${DATABASE_URL:?DATABASE_URL must be set} SESSION_SECRET: ${SESSION_SECRET:?SESSION_SECRET must be set} # CORS: the public origin the browser loads the web app from. WEB_ORIGIN: ${WEB_ORIGIN:?WEB_ORIGIN must be set} PORT: "3001" INGEST_DIR: /data/ingest BACKUP_DIR: /data/backups MIGRATION_ENV: prod # Credentials the "Operaciones" screen runs mysqldump/mysql as. NOT the # application user: --single-transaction needs the global RELOAD privilege # and the app user has only ALL ON jorgecuadros.*, so every backup, sync # and re-import fails without this. Host/port/database still come from # DATABASE_URL — this only changes who logs in. See opsConn() in # apps/api/src/ops/ops.service.ts. OPS_DB_ADMIN_USER: ${OPS_DB_ADMIN_USER:-root} OPS_DB_ADMIN_PASSWORD: ${OPS_DB_ADMIN_PASSWORD:?OPS_DB_ADMIN_PASSWORD must be set} # Object storage — internal endpoint the API (server-side) uses to reach # the minio stack. Not browser-facing (downloads proxy through the API). S3_ENDPOINT: ${S3_ENDPOINT:?S3_ENDPOINT must be set} S3_BUCKET: ${S3_BUCKET:-jorgecuadros-documents} MINIO_ROOT_USER: ${MINIO_ROOT_USER:?MINIO_ROOT_USER must be set} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?MINIO_ROOT_PASSWORD must be set} # Outbound mail (SES) — runtime config, not a build-time CI secret. SES_REGION: ${SES_REGION:-} SES_FROM: ${SES_FROM:-} SES_FROM_NAME: ${SES_FROM_NAME:-} SES_ACCESS_KEY: ${SES_ACCESS_KEY:-} SES_SECRET_KEY: ${SES_SECRET_KEY:-} SES_CONFIGURATION_SET: ${SES_CONFIGURATION_SET:-} NOTIFICATION_ADMIN_EMAILS: ${NOTIFICATION_ADMIN_EMAILS:-} ports: - target: 3001 published: ${API_PORT:-3001} protocol: tcp mode: ingress volumes: - ingest_data:/data/ingest - backup_data:/data/backups deploy: replicas: 1 placement: constraints: - node.labels.jorgecuadros_db == true restart_policy: condition: any update_config: order: stop-first healthcheck: test: ["CMD-SHELL", "wget -qO- http://localhost:3001/health || exit 1"] interval: 15s timeout: 5s retries: 10 start_period: 30s web: image: git.mancinas.io/rmancinas/jorgecuadros-web:${APP_TAG:-latest} labels: io.jorgecuadros.role: "web" environment: # Public API URL the browser calls (injected at runtime, see layout.tsx). API_ORIGIN: ${API_ORIGIN:?API_ORIGIN must be set} ports: - target: 3000 published: ${WEB_PORT:-3000} protocol: tcp mode: ingress depends_on: - api deploy: replicas: 1 restart_policy: condition: any update_config: order: start-first healthcheck: test: ["CMD-SHELL", "wget -qO- http://localhost:3000/ >/dev/null 2>&1 || exit 1"] interval: 15s timeout: 5s retries: 10 start_period: 30s volumes: ingest_data: backup_data: