Fase 5 (migracion de datos): - api/scripts/migrate-sqlite-to-postgres.ts: ETL unico SQLite -> Postgres. Pre-flight (CURP/RFC duplicados case-insensitive, FKs huerfanas, tenant_id invalido, fechas mal formateadas) -> carga por base/esquema con credenciales _owner (setval de secuencias, OVERRIDING SYSTEM VALUE, ON CONFLICT DO NOTHING idempotente) -> verificacion de conteos y sumas de dinero con tolerancia. Probado end-to-end contra un dataset SQLite legacy sintetico y un Postgres limpio (solo schema+catalogos, sin contexto dev): 100% de las filas migradas, sumas de dinero exactas, snapshot de uploaded_by/created_by resuelto correctamente. - db/RUNBOOK-corte.md: checklist go/no-go para el corte por ambiente. - Fix de bug real encontrado al probar: sin --context-filter explicito, Liquibase corre TAMBIEN los changesets context=dev (comportamiento por defecto, no "modo seguro") -- documentado en db/README.md con el ejemplo correcto (--context-filter='!dev' para staging/produccion). Fase 5b: patron de compensacion para createTenant ya resuelto en el rewrite de saas.ts (fase 3/4). Fase 6 (backups): db/backups/ con plantillas pgBackRest por base, politica de retencion, nota de persistencia minima de Redis (RDB, sin retencion de negocio), checklist de simulacro de restauracion mensual, y la validacion pendiente de si Coolify permite WAL archiving antes de comprometerse a PITR real. Fase 7 (limpieza y CI): - Dockerfile.api simplificado (sin JRE/Liquibase/FFI). Dockerfile.migrate y Dockerfile.provision nuevos, de un solo uso, para el paso explicito de deploy (nunca sirven trafico). - docker-compose.yml: postgres+redis+provision+migrate para dev local end-to-end; api ya no arranca hasta que migrate termina bien. - docs/coolify.md y .env.example actualizados al modelo de 2 bases + Redis + Contabo. - api/schema.sql eliminado (desincronizado, competia con Liquibase como fuente de verdad). - .github/workflows/ci.yml: Postgres+Redis de servicio, aprovisiona roles/ACLs, dry-run de Liquibase (updateSQL) antes de aplicar, verify-isolation.sh, deno check + deno test, build de los dos frontends. Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx> |
||
|---|---|---|
| .. | ||
| pgbackrest-panels_platform.conf | ||
| pgbackrest-panels_product.conf | ||
| README.md | ||
| redis-persistence.md | ||
| restore-drill-checklist.md | ||
Backups y continuidad (Fase 6)
0. Validar antes de diseñar el resto
Coolify despliega Postgres como un recurso "managed" que en realidad es un
contenedor postgres:16 estándar con volumen persistente. Hay que
confirmar, para el ambiente real, si se puede:
- Montar un
postgresql.confcustom (paraarchive_mode,archive_command,wal_level), y - Ejecutar un sidecar/proceso adicional (
pgbackrest) con acceso al mismo volumen de datos y a WAL.
- Si sí se puede: seguir esta guía con pgBackRest (PITR real).
- Si no se puede (Coolify no expone esos hooks en el plan/versión
usada): el respaldo queda limitado a los
pg_dumpprogramados que Coolify ya ofrece -- el RPO pasa a ser "la frecuencia del dump", no segundos. En ese caso, para producción, evaluar auto-hospedar Postgres (contenedor propio, fuera del recurso "managed" de Coolify) solo para tener control de WAL -- es la única forma de bajar el RPO por debajo de la frecuencia del dump.
Esta decisión condiciona todo lo demás; no asumir que WAL archiving va a funcionar sin probarlo primero contra el Coolify real del ambiente.
1. Qué se respalda y con qué política
| Qué | Herramienta | Frecuencia | Retención | RPO objetivo |
|---|---|---|---|---|
panels_platform (prod) |
pgBackRest (o pg_dump si no hay WAL) |
full diario + diferencial c/6h + WAL continuo | 30 días | segundos (con WAL) / 24h (solo dump) |
panels_product (prod) |
igual que arriba | igual | 30 días | igual |
panels_platform/panels_product (staging/dev) |
pg_dump |
full diario | 7 días | 24h |
| Archivos (Contabo, Fase 4c) | versionado de objetos del bucket + backup del bucket | continuo (versionado) | igual que la BD | ver nota de coordinación abajo |
| Redis | RDB snapshot | al reiniciar/periódico | ninguna (no es fuente de verdad) | N/A -- ver db/backups/redis-persistence.md |
Respaldar SIEMPRE ambas bases. Es fácil configurar el backup de
panels_product (donde está "todo el negocio") y olvidar panels_platform
(que tiene el registro de TODOS los tenants) -- el día del incidente ahí es
cuando se descubre.
2. pgBackRest (si Coolify lo permite)
Ver pgbackrest-panels_platform.conf y
pgbackrest-panels_product.conf --
plantillas, una stanza por base de datos (no por esquema: panels_product
es una sola stanza aunque tenga los esquemas iam/core adentro).
Pasos (por base):
# 1. postgresql.conf del contenedor
wal_level = replica
archive_mode = on
archive_command = 'pgbackrest --stanza=panels_platform archive-push %p'
archive_timeout = 60 # cap de RPO a 60s incluso en bases con poca escritura
# 2. Crear la stanza una vez
pgbackrest --stanza=panels_platform --log-level-console=info stanza-create
# 3. Verificar que el archiving realmente funciona ANTES de confiar en él
pgbackrest --stanza=panels_platform check
# 4. Backups programados (cron / scheduler de Coolify)
pgbackrest --stanza=panels_platform --type=full backup # 1x/semana
pgbackrest --stanza=panels_platform --type=diff backup # cada 6h
Repetir con --stanza=panels_product y su propio archive_command.
Point-in-time recovery
pgbackrest --stanza=panels_platform --type=time \
--target="2026-09-01 14:00:00-06" --target-action=promote restore
3. Si NO hay WAL archiving (solo pg_dump)
pg_dump --format=custom --file=panels_platform-$(date +%Y%m%d).dump "$DATABASE_URL_PLATFORM_OWNER"
pg_dump --format=custom --file=panels_product-$(date +%Y%m%d).dump "$DATABASE_URL_CORE_OWNER"
--format=custom permite restore paralelo (pg_restore -j4) y restore
selectivo por tabla si algún día hace falta. Subir los .dump a un bucket
S3-compatible (puede ser el mismo de Contabo, con prefijo/bucket distinto
al de documentos) con retención por lifecycle policy del bucket.
4. Coordinación con el respaldo de archivos (Fase 4c)
Un restore de Postgres a un punto en el tiempo, sin restaurar los archivos
de Contabo al MISMO punto, deja filas (documents.storage_name) apuntando
a objetos que ya no existen o que cambiaron. Política:
- Activar versionado de objetos en el bucket de Contabo.
- Si se hace un PITR de Postgres a una hora
T, documentar que los archivos subidos/borrados después deTpueden quedar huérfanos o inconsistentes -- es una ventana de inconsistencia aceptada, no un bug a corregir en caliente durante el incidente.
5. Redis
Ver redis-persistence.md. Resumen: solo RDB
para evitar deslogueo masivo, sin retención de negocio, fuera del alcance
del simulacro de restauración de abajo.
6. Simulacro de restauración (mensual, obligatorio)
Ver restore-drill-checklist.md. Un
backup que nunca se probó restaurar no es un backup, es una esperanza.