Skip to content
On this page

💾 Backup System - Gunei ERP (Enterprise Architecture)

Sistema automatizado de backups para PostgreSQL Shared con verificación de integridad, políticas de retención y soporte multi-ambiente.

Versión 2.1 - PostgreSQL Shared + Multi-Ambiente


📋 Tabla de Contenidos


⚡ Quick Reference

Comandos Más Usados

bash
# === BACKUP ===
# Backup de ambos ambientes
/root/scripts/backup-postgres.sh

# Backup solo staging
docker exec postgres-shared pg_dump -U gunei_staging_user gunei_erp_staging | gzip > backup_staging_$(date +%Y%m%d).sql.gz

# Backup solo production
docker exec postgres-shared pg_dump -U gunei_prod_user gunei_erp_production | gzip > backup_production_$(date +%Y%m%d).sql.gz

# === RESTORE ===
# Restore staging
gunzip -c backup_staging.sql.gz | docker exec -i postgres-shared psql -U gunei_staging_user -d gunei_erp_staging

# Restore production
gunzip -c backup_production.sql.gz | docker exec -i postgres-shared psql -U gunei_prod_user -d gunei_erp_production

# === VERIFICACIÓN ===
# Ver backups recientes
ls -lht /opt/infrastructure/postgres/backups/staging/ | head -5
ls -lht /opt/infrastructure/postgres/backups/production/ | head -5

# Ver logs
tail -50 /var/log/gunei-backups.log

# Dashboard
/root/scripts/backup-dashboard.sh

Conexión a PostgreSQL

bash
# Desde el host (puerto 5433)
psql -h localhost -p 5433 -U postgres
psql -h localhost -p 5433 -U gunei_staging_user -d gunei_erp_staging
psql -h localhost -p 5433 -U gunei_prod_user -d gunei_erp_production

# Desde container (puerto 5432 interno)
docker exec -it postgres-shared psql -U postgres
docker exec -it postgres-shared psql -U gunei_staging_user -d gunei_erp_staging
docker exec -it postgres-shared psql -U gunei_prod_user -d gunei_erp_production

Ubicaciones Clave

RecursoPath
Scripts/root/scripts/
Backups Staging/opt/infrastructure/postgres/backups/staging/
Backups Production/opt/infrastructure/postgres/backups/production/
Logs/var/log/gunei-backups.log
PostgreSQL Data/opt/infrastructure/postgres/data/

Timezone

Todos los timestamps de backups usan hora Argentina:

yaml
TZ=America/Argentina/Buenos_Aires

Nomenclatura de archivos:

backup_staging_YYYYMMDD_HHMMSS.sql.gz    # Hora Argentina
backup_production_YYYYMMDD_HHMMSS.sql.gz # Hora Argentina

Verificación:

bash
# Ver timezone del contenedor PostgreSQL
docker exec postgres-shared date

# Ver timestamp de últimos backups
ls -lh /opt/infrastructure/postgres/backups/staging/ | head -5
ls -lh /opt/infrastructure/postgres/backups/production/ | head -5

Implicaciones:

  • Cron ejecuta en hora local del servidor (Argentina)
  • Nombres de archivos reflejan hora Argentina
  • Logs de backup con timestamps en hora local

🎯 Descripción General

Objetivos

  • Backups diarios automáticos de PostgreSQL Shared (ambos ambientes)
  • Verificación de integridad de cada backup
  • Retención inteligente (diarios, semanales, mensuales)
  • Alertas de espacio en disco
  • Proceso de restore documentado y probado por ambiente
  • Disaster recovery para staging y production

Características

  • Multi-Ambiente: Backup separado de gunei_erp_staging y gunei_erp_production
  • Compresión: gzip para ahorrar espacio (~70% reducción)
  • Metadata: Timestamp, tamaño, checksum, ambiente
  • Verificación: Test de restore automático post-backup
  • Cleanup: Retención de 30 días con semanales/mensuales extra
  • Logs: Registro completo de todas las operaciones

Ubicación

bash
# Directorio de backups (organizado por ambiente)
/opt/infrastructure/postgres/backups/
├── staging/
│   ├── backup_staging_20260112_020000.sql.gz
│   ├── backup_staging_20260112_020000.sql.gz.meta
│   └── ...
└── production/
    ├── backup_production_20260112_020000.sql.gz
    ├── backup_production_20260112_020000.sql.gz.meta
    └── ...

# Scripts de backup
/root/scripts/
├── backup-postgres.sh      # Backup principal (ambos ambientes)
├── backup-db-staging.sh    # Backup solo staging
├── backup-db-production.sh # Backup solo production
├── restore-backup.sh       # Restore (especificar ambiente)
├── verify-backup.sh        # Verificación de integridad
├── cleanup-backups.sh      # Limpieza con retención
└── check-disk-space.sh     # Monitoreo de disco

🏗️ Arquitectura de Backups

PostgreSQL Shared

PostgreSQL Shared (postgres-shared:5433)
├── gunei_erp_staging
│   ├── Usuario: gunei_staging_user
│   └── Backup: /opt/infrastructure/postgres/backups/staging/
└── gunei_erp_production
    ├── Usuario: gunei_prod_user
    └── Backup: /opt/infrastructure/postgres/backups/production/

Flujo de Backup

Cron (02:00 AM)

backup-postgres.sh

   ├─→ backup-db-staging.sh
   │      ↓
   │   pg_dump gunei_erp_staging
   │      ↓
   │   Compress → Metadata → Log

   └─→ backup-db-production.sh

       pg_dump gunei_erp_production

       Compress → Metadata → Log

Databases y Usuarios

