PLAN DE MIGRACIÓN DE EMERGENCIA Y PORTABILIDAD DE BASE DE DATOS
🚨 Objetivo: Si Supabase, Cloudflare D1 o tu proveedor actual sufre una caída masiva, aumenta precios drásticamente o el cliente exige portabilidad a sus propios servidores (VPS / AWS / On-Premise), este runbook permite migrar el 100% de la base de datos (esquemas, datos, roles y autenticación) sin perder información y en menos de 30 minutos.
1. Estrategia de Portabilidad: Evitar el Vendor Lock-in
[REQUIRED] Aunque usemos Supabase o Cloudflare D1, el núcleo del sistema es PostgreSQL / SQLite estándar. Las reglas de portabilidad son:
- Las tablas de negocio viven en el esquema
public. - Las políticas RLS usan funciones SQL estándar sin depender de extensiones propietarias cerradas.
- Se mantiene una copia de seguridad diaria exportable en formato
.sql/.dump.
2. Migración Completa de Supabase a VPS Propio (PostgreSQL)
Paso 1: Exportación Segura de Datos con pg_dump
Para volcar la base de datos sin errores de permisos (omitiendo los esquemas internos propietarios de Supabase como supabase_admin o graphql):
# 1. Exportar solo el esquema público y extensiones compatibles
pg_dump -h db.<PROJECT_REF>.supabase.co -U postgres -d postgres \
--schema=public \
--no-owner \
--no-privileges \
--clean \
--if-exists \
-F c -b -v -f supabase_backup_public.dump
# 2. Exportar la tabla de autenticación (usuarios y contraseñas hasheadas)
pg_dump -h db.<PROJECT_REF>.supabase.co -U postgres -d postgres \
--table=auth.users \
--table=auth.identities \
--data-only \
--no-owner \
-F c -v -f supabase_auth_data.dumpPaso 2: Preparar el Nuevo Servidor PostgreSQL (VPS Hetzner / Contabo)
En el nuevo servidor VPS con Docker Compose:
# docker-compose.yml en el VPS
version: '3.8'
services:
postgres:
image: postgres:16-alpine
container_name: agency_postgres_prod
restart: always
environment:
POSTGRES_DB: app_production
POSTGRES_USER: agency_admin
POSTGRES_PASSWORD: ${DB_STRONG_PASSWORD}
volumes:
- /var/lib/postgresql/data:/var/lib/postgresql/data
ports:
- "127.0.0.1:5432:5432" # Solo accesible localmente por seguridadPaso 3: Restaurar en el Nuevo PostgreSQL
# 1. Restaurar el esquema y datos de negocio
pg_restore -h localhost -p 5432 -U agency_admin -d app_production \
--no-owner \
--no-privileges \
-v supabase_backup_public.dump
# 2. Verificar la integridad de las tablas
psql -h localhost -U agency_admin -d app_production -c "SELECT count(*) FROM users;"Paso 4: Cambio de DNS / Variable de Conexión en los Workers
- Actualizar la variable de entorno
DATABASE_URLoSUPABASE_URLen Cloudflare Workers / Frontend:bashnpx wrangler secret put DATABASE_URL # Ingresar: postgres://agency_admin:password@vps.tuagencia.com:5432/app_production?sslmode=require - Desplegar los workers actualizados. Latencia de corte: < 10 segundos.
3. Migración de Cloudflare D1 (SQLite Edge) a PostgreSQL / Turso
Si un proyecto en D1 necesita migrar a una base de datos relacional tradicional:
# 1. Exportar todo el contenido de D1 a un script SQL plano
npx wrangler d1 export indexgenius-content --remote --output=./d1_backup.sql
# 2. Convertir tipos SQLite (INTEGER/TEXT) a Postgres e importar:
psql -h localhost -U agency_admin -d app_production -f ./d1_backup.sql4. Estrategia de Migraciones Versionadas y Rollback (Up / Down)
[REQUIRED] En todo proyecto de producción, cada cambio estructural de base de datos debe tener su archivo de subida (.up.sql) y su contraparte de reversión (.down.sql):
supabase/migrations/
├── 20260814120000_create_orders_table.up.sql
└── 20260814120000_create_orders_table.down.sqlContenido de 20260814120000_create_orders_table.up.sql:
CREATE TABLE IF NOT EXISTS public.orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
amount_cents BIGINT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_orders_user_id ON public.orders(user_id);Contenido de 20260814120000_create_orders_table.down.sql (Rollback):
DROP TABLE IF EXISTS public.orders CASCADE;5. Checklist de Verificación Post-Migración
- [ ] Las tablas y registros coinciden en cantidad (
SELECT count(*) FROM table). - [ ] Las claves foráneas e índices secundarios están activos (
DB-002). - [ ] Las políticas RLS están activas y bloquean accesos no autorizados (
TENANT-001). - [ ] Las APIs y Workers responden con
200 OKen flujos de lectura y escritura. - [ ] El pool de conexiones (
Supavisor/PgBouncer) está balanceando las peticiones concurrentes.