Files
recordalexia/docs/operacion-saas.md
Jaume Garriga Maestre 6b655634ca feat(saas): infraestructura de producción para la VM
Bloque C de la fase SaaS (ficheros; el despliegue se hace con el runbook):
- docker-compose.prod.yml: superficie mínima (solo el frontend en
  127.0.0.1:8089 para el nginx nativo), límites de memoria, sin seeder.
- deploy/nginx-recordalexia.conf: server block con cabeceras de seguridad,
  X-Forwarded-For para el rate-limiter y preparado para certbot.
- deploy/backup.sh: pg_dump diario comprimido, retención 14, copia externa
  vía rclone y detección de backups vacíos.
- .env.prod.example (y .env.prod en .gitignore).
- docs/operacion-saas.md: runbook con primer despliegue, checklist,
  restore ENSAYADO paso a paso, actualización y problemas conocidos.
2026-07-13 22:33:12 +02:00

4.8 KiB

recordaLexia · Runbook de operación (instancia pública)

Operación de la instancia SaaS en la VM (Ubuntu, Docker, nginx nativo). Escrito para poder ejecutarse dentro de 6 meses sin recordar nada.

0. Requisitos

  • VM con Docker + Compose v2 y nginx nativo (ya presentes en vnicjaume).
  • DNS: recordalexia.jaumegar.work (o el subdominio elegido) apuntando a la IP de la VM.
  • Credenciales SMTP de cualquier relay estándar (para el email de recuperación).
  • rclone configurado con un remoto para backups fuera de la VM (recomendado).

1. Primer despliegue

# 1) Código
git clone https://gitea.jaumegar.work/jaume/recordalexia.git
cd recordalexia

# 2) Variables (rellenar TODAS; DB_PASSWORD nueva y larga)
cp .env.prod.example .env.prod
nano .env.prod

# 3) Levantar el stack (build en la propia VM)
docker compose -f docker-compose.prod.yml --env-file .env.prod up -d --build

# 4) Comprobar salud
docker compose -f docker-compose.prod.yml --env-file .env.prod ps
curl -s http://127.0.0.1:8089/ | head -c 200   # el frontend responde en loopback

# 5) Nginx + TLS
sudo cp deploy/nginx-recordalexia.conf /etc/nginx/sites-available/recordalexia
sudo ln -s /etc/nginx/sites-available/recordalexia /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d recordalexia.jaumegar.work

# 6) Humo: abrir https://recordalexia.jaumegar.work, registrar una familia de
#    prueba, pedir recuperación de contraseña y comprobar que LLEGA EL EMAIL.
#    Después borrar la familia desde Cuenta → Borrar cuenta.

Checklist post-despliegue:

  • https://… carga y Chrome ofrece «Instalar app» (o Añadir a pantalla de inicio).
  • NO existe la familia demo (el seeder queda apagado en prod).
  • El email de recuperación llega (si no: revisar SMTP_* y el log del backend).
  • Cabeceras: curl -sI https://… | grep -iE "nosniff|frame-ancestors|referrer".
  • Alta del dominio en uptime-kuma.
  • Backup + RESTORE ensayado (sección 3). Sin esto no se abre el registro a terceros.

2. Backups

chmod +x deploy/backup.sh
crontab -e
# 03:30 Europe/Madrid, log en el home:
30 3 * * * /home/ubuntu/recordalexia/deploy/backup.sh >> $HOME/recordalexia-backup.log 2>&1
  • Local: ~/backups/recordalexia/ (retención 14).
  • Externa: define RCLONE_REMOTE en .env.prod (p. ej. b2:recordalexia-backups).
  • Vigila el log: un backup de menos de 1 KB se marca como ERROR.

3. Restauración (ENSAYAR antes de abrir al público)

El ensayo restaura sobre una BD limpia SIN tocar la de producción:

cd ~/recordalexia
LAST=$(ls -1t ~/backups/recordalexia/recordalexia-*.sql.gz | head -1)

# BD de ensayo temporal (sin volumen persistente ni puertos publicados)
docker run -d --name restore-test \
  -e POSTGRES_DB=recordalexia -e POSTGRES_USER=recordalexia \
  -e POSTGRES_PASSWORD=ensayo postgres:16-alpine
sleep 5
gunzip -c "$LAST" | docker exec -i restore-test psql -U recordalexia recordalexia

# Verificar: las familias están
docker exec restore-test psql -U recordalexia recordalexia -tA \
  -c "SELECT count(*) FROM family;"

docker rm -f restore-test

Restauración REAL (pérdida de la BD de producción):

docker compose -f docker-compose.prod.yml --env-file .env.prod stop backend
gunzip -c "$LAST" | docker compose -f docker-compose.prod.yml --env-file .env.prod \
  exec -T postgres psql -U recordalexia recordalexia
docker compose -f docker-compose.prod.yml --env-file .env.prod start backend

4. Actualizar la aplicación

cd ~/recordalexia && git pull
docker compose -f docker-compose.prod.yml --env-file .env.prod up -d --build

Liquibase aplica las migraciones al arrancar. Si el backend no levanta, sus logs dicen exactamente qué changeset falló: docker logs recordalexia-prod-backend | grep -iA3 liquibase.

5. Problemas conocidos

Síntoma Causa probable Arreglo
413 al subir algo límite del nginx ya hay client_max_body_size en el server block y 64m global
No llegan emails SMTP_* mal o puerto bloqueado docker logs recordalexia-prod-backend | grep -i mail; probar el relay con openssl s_client -starttls smtp -connect $SMTP_HOST:587
429 al entrar freno anti fuerza bruta esperar el Retry-After; es el comportamiento diseñado
Certificado caducado certbot sudo certbot renew --dry-run; el timer de systemd debería renovarlo solo
La PWA no se actualiza SW cacheado el SW comprueba ngsw.json al abrir; forzar con recarga o cerrar/abrir la app

6. Rotación de credenciales

  • DB: cambiar en .env.prod + ALTER USER recordalexia WITH PASSWORD '…' en el contenedor postgres + up -d del backend.
  • SMTP: cambiar en .env.prod + up -d backend.
  • PIN/contraseña de una familia: lo hace la propia familia desde la app (Cuenta o «¿Has olvidado la contraseña?»).