AmbienteDatabaseUsuarioPassword Location
Staginggunei_erp_staginggunei_staging_user/opt/apps/gunei-erp/backend/staging/.env
Productiongunei_erp_productiongunei_prod_user/opt/apps/gunei-erp/backend/production/.env

⏰ Schedule Automático

Cron Configuration

bash
# Ver configuración actual
crontab -l

# Editar si es necesario
crontab -e

Schedule Completo (Actualizado)

bash
# 1. Backup diario a las 2 AM (ambos ambientes)
0 2 * * * /root/scripts/backup-postgres.sh >> /var/log/gunei-backups.log 2>&1

# 2. Verificación a las 3 AM (1 hora después del backup)
0 3 * * * /root/scripts/verify-backup.sh >> /var/log/gunei-backups.log 2>&1

# 3. Cleanup semanal (domingos a las 4 AM)
0 4 * * 0 /root/scripts/cleanup-backups.sh >> /var/log/gunei-backups.log 2>&1

# 4. Check de espacio diario a las 5 AM
0 5 * * * /root/scripts/check-disk-space.sh >> /var/log/gunei-health.log 2>&1

Timeline Típico

02:00 → Backup inicia (staging + production)
02:05 → Backup staging completo (5 minutos)
02:10 → Backup production completo (5 minutos)
02:10 → Compresión y metadata
03:00 → Verificación de integridad inicia
03:05 → Verificación staging completa
03:10 → Verificación production completa
04:00 → Cleanup (solo domingos)
05:00 → Check de espacio en disco

Duración total: ~15-20 minutos para ambos ambientes


🔧 Scripts de Backup

1. backup-postgres.sh (Principal)

Propósito: Backup automático de ambos ambientes

bash
#!/bin/bash
# Backup completo de PostgreSQL Shared (staging + production)

# Uso
/root/scripts/backup-postgres.sh

# O especificar solo un ambiente
/root/scripts/backup-postgres.sh staging
/root/scripts/backup-postgres.sh production

Proceso:

  1. Detecta ambientes disponibles
  2. Para cada ambiente:
    • Genera timestamp único
    • Ejecuta pg_dump con usuario correcto
    • Comprime con gzip (nivel 9)
    • Guarda metadata (tamaño, checksum, ambiente)
    • Registra en log
  3. Reporta resultado consolidado

Ejemplo de implementación:

bash
#!/bin/bash
# /root/scripts/backup-postgres.sh

BACKUP_DIR="/opt/infrastructure/postgres/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/var/log/gunei-backups.log"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

backup_environment() {
    ENV=$1
    
    if [ "$ENV" = "staging" ]; then
        DB_NAME="gunei_erp_staging"
        DB_USER="gunei_staging_user"
    elif [ "$ENV" = "production" ]; then
        DB_NAME="gunei_erp_production"
        DB_USER="gunei_prod_user"
    else
        log "[ERROR] Invalid environment: $ENV"
        return 1
    fi
    
    BACKUP_FILE="${BACKUP_DIR}/${ENV}/backup_${ENV}_${TIMESTAMP}.sql"
    BACKUP_GZ="${BACKUP_FILE}.gz"
    META_FILE="${BACKUP_GZ}.meta"
    
    log "[INFO] Starting backup of $DB_NAME ($ENV)"
    
    # Backup
    docker exec postgres-shared pg_dump -U "$DB_USER" "$DB_NAME" > "$BACKUP_FILE"
    
    if [ $? -ne 0 ]; then
        log "[ERROR] Backup failed for $ENV"
        return 1
    fi
    
    # Compress
    gzip -9 "$BACKUP_FILE"
    
    # Metadata
    SIZE=$(stat -f%z "$BACKUP_GZ" 2>/dev/null || stat -c%s "$BACKUP_GZ")
    CHECKSUM=$(sha256sum "$BACKUP_GZ" | awk '{print $1}')
    
    cat > "$META_FILE" << EOF
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
ENVIRONMENT=$ENV
DATABASE=$DB_NAME
SIZE=$SIZE
CHECKSUM=$CHECKSUM
EOF
    
    log "[INFO] Backup completed: $ENV ($(du -h $BACKUP_GZ | cut -f1))"
    return 0
}

# Main
log "[INFO] === PostgreSQL Shared Backup Started ==="

if [ -n "$1" ]; then
    # Backup solo el ambiente especificado
    backup_environment "$1"
else
    # Backup ambos ambientes
    backup_environment "staging"
    backup_environment "production"
fi

log "[INFO] === PostgreSQL Shared Backup Completed ==="

Output:

bash
# Nombres de archivos
/opt/infrastructure/postgres/backups/staging/backup_staging_20260112_020000.sql.gz
/opt/infrastructure/postgres/backups/staging/backup_staging_20260112_020000.sql.gz.meta

/opt/infrastructure/postgres/backups/production/backup_production_20260112_020000.sql.gz
/opt/infrastructure/postgres/backups/production/backup_production_20260112_020000.sql.gz.meta

Metadata incluida:

bash
TIMESTAMP=2026-01-12 02:00:00
ENVIRONMENT=staging
DATABASE=gunei_erp_staging
SIZE=45123456  # bytes
CHECKSUM=a1b2c3d4e5f6...

Logs:

[2026-01-12 02:00:00] [INFO] === PostgreSQL Shared Backup Started ===
[2026-01-12 02:00:00] [INFO] Starting backup of gunei_erp_staging (staging)
[2026-01-12 02:04:45] [INFO] Backup completed: staging (12.3 MB)
[2026-01-12 02:05:00] [INFO] Starting backup of gunei_erp_production (production)
[2026-01-12 02:09:30] [INFO] Backup completed: production (8.7 MB)
[2026-01-12 02:09:30] [INFO] === PostgreSQL Shared Backup Completed ===

2. backup-db-staging.sh / backup-db-production.sh

Propósito: Backup de un ambiente específico

bash
#!/bin/bash
# Backup solo staging
/root/scripts/backup-db-staging.sh

# Backup solo production
/root/scripts/backup-db-production.sh

Estos scripts son wrappers que llaman a backup-postgres.sh con el ambiente específico:

bash
#!/bin/bash
# /root/scripts/backup-db-staging.sh
/root/scripts/backup-postgres.sh staging
bash
#!/bin/bash
# /root/scripts/backup-db-production.sh
/root/scripts/backup-postgres.sh production

3. restore-backup.sh (Actualizado)

Propósito: Restaurar base de datos desde backup especificando ambiente

bash
#!/bin/bash
# Restore de backup con validación

# Uso
/root/scripts/restore-backup.sh <backup-file> <environment> [target-db]

# Ejemplos:
# Restore staging a su database original
/root/scripts/restore-backup.sh /opt/infrastructure/postgres/backups/staging/backup_staging_20260112_020000.sql.gz staging

# Restore production a su database original (CUIDADO!)
/root/scripts/restore-backup.sh /opt/infrastructure/postgres/backups/production/backup_production_20260112_020000.sql.gz production

# Restore staging a database temporal para testing
/root/scripts/restore-backup.sh /opt/infrastructure/postgres/backups/staging/backup_staging_20260112_020000.sql.gz staging gunei_erp_test

Proceso:

  1. Valida que el archivo existe
  2. Verifica checksum contra metadata
  3. Determina usuario correcto según ambiente
  4. Descomprime backup
  5. Crea base de datos destino (si no existe)
  6. Ejecuta psql con usuario correcto
  7. Valida integridad post-restore
  8. Registra resultado

Ejemplo de implementación:

bash
#!/bin/bash
# /root/scripts/restore-backup.sh

BACKUP_FILE=$1
ENVIRONMENT=$2
TARGET_DB=$3

if [ -z "$BACKUP_FILE" ] || [ -z "$ENVIRONMENT" ]; then
    echo "Usage: $0 <backup-file> <environment> [target-db]"
    exit 1
fi

# Determinar database y usuario según ambiente
if [ "$ENVIRONMENT" = "staging" ]; then
    DEFAULT_DB="gunei_erp_staging"
    DB_USER="gunei_staging_user"
elif [ "$ENVIRONMENT" = "production" ]; then
    DEFAULT_DB="gunei_erp_production"
    DB_USER="gunei_prod_user"
else
    echo "[ERROR] Invalid environment: $ENVIRONMENT"
    exit 1
fi

# Usar target-db si se especificó, sino usar default
DB_NAME=${TARGET_DB:-$DEFAULT_DB}

echo "[INFO] Restoring to $DB_NAME ($ENVIRONMENT)"

# Descomprimir
TMP_FILE="/tmp/restore_${ENVIRONMENT}_$$.sql"
gunzip -c "$BACKUP_FILE" > "$TMP_FILE"

# Restore
docker exec -i postgres-shared psql -U "$DB_USER" -d "$DB_NAME" < "$TMP_FILE"

if [ $? -eq 0 ]; then
    echo "[INFO] Restore completed successfully"
else
    echo "[ERROR] Restore failed"
    rm "$TMP_FILE"
    exit 1
fi

# Cleanup
rm "$TMP_FILE"

Logs:

[2026-01-12 10:30:00] [INFO] Starting restore from backup_staging_20260112_020000.sql.gz
[2026-01-12 10:30:01] [INFO] Environment: staging
[2026-01-12 10:30:01] [INFO] Target database: gunei_erp_staging
[2026-01-12 10:30:01] [INFO] Checksum verified: OK
[2026-01-12 10:30:05] [INFO] Decompressing backup...
[2026-01-12 10:30:15] [INFO] Restoring data...
[2026-01-12 10:35:42] [INFO] Restore completed successfully

4. verify-backup.sh (Actualizado)

Propósito: Verificar integridad de los backups de ambos ambientes

bash
#!/bin/bash
# Verificación automática post-backup

# Uso (automático vía cron)
/root/scripts/verify-backup.sh

# O manual para backup específico
/root/scripts/verify-backup.sh /opt/infrastructure/postgres/backups/staging/backup_staging_20260112_020000.sql.gz staging

Proceso:

  1. Identifica últimos backups de cada ambiente
  2. Para cada backup:
    • Verifica checksum
    • Intenta descomprimir (test)
    • Crea DB temporal de prueba
    • Ejecuta restore completo
    • Valida integridad de datos
    • Limpia DB temporal
  3. Registra resultado consolidado

Validaciones por ambiente:

  • Checksum coincide con metadata
  • Archivo descomprime correctamente
  • psql completa sin errores
  • Tablas esperadas existen
  • Conteo de filas razonable
  • Índices y constraints presentes

Logs exitosos:

[2026-01-12 03:00:00] [INFO] === Backup Verification Started ===
[2026-01-12 03:00:00] [INFO] Verifying staging: backup_staging_20260112_020000.sql.gz
[2026-01-12 03:00:01] [INFO] Checksum: OK
[2026-01-12 03:00:05] [INFO] Decompression test: OK
[2026-01-12 03:00:10] [INFO] Restore test started
[2026-01-12 03:04:32] [INFO] Restore test: OK
[2026-01-12 03:04:33] [INFO] ✅ Staging backup verified successfully
[2026-01-12 03:04:40] [INFO] Verifying production: backup_production_20260112_020000.sql.gz
[2026-01-12 03:04:41] [INFO] Checksum: OK
[2026-01-12 03:04:45] [INFO] Decompression test: OK
[2026-01-12 03:04:50] [INFO] Restore test started
[2026-01-12 03:08:12] [INFO] Restore test: OK
[2026-01-12 03:08:13] [INFO] ✅ Production backup verified successfully
[2026-01-12 03:08:13] [INFO] === All Backups Verified ===

5. cleanup-backups.sh (Actualizado)

Propósito: Aplicar política de retención por ambiente

bash
#!/bin/bash
# Limpieza con retención inteligente

# Uso (automático domingos vía cron)
/root/scripts/cleanup-backups.sh

# O forzar limpieza manual
/root/scripts/cleanup-backups.sh --force

# Limpiar solo un ambiente
/root/scripts/cleanup-backups.sh staging
/root/scripts/cleanup-backups.sh production

Política de retención (por ambiente):

Últimos 7 días:    TODOS los backups diarios
Últimas 4 semanas: 1 backup por semana (domingo)
Últimos 12 meses:  1 backup por mes (primer domingo)
Más antiguos:      ELIMINAR

Logs:

[2026-01-12 04:00:00] [INFO] === Backup Cleanup Started ===
[2026-01-12 04:00:00] [INFO] Policy: 7 daily, 4 weekly, 12 monthly
[2026-01-12 04:00:01] [INFO] Cleaning staging backups...
[2026-01-12 04:00:01] [INFO] Found 45 staging backups
[2026-01-12 04:00:02] [INFO] Keeping 23 staging backups
[2026-01-12 04:00:02] [INFO] Removing 22 old staging backups
[2026-01-12 04:00:03] [INFO] Freed 180 MB from staging
[2026-01-12 04:00:03] [INFO] Cleaning production backups...
[2026-01-12 04:00:03] [INFO] Found 38 production backups
[2026-01-12 04:00:04] [INFO] Keeping 20 production backups
[2026-01-12 04:00:04] [INFO] Removing 18 old production backups
[2026-01-12 04:00:05] [INFO] Freed 160 MB from production
[2026-01-12 04:00:05] [INFO] Total freed: 340 MB
[2026-01-12 04:00:05] [INFO] === Cleanup Completed ===

6. check-disk-space.sh

Propósito: Monitorear espacio en disco (sin cambios del original)

bash
#!/bin/bash
# Alertar si disco está cerca de llenarse

# Uso (automático diario vía cron)
/root/scripts/check-disk-space.sh

Umbrales:

bash
WARNING:  80% usado  → Log warning
CRITICAL: 90% usado  → Log critical + alerta Discord
EMERGENCY: 95% usado → Log emergency + múltiples alertas

🔄 Proceso de Restore

Escenario 1: Restore de Staging (Emergencia)

Situación: Database staging corrupta, necesitamos restore inmediato

bash
# 1. Verificar último backup de staging
ls -lh /opt/infrastructure/postgres/backups/staging/ | head -n 5

# 2. Verificar integridad
/root/scripts/verify-backup.sh /opt/infrastructure/postgres/backups/staging/backup_staging_20260112_020000.sql.gz staging

# 3. Detener backend staging
cd /opt/apps/gunei-erp/backend/staging
docker compose stop backend

# 4. Backup de seguridad de DB actual (por si acaso)
docker exec postgres-shared pg_dump -U gunei_staging_user gunei_erp_staging > /tmp/gunei_erp_staging-before-restore.sql

# 5. Eliminar DB actual
docker exec postgres-shared psql -U postgres -c "DROP DATABASE IF EXISTS gunei_erp_staging;"

# 6. Recrear DB vacía
docker exec postgres-shared psql -U postgres -c "CREATE DATABASE gunei_erp_staging;"
docker exec postgres-shared psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE gunei_erp_staging TO gunei_staging_user;"

# 7. Restore
/root/scripts/restore-backup.sh /opt/infrastructure/postgres/backups/staging/backup_staging_20260112_020000.sql.gz staging

# 8. Verificar restore
docker exec postgres-shared psql -U gunei_staging_user -d gunei_erp_staging -c "SELECT COUNT(*) FROM users;"

# 9. Reiniciar backend staging
cd /opt/apps/gunei-erp/backend/staging
docker compose start backend

# 10. Verificar aplicación
curl https://staging-erpback.gunei.xyz/status
docker logs gunei-backend-staging --tail 20

Tiempo estimado: 10-15 minutos

Escenario 2: Restore de Production (Crítico)

Situación: Database production corrupta, disaster recovery

bash
# PROCESO SIMILAR A STAGING pero con paths de production:

# 1. Verificar backup
ls -lh /opt/infrastructure/postgres/backups/production/ | head -n 5

# 2. Detener backend production
cd /opt/apps/gunei-erp/backend/production
docker compose stop backend

# 3. Backup de seguridad
docker exec postgres-shared pg_dump -U gunei_prod_user gunei_erp_production > /tmp/gunei_erp_production-before-restore.sql

# 4. Drop y recrear DB
docker exec postgres-shared psql -U postgres -c "DROP DATABASE IF EXISTS gunei_erp_production;"
docker exec postgres-shared psql -U postgres -c "CREATE DATABASE gunei_erp_production;"
docker exec postgres-shared psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE gunei_erp_production TO gunei_prod_user;"

# 5. Restore
/root/scripts/restore-backup.sh /opt/infrastructure/postgres/backups/production/backup_production_20260112_020000.sql.gz production

# 6. Verificar
docker exec postgres-shared psql -U gunei_prod_user -d gunei_erp_production -c "SELECT COUNT(*) FROM users;"

# 7. Reiniciar backend production
cd /opt/apps/gunei-erp/backend/production
docker compose start backend

# 8. Verificar aplicación (URL de production cuando esté configurada)
curl http://localhost:3100/status
docker logs gunei-backend-production --tail 20

Tiempo estimado: 10-15 minutos

Escenario 3: Copiar Staging a Testing

Situación: Necesitamos copia de staging para pruebas

bash
# 1. Crear DB de testing
docker exec postgres-shared psql -U postgres -c "CREATE DATABASE gunei_erp_test;"

# 2. Restore backup de staging en DB de testing
/root/scripts/restore-backup.sh /opt/infrastructure/postgres/backups/staging/backup_staging_20260112_020000.sql.gz staging gunei_erp_test

# 3. Ajustar permisos
docker exec postgres-shared psql -U postgres -c "GRANT ALL ON DATABASE gunei_erp_test TO gunei_staging_user;"

# 4. Usar en testing
# Actualizar DATABASE_URL temporalmente o crear nuevo .env para testing

Escenario 4: Promover Staging a Production

Situación: Migrar datos de staging a production

bash
# ⚠️ CUIDADO: Esto SOBRESCRIBE production con datos de staging

# 1. Crear backup de production actual (safety)
/root/scripts/backup-db-production.sh

# 2. Usar backup más reciente de staging
STAGING_BACKUP="/opt/infrastructure/postgres/backups/staging/backup_staging_20260112_020000.sql.gz"

# 3. Detener backend production
cd /opt/apps/gunei-erp/backend/production
docker compose stop backend

# 4. Drop y recrear production DB
docker exec postgres-shared psql -U postgres -c "DROP DATABASE IF EXISTS gunei_erp_production;"
docker exec postgres-shared psql -U postgres -c "CREATE DATABASE gunei_erp_production;"
docker exec postgres-shared psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE gunei_erp_production TO gunei_prod_user;"

# 5. Restore staging backup a production
# Nota: Usamos el backup de staging pero lo restauramos a production
gunzip -c "$STAGING_BACKUP" > /tmp/staging_to_prod.sql
docker exec -i postgres-shared psql -U gunei_prod_user -d gunei_erp_production < /tmp/staging_to_prod.sql
rm /tmp/staging_to_prod.sql

# 6. Verificar
docker exec postgres-shared psql -U gunei_prod_user -d gunei_erp_production -c "SELECT COUNT(*) FROM users;"

# 7. Reiniciar backend production
docker compose start backend

# 8. Crear nuevo backup de production (con datos migrados)
/root/scripts/backup-db-production.sh

Escenario 5: Restore Selectivo (Solo Ciertas Tablas)

Situación: Necesitamos recuperar solo tabla clientes de staging

bash
# 1. Descomprimir backup
cd /opt/infrastructure/postgres/backups/staging
gunzip -c backup_staging_20260112_020000.sql.gz > /tmp/backup_staging.sql

# 2. Extraer solo tabla clientes (usando grep/sed o pg_restore si está en formato custom)
# Si es SQL plano:
grep -A 1000 "CREATE TABLE clientes" /tmp/backup_staging.sql > /tmp/clientes_only.sql

# 3. Restore solo esa tabla
docker exec -i postgres-shared psql -U gunei_staging_user -d gunei_erp_staging < /tmp/clientes_only.sql

# 4. Verificar
docker exec postgres-shared psql -U gunei_staging_user -d gunei_erp_staging -c "SELECT COUNT(*) FROM clientes;"

# 5. Limpiar
rm /tmp/backup_staging.sql /tmp/clientes_only.sql

🌍 Gestión por Ambiente

Estructura de Backups

/opt/infrastructure/postgres/backups/
├── staging/
│   ├── backup_staging_20260112_020000.sql.gz
│   ├── backup_staging_20260112_020000.sql.gz.meta
│   ├── backup_staging_20260111_020000.sql.gz
│   ├── backup_staging_20260111_020000.sql.gz.meta
│   └── ...
└── production/
    ├── backup_production_20260112_020000.sql.gz
    ├── backup_production_20260112_020000.sql.gz.meta
    ├── backup_production_20260111_020000.sql.gz
    ├── backup_production_20260111_020000.sql.gz.meta
    └── ...

Tamaños Típicos por Ambiente

Staging (ambiente de desarrollo):

  • Base de datos: ~150 MB
  • Backup sin comprimir: ~150 MB
  • Backup comprimido: ~40 MB (73% compresión)
  • 30 backups diarios ≈ 1.2 GB

Production (cuando esté operativo):

  • Base de datos: ~200 MB (esperado)
  • Backup sin comprimir: ~200 MB
  • Backup comprimido: ~55 MB (73% compresión)
  • 30 backups diarios ≈ 1.7 GB

Total ambos ambientes: ~3 GB para 30 días de retención completa

Comandos por Ambiente

Listar backups de staging:

bash
ls -lh /opt/infrastructure/postgres/backups/staging/

Listar backups de production:

bash
ls -lh /opt/infrastructure/postgres/backups/production/

Ver último backup de cada ambiente:

bash
# Staging
ls -t /opt/infrastructure/postgres/backups/staging/backup_staging_*.sql.gz | head -n 1

# Production
ls -t /opt/infrastructure/postgres/backups/production/backup_production_*.sql.gz | head -n 1

Estadísticas por ambiente:

bash
# Script de stats
cat > /root/scripts/backup-stats.sh << 'EOF'
#!/bin/bash

echo "=== Backup Statistics by Environment ==="
echo ""

for ENV in staging production; do
    BACKUP_DIR="/opt/infrastructure/postgres/backups/$ENV"
    
    echo "--- $ENV ---"
    echo "Total backups: $(ls $BACKUP_DIR/backup_${ENV}_*.sql.gz 2>/dev/null | wc -l)"
    echo "Total size: $(du -sh $BACKUP_DIR 2>/dev/null | cut -f1)"
    echo "Oldest: $(ls -t $BACKUP_DIR/backup_${ENV}_*.sql.gz 2>/dev/null | tail -n 1 | xargs basename)"
    echo "Newest: $(ls -t $BACKUP_DIR/backup_${ENV}_*.sql.gz 2>/dev/null | head -n 1 | xargs basename)"
    echo ""
done
EOF
chmod +x /root/scripts/backup-stats.sh

# Ejecutar
/root/scripts/backup-stats.sh

📊 Políticas de Retención

Política por Ambiente

Cada ambiente mantiene su propia política de retención independiente:

Últimos 7 días:    TODOS los backups diarios
Últimas 4 semanas: 1 backup por semana (domingo)
Últimos 12 meses:  1 backup por mes (primer domingo)
Más antiguos:      ELIMINAR

Ejemplo Práctico - Staging

Hoy: 2026-01-12 (domingo)

/opt/infrastructure/postgres/backups/staging/

CONSERVAR:
✓ backup_staging_20260112_020000.sql.gz  (hoy - diario)
✓ backup_staging_20260111_020000.sql.gz  (ayer - diario)
✓ backup_staging_20260110_020000.sql.gz  (hace 2 días - diario)
✓ backup_staging_20260109_020000.sql.gz  (hace 3 días - diario)
✓ backup_staging_20260108_020000.sql.gz  (hace 4 días - diario)
✓ backup_staging_20260107_020000.sql.gz  (hace 5 días - diario)
✓ backup_staging_20260106_020000.sql.gz  (hace 6 días - diario)
✓ backup_staging_20260105_020000.sql.gz  (hace 7 días - diario + semanal)
✓ backup_staging_20251229_020000.sql.gz  (semanal - hace 2 semanas)
✓ backup_staging_20251222_020000.sql.gz  (semanal - hace 3 semanas)
✓ backup_staging_20251215_020000.sql.gz  (semanal - hace 4 semanas)
✓ backup_staging_20251207_020000.sql.gz  (mensual - diciembre)
✓ backup_staging_20251103_020000.sql.gz  (mensual - noviembre)

ELIMINAR:
✗ backup_staging_20260104_020000.sql.gz  (más de 7 días, no es domingo)
✗ backup_staging_20251015_020000.sql.gz  (más de 1 mes, no es primer domingo)

Ejemplo Práctico - Production

Similar proceso pero independiente de staging:

/opt/infrastructure/postgres/backups/production/

CONSERVAR y ELIMINAR según la misma política, pero aplicada independientemente

📈 Monitoreo de Backups

Verificar Último Backup por Ambiente

Staging:

bash
# Listar backups recientes
ls -lht /opt/infrastructure/postgres/backups/staging/ | head -n 5

# Ver metadata del último
cat /opt/infrastructure/postgres/backups/staging/backup_staging_$(date +%Y%m%d)_020000.sql.gz.meta

# Verificar que el backup de hoy existe
BACKUP_TODAY="/opt/infrastructure/postgres/backups/staging/backup_staging_$(date +%Y%m%d)_020000.sql.gz"
if [ -f "$BACKUP_TODAY" ]; then
    echo "✅ Backup staging de hoy existe"
else
    echo "❌ Backup staging de hoy NO existe"
fi

Production:

bash
# Listar backups recientes
ls -lht /opt/infrastructure/postgres/backups/production/ | head -n 5

# Ver metadata del último
cat /opt/infrastructure/postgres/backups/production/backup_production_$(date +%Y%m%d)_020000.sql.gz.meta

# Verificar que el backup de hoy existe
BACKUP_TODAY="/opt/infrastructure/postgres/backups/production/backup_production_$(date +%Y%m%d)_020000.sql.gz"
if [ -f "$BACKUP_TODAY" ]; then
    echo "✅ Backup production de hoy existe"
else
    echo "❌ Backup production de hoy NO existe"
fi

Verificar Logs de Backup

bash
# Ver backups exitosos de ambos ambientes (últimos 7 días)
grep "Backup completed" /var/log/gunei-backups.log | tail -n 14

# Ver si hubo errores en cualquier ambiente
grep ERROR /var/log/gunei-backups.log | tail -n 20

# Ver último proceso completo
tail -n 100 /var/log/gunei-backups.log

# Filtrar por ambiente específico
grep "staging" /var/log/gunei-backups.log | tail -n 50
grep "production" /var/log/gunei-backups.log | tail -n 50

Dashboard Consolidado

bash
# Script de monitoreo consolidado
cat > /root/scripts/backup-dashboard.sh << 'EOF'
#!/bin/bash

echo "╔═══════════════════════════════════════╗"
echo "║   Gunei ERP - Backup Dashboard       ║"
echo "╚═══════════════════════════════════════╝"
echo ""

for ENV in staging production; do
    BACKUP_DIR="/opt/infrastructure/postgres/backups/$ENV"
    
    echo "┌─ $ENV Environment ─────────────────────"
    
    # Total backups
    TOTAL=$(ls $BACKUP_DIR/backup_${ENV}_*.sql.gz 2>/dev/null | wc -l)
    echo "│ Total backups: $TOTAL"
    
    # Total size
    SIZE=$(du -sh $BACKUP_DIR 2>/dev/null | cut -f1)
    echo "│ Total size: $SIZE"
    
    # Último backup
    LATEST=$(ls -t $BACKUP_DIR/backup_${ENV}_*.sql.gz 2>/dev/null | head -n 1)
    if [ -n "$LATEST" ]; then
        LATEST_NAME=$(basename "$LATEST")
        LATEST_SIZE=$(du -h "$LATEST" | cut -f1)
        LATEST_DATE=$(stat -f "%Sm" -t "%Y-%m-%d %H:%M" "$LATEST" 2>/dev/null || stat -c "%y" "$LATEST" | cut -d'.' -f1)
        echo "│ Latest: $LATEST_NAME"
        echo "│   Size: $LATEST_SIZE"
        echo "│   Date: $LATEST_DATE"
        
        # Verificar metadata
        META_FILE="${LATEST}.meta"
        if [ -f "$META_FILE" ]; then
            echo "│   ✅ Metadata exists"
        else
            echo "│   ⚠️  Metadata missing"
        fi
    else
        echo "│ ⚠️  No backups found"
    fi
    
    # Último backup exitoso en logs
    LAST_SUCCESS=$(grep "Backup completed: $ENV" /var/log/gunei-backups.log | tail -n 1)
    if [ -n "$LAST_SUCCESS" ]; then
        echo "│ Last success: $(echo $LAST_SUCCESS | cut -d']' -f1 | tr -d '[')"
    fi
    
    # Errores recientes
    ERROR_COUNT=$(grep "$ENV" /var/log/gunei-backups.log | grep ERROR | tail -n 10 | wc -l)
    if [ "$ERROR_COUNT" -gt 0 ]; then
        echo "│ ⚠️  Recent errors: $ERROR_COUNT"
    else
        echo "│ ✅ No recent errors"
    fi
    
    echo "└────────────────────────────────────────"
    echo ""
done

# Disk space
echo "┌─ Disk Space ───────────────────────────"
DISK_USAGE=$(df -h /opt/infrastructure/postgres/backups | awk 'NR==2 {print $5}' | tr -d '%')
echo "│ Backup partition: $DISK_USAGE% used"
if [ "$DISK_USAGE" -gt 80 ]; then
    echo "│ ⚠️  WARNING: High disk usage"
elif [ "$DISK_USAGE" -gt 90 ]; then
    echo "│ 🚨 CRITICAL: Disk almost full"
else
    echo "│ ✅ Disk usage healthy"
fi
echo "└────────────────────────────────────────"
EOF
chmod +x /root/scripts/backup-dashboard.sh

# Ejecutar
/root/scripts/backup-dashboard.sh

🚨 Troubleshooting

Backup Falla en un Ambiente Específico

Síntoma: Backup de staging OK pero production falla (o viceversa)

Diagnóstico:

bash
# Ver logs filtrados por ambiente
grep "production" /var/log/gunei-backups.log | tail -n 20
grep "staging" /var/log/gunei-backups.log | tail -n 20

# Verificar que la database existe
docker exec postgres-shared psql -U postgres -c "\l" | grep gunei_erp

# Test de conexión por ambiente
docker exec postgres-shared pg_isready -U gunei_staging_user -d gunei_erp_staging
docker exec postgres-shared pg_isready -U gunei_prod_user -d gunei_erp_production

Solución:

bash
# Si el usuario no tiene permisos
docker exec postgres-shared psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE gunei_erp_staging TO gunei_staging_user;"
docker exec postgres-shared psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE gunei_erp_production TO gunei_prod_user;"

# Re-intentar backup del ambiente específico
/root/scripts/backup-db-staging.sh
# o
/root/scripts/backup-db-production.sh

Disco Lleno por Backups de Ambos Ambientes

Síntoma:

[ERROR] pg_dump failed: No space left on device

Diagnóstico:

bash
# Ver uso de disco por ambiente
du -sh /opt/infrastructure/postgres/backups/staging/
du -sh /opt/infrastructure/postgres/backups/production/

# Ver uso total
du -sh /opt/infrastructure/postgres/backups/

Solución:

bash
# 1. Limpiar backups de ambos ambientes
/root/scripts/cleanup-backups.sh --force

# 2. Si sigue sin espacio, limpiar más agresivamente (solo staging por ejemplo)
find /opt/infrastructure/postgres/backups/staging/ -name "*.gz" -mtime +45 -delete

# 3. Re-intentar backup
/root/scripts/backup-postgres.sh

Backup de Staging OK pero Verificación Falla

Síntoma:

[ERROR] Backup verification FAILED
[ERROR] Checksum mismatch (staging)

Solución:

bash
# 1. Re-ejecutar backup de staging inmediatamente
/root/scripts/backup-db-staging.sh

# 2. Verificar nuevo backup
/root/scripts/verify-backup.sh /opt/infrastructure/postgres/backups/staging/backup_staging_$(date +%Y%m%d)_*.sql.gz staging

# 3. Si persiste, puede ser problema de disco
smartctl -a /dev/sda

Restore Falla - "role does not exist"

Síntoma:

[ERROR] role "gunei_staging_user" does not exist

Solución:

bash
# Recrear usuario
docker exec postgres-shared psql -U postgres << EOF
CREATE USER gunei_staging_user WITH PASSWORD 'staging_password';
GRANT ALL PRIVILEGES ON DATABASE gunei_erp_staging TO gunei_staging_user;
EOF

# O para production
docker exec postgres-shared psql -U postgres << EOF
CREATE USER gunei_prod_user WITH PASSWORD 'production_password';
GRANT ALL PRIVILEGES ON DATABASE gunei_erp_production TO gunei_prod_user;
EOF

# Re-intentar restore
/root/scripts/restore-backup.sh <backup-file> <environment>

Cron No Ejecuta Backups

Síntoma: No hay backups nuevos de ningún ambiente en días

Diagnóstico:

bash
# 1. Cron está corriendo?
systemctl status cron

# 2. Crontab está configurado?
crontab -l | grep backup

# 3. Scripts son ejecutables?
ls -la /root/scripts/backup-postgres.sh

# 4. Logs de cron
grep CRON /var/log/syslog | grep backup

# 5. Permisos del directorio de backups
ls -la /opt/infrastructure/postgres/backups/

Solución:

bash
# Asegurar permisos de scripts
chmod +x /root/scripts/*.sh

# Asegurar permisos de directorios
chmod 755 /opt/infrastructure/postgres/backups/
chmod 755 /opt/infrastructure/postgres/backups/staging/
chmod 755 /opt/infrastructure/postgres/backups/production/

# Test manual
/root/scripts/backup-postgres.sh

# Si funciona manual pero no en cron, agregar PATH
crontab -e
# Primera línea:
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

Backup Muy Lento en Production

Síntoma: Backup de production tarda > 30 minutos, staging OK

Causas posibles:

  • Database production más grande
  • Más tráfico durante backup
  • Locks en tablas

Solución:

bash
# 1. Ver tamaño de cada database
docker exec postgres-shared psql -U postgres -c "SELECT pg_database.datname, pg_size_pretty(pg_database_size(pg_database.datname)) FROM pg_database ORDER BY pg_database_size(pg_database.datname) DESC;"

# 2. Ajustar horario si hay tráfico
# Mover backup de production a horario de menor carga
crontab -e
# Cambiar a 3 AM solo para production:
0 2 * * * /root/scripts/backup-db-staging.sh >> /var/log/gunei-backups.log 2>&1
0 3 * * * /root/scripts/backup-db-production.sh >> /var/log/gunei-backups.log 2>&1

# 3. Excluir tablas grandes de logs (si existen)
# Editar backup-postgres.sh para agregar --exclude-table

🔐 Seguridad de Backups

Permisos por Ambiente

bash
# Directorio raíz
chmod 755 /opt/infrastructure/postgres/backups/

# Directorios de ambientes
chmod 755 /opt/infrastructure/postgres/backups/staging/
chmod 755 /opt/infrastructure/postgres/backups/production/

# Backups (solo root puede leer/escribir)
find /opt/infrastructure/postgres/backups/ -name "*.gz" -exec chmod 640 {} \;
find /opt/infrastructure/postgres/backups/ -name "*.meta" -exec chmod 640 {} \;
chown -R root:postgres /opt/infrastructure/postgres/backups/

📈 Mejoras Futuras

Corto Plazo

  • [ ] Notificaciones Discord para backups fallidos por ambiente
  • [ ] Dashboard web de backups (listar por ambiente, descargar)
  • [ ] Backup diferencial por ambiente

Mediano Plazo

  • [ ] Encriptación GPG de backups
  • [ ] Replicación a S3 o storage remoto (por ambiente)
  • [ ] Point-in-time recovery (PITR) por ambiente
  • [ ] Backup de archivos (uploads, certs)

Largo Plazo

  • [ ] Replicación continua (streaming replication) de production
  • [ ] Multi-site backups (geo-redundancia)
  • [ ] Automated DR testing por ambiente
  • [ ] Backup compliance reporting

📚 Referencias

Scripts Relacionados

  • /root/scripts/backup-postgres.sh: Backup principal
  • /root/scripts/backup-db-staging.sh: Backup staging
  • /root/scripts/backup-db-production.sh: Backup production
  • /root/scripts/restore-backup.sh: Restore por ambiente
  • /root/scripts/verify-backup.sh: Verificación
  • /root/scripts/cleanup-backups.sh: Limpieza con retención
  • /root/scripts/check-disk-space.sh: Monitoreo de disco
  • /root/scripts/backup-stats.sh: Estadísticas por ambiente
  • /root/scripts/backup-dashboard.sh: Dashboard consolidado

PostgreSQL Shared

  • Contenedor: postgres-shared
  • Puerto: 5433 (externo), 5432 (interno)
  • Ubicación: /opt/infrastructure/postgres/
  • Databases:
    • gunei_erp_staging (usuario: gunei_staging_user)
    • gunei_erp_production (usuario: gunei_prod_user)

Última actualización: 14 Enero 2026 Versión: 2.1.0 Responsable: Mikle SLA: RPO 24 horas, RTO 15 minutos (por ambiente)

Cambios en v2.1:

  • ✅ Quick Reference con comandos más usados
  • ✅ Conexión a PostgreSQL desde host (puerto 5433) y container (5432)
  • ✅ Tabla de ubicaciones clave
  • ✅ Timezone Argentina (TZ=America/Argentina/Buenos_Aires)
  • ✅ Nomenclatura de backups con hora Argentina

Cambios en v2.0:

  • ✅ Soporte para PostgreSQL Shared
  • ✅ Backup separado por ambiente (staging + production)
  • ✅ Scripts actualizados para multi-ambiente
  • ✅ Restore con especificación de ambiente
  • ✅ Políticas de retención independientes por ambiente
  • ✅ Monitoreo y troubleshooting por ambiente
  • ✅ Dashboard consolidado de ambos ambientes