¡Respaldo seguro y eficiente: Usando Azure Blob Storage para proteger tus datos!
Daniel J. Saldaña- 22 de diciembre de 2025
- Puntuación de feedback

Hoy quiero abordar uno de los temas más críticos para la seguridad de cualquier sitio o proyecto: los respaldos. En este post, exploraremos cómo garantizar la protección y accesibilidad de tus datos, utilizando Azure Blob Storage como la solución ideal para crear respaldos seguros y automatizados.
¿Por qué es tan importante un sistema de respaldo?
Los datos son el activo más valioso en cualquier proyecto digital. Sin embargo, a menudo subestimamos la importancia de tener un respaldo confiable. Un fallo del sistema, un ataque malicioso o incluso un error humano pueden provocar la pérdida de información crítica. Es por eso que, tener una estrategia de respaldos sólida y eficiente es fundamental.
¿Por qué elegir Azure Blob Storage?
Cuando hablamos de soluciones en la nube, Azure se destaca como uno de los proveedores más robustos y confiables. Azure Blob Storage es una opción poderosa para almacenar grandes volúmenes de datos y realizar respaldos de forma segura, con múltiples capas de protección.
Con Azure, puedes aprovechar sus características como:
- Escalabilidad: Puedes almacenar desde unos pocos gigabytes hasta petabytes de datos.
- Seguridad: Azure proporciona múltiples opciones de autenticación y control de acceso, asegurando que tus datos estén protegidos.
- Facilidad de integración: Usando herramientas como Azure CLI y APIs, podemos automatizar el proceso de respaldo y facilitar su gestión.
Cómo crear un Azure Storage Account y Blob Container
Antes de comenzar con la automatización del respaldo, necesitamos crear un Storage Account y un Blob Container en Azure. Aquí te explico cómo hacerlo:
Paso 1: Crear un Azure Storage Account
Accede al portal de Azure: Dirígete a Azure Portal.
Crear un nuevo Storage Account:
Haz clic en “Crear un recurso” en la parte superior izquierda.
Busca “Storage account” y selecciona la opción “Storage account - blob, file, table, queue”.
Completa los campos requeridos:
- Subscription: Selecciona tu suscripción de Azure.
- Resource group: Selecciona un grupo de recursos existente o crea uno nuevo.
- Storage account name: Define un nombre único para tu cuenta de almacenamiento.
- Region: Elige la región donde quieres que se almacenen tus datos.
- Performance: Deja la opción por defecto en Standard.
- Replication: Selecciona la opción de replicación que más se ajuste a tus necesidades (por ejemplo, LRS para localmente redundante).
Revisar y crear: Revisa los detalles y haz clic en “Crear”. El proceso de creación tomará unos minutos.
Paso 2: Crear un Blob Container
Accede a tu Storage Account: Una vez creado el Storage Account, haz clic en él desde el portal de Azure.
Crear un Blob Container:
- Dentro de tu Storage Account, ve a “Containers” en el menú de la izquierda.
- Haz clic en ”+ Container”.
- Asigna un nombre al contenedor (por ejemplo, ghost-backups).
- Selecciona el nivel de acceso private.
- Haz clic en “Crear”.
¡Listo! Ahora tienes un Storage Account y un Blob Container donde podrás almacenar tus respaldos.
Ejemplo práctico: Respaldo automatizado de Ghost con Azure Blob Storage
En este ejemplo, vamos a ver cómo podemos respaldar los datos de un sitio web usando Azure Blob Storage. Aunque el ejemplo es sobre Ghost, este proceso puede adaptarse fácilmente a cualquier otro sistema.
¿Qué haremos?
- Generar el respaldo: Primero, crearemos un respaldo de los datos de Ghost y su base de datos MySQL.
- Comprimir los archivos: Luego, comprimiremos el respaldo en un archivo
.tar.gzpara reducir el tamaño y hacerlo más eficiente. - Subir a Azure: Finalmente, utilizaremos Azure CLI para subir el archivo comprimido a Azure Blob Storage.
Paso 1: Crear el respaldo de los datos
El primer paso es crear el respaldo de los datos de tu servidor, que incluye tanto los archivos de Ghost como la base de datos MySQL.
# Conectar al servidor y crear respaldossh -i ~/.ssh/id_rsa ${{ secrets.SSH_USER }}@${{ secrets.SERVER_HOST }} \ "tar -czf /tmp/ghost-backup.tar.gz /opt/ghost/data /tmp/ghost_database.sql"Este comando crea un archivo comprimido que contiene tanto los archivos de Ghost como la base de datos MySQL.
Paso 2: Subir el respaldo a Azure Blob Storage
Una vez que tenemos el archivo de respaldo, el siguiente paso es subirlo a Azure Blob Storage. Esto se hace de manera sencilla utilizando Azure CLI:
# Subir el archivo de respaldo a Azure Blob Storageaz storage blob upload \ --account-name $AZURE_STORAGE_ACCOUNT \ --container-name $CONTAINER_NAME \ --name "ghost-backup-${TIMESTAMP}.tar.gz" \ --file "/tmp/ghost-backup.tar.gz" \ --sas-token $SAS_TOKENEste comando sube el archivo comprimido a tu contenedor en Azure Blob Storage, asegurando que el respaldo esté almacenado de forma segura en la nube.
Paso 3: Limpiar respaldos antiguos
La gestión de los respaldos no termina solo con la creación y almacenamiento. También es importante mantener tu almacenamiento limpio y organizado. Por eso, puedes establecer una política de retención que elimine los respaldos antiguos.
# Limpiar respaldos antiguos en Azureaz storage blob list \ --account-name $AZURE_STORAGE_ACCOUNT \ --container-name $CONTAINER_NAME \ --query "[?properties.lastModified < '${CUTOFF_DATE}'].name" \ --output tsv \ --sas-token $SAS_TOKEN | while read blob_name; do az storage blob delete \ --account-name $AZURE_STORAGE_ACCOUNT \ --container-name $CONTAINER_NAME \ --name "$blob_name" \ --sas-token $SAS_TOKEN doneEste comando lista los respaldos antiguos y los elimina automáticamente según la fecha de retención configurada, ayudando a liberar espacio en Azure y mantener solo los respaldos más recientes.
Código completo de la automatización con GitHub Actions
A continuación, te presento el workflow completo de GitHub Actions que implementa todo el sistema de respaldos automatizado. Este archivo YAML incluye todas las funcionalidades que hemos discutido: conexión SSH segura, respaldo de base de datos MySQL, compresión de archivos, subida a Azure Blob Storage y limpieza automática de respaldos antiguos.
El workflow está diseñado para ejecutarse automáticamente cada día a las 3:00 AM (hora española) y también puede ejecutarse manualmente cuando sea necesario. Incluye manejo robusto de errores, validaciones de espacio en disco y notificaciones automáticas en caso de fallos.
Características principales del código:
- ✅ Respaldo automático diario y manual bajo demanda
- ✅ Validación de espacio en disco antes de crear respaldos
- ✅ Compresión eficiente con tar.gz
- ✅ Subida segura a Azure Blob Storage
- ✅ Limpieza automática de respaldos antiguos (60 días de retención)
- ✅ Notificaciones automáticas en caso de errores
- ✅ Logs detallados para facilitar el debugging
Guarda este código en .github/workflows/backup-ghost-data.yml en tu repositorio:
name: Respaldo de Datos Ghost
on: # Ejecutar manualmente workflow_dispatch: # Ejecutar automáticamente todos los días a las 3:00 AM hora española (1:00 AM UTC en invierno, 2:00 AM UTC en verano) schedule: - cron: "0 2 * * *" # 3:00 AM CEST (verano)
env: RETENTION_DAYS: 60 CONTAINER_NAME: ghost-backups AZURE_STORAGE_ACCOUNT: aprendejaponesbackup
concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true
jobs: backup: runs-on: ubuntu-latest timeout-minutes: 30 steps: - name: Descargar repositorio uses: actions/checkout@v5
- name: Configurar clave SSH run: | mkdir -p ~/.ssh echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa ssh-keyscan -H ${{ secrets.SERVER_HOST }} >> ~/.ssh/known_hosts
- name: Crear directorio de respaldo run: | echo "📁 Creando directorio de backup local..." mkdir -p backup echo "✅ Directorio creado en: $(pwd)/backup" echo "💾 Espacio disponible en disco:" df -h $(pwd) | tail -1
- name: Generar nombre del archivo de respaldo id: backup_info run: | echo "🏷️ Generando nombre de archivo de backup..." TIMESTAMP=$(date +%Y%m%d-%H%M%S) BACKUP_START_TIME=$(date +%s) if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then # Ejecución manual - usar carpeta snapshots FOLDER="snapshots" FILENAME="ghost-snapshot-${TIMESTAMP}.tar.gz" echo "📸 Tipo: Snapshot manual" else # Ejecución automática - carpeta raíz FOLDER="" FILENAME="ghost-data-backup-${TIMESTAMP}.tar.gz" echo "⏰ Tipo: Backup automático" fi echo "📝 Archivo: ${FILENAME}" echo "📂 Carpeta: ${FOLDER:-'(raíz)'}" echo "filename=${FILENAME}" >> $GITHUB_OUTPUT echo "folder=${FOLDER}" >> $GITHUB_OUTPUT echo "timestamp=${TIMESTAMP}" >> $GITHUB_OUTPUT echo "start_time=${BACKUP_START_TIME}" >> $GITHUB_OUTPUT
- name: Respaldar base de datos MySQL run: | echo "🗄️ Iniciando backup de la base de datos MySQL..." echo "🔗 Conectando al servidor ${{ secrets.SERVER_HOST }}..."
# Crear directorio temporal para el backup de la BD ssh -i ~/.ssh/id_rsa ${{ secrets.SSH_USER }}@${{ secrets.SERVER_HOST }} \ "mkdir -p /tmp/ghost-backup-${{ steps.backup_info.outputs.timestamp }}"
# Obtener el nombre del contenedor MySQL echo "🔍 Buscando contenedor MySQL..." MYSQL_CONTAINER=$(ssh -i ~/.ssh/id_rsa ${{ secrets.SSH_USER }}@${{ secrets.SERVER_HOST }} \ "sudo docker ps --format '{{.Names}}' | grep -i mysql | head -1")
if [ -z "$MYSQL_CONTAINER" ]; then echo "⚠️ No se encontró contenedor MySQL, intentando con 'db' o 'mysql'..." MYSQL_CONTAINER=$(ssh -i ~/.ssh/id_rsa ${{ secrets.SSH_USER }}@${{ secrets.SERVER_HOST }} \ "sudo docker ps --format '{{.Names}}' | grep -E 'db|mysql' | head -1") fi
if [ -z "$MYSQL_CONTAINER" ]; then echo "❌ ERROR: No se pudo encontrar el contenedor MySQL" echo "📋 Contenedores disponibles:" ssh -i ~/.ssh/id_rsa ${{ secrets.SSH_USER }}@${{ secrets.SERVER_HOST }} "sudo docker ps --format '{{.Names}}'" exit 1 fi
echo "✅ Contenedor MySQL encontrado: ${MYSQL_CONTAINER}"
# Exportar la base de datos echo "💾 Exportando base de datos 'ghost'..."
# Intentar primero con el usuario de la base de datos echo "🔐 Intentando con usuario de base de datos..." if ssh -i ~/.ssh/id_rsa ${{ secrets.SSH_USER }}@${{ secrets.SERVER_HOST }} \ "sudo docker exec -e MYSQL_PWD='${{ secrets.DATABASE_PASSWORD }}' ${MYSQL_CONTAINER} mysqldump -u ${{ secrets.DATABASE_USER }} ghost | sudo tee /tmp/ghost-backup-${{ steps.backup_info.outputs.timestamp }}/ghost_database.sql > /dev/null" 2>/dev/null; then echo "✅ Backup exitoso con usuario de base de datos" else echo "⚠️ Falló con usuario de BD, intentando con root..." ssh -i ~/.ssh/id_rsa ${{ secrets.SSH_USER }}@${{ secrets.SERVER_HOST }} \ "sudo docker exec -e MYSQL_PWD='${{ secrets.MYSQL_ROOT_PASSWORD }}' ${MYSQL_CONTAINER} mysqldump -u root ghost | sudo tee /tmp/ghost-backup-${{ steps.backup_info.outputs.timestamp }}/ghost_database.sql > /dev/null" echo "✅ Backup exitoso con usuario root" fi
# Verificar que el backup se creó correctamente DB_SIZE=$(ssh -i ~/.ssh/id_rsa ${{ secrets.SSH_USER }}@${{ secrets.SERVER_HOST }} \ "ls -lh /tmp/ghost-backup-${{ steps.backup_info.outputs.timestamp }}/ghost_database.sql | awk '{print \$5}'")
echo "✅ Base de datos exportada exitosamente" echo "📊 Tamaño del dump SQL: ${DB_SIZE}"
- name: Respaldar datos de Ghost vía SSH run: | echo "🔗 Conectando al servidor ${{ secrets.SERVER_HOST }}..." echo "📦 Creando backup comprimido en el servidor..." echo "📍 Origen archivos: /opt/ghost/data/ghost" echo "📍 Origen BD: /tmp/ghost-backup-${{ steps.backup_info.outputs.timestamp }}/ghost_database.sql" echo "📍 Destino temporal: /tmp/${{ steps.backup_info.outputs.filename }}"
# Verificar espacio disponible en el servidor echo "💾 Verificando espacio en servidor..." ssh -i ~/.ssh/id_rsa ${{ secrets.SSH_USER }}@${{ secrets.SERVER_HOST }} \ "echo 'Disco principal:' && df -h /"
# Validar espacio libre mínimo (30%) echo "🔍 Validando espacio libre mínimo..." DISK_USAGE=$(ssh -i ~/.ssh/id_rsa ${{ secrets.SSH_USER }}@${{ secrets.SERVER_HOST }} \ "df / | tail -1 | awk '{print \$5}' | sed 's/%//'")
echo "📊 Uso actual del disco: ${DISK_USAGE}%"
if [ "$DISK_USAGE" -gt 70 ]; then echo "❌ ERROR: Espacio insuficiente en el servidor" echo " 💾 Uso actual: ${DISK_USAGE}% (máximo permitido: 70%)" echo " 🚨 Se requiere al menos 30% de espacio libre para crear el backup" echo " 💡 Libera espacio en el servidor antes de continuar" exit 1 else echo "✅ Espacio suficiente: ${DISK_USAGE}% usado ($((100-DISK_USAGE))% libre)" fi
# Estimar tamaño del backup y verificar si cabe echo "📏 Estimando tamaño del backup..." DATA_SIZE_KB=$(ssh -i ~/.ssh/id_rsa ${{ secrets.SSH_USER }}@${{ secrets.SERVER_HOST }} \ "du -sk /opt/ghost/data 2>/dev/null | awk '{print \$1}' || echo '0'")
if [ "$DATA_SIZE_KB" -gt 0 ]; then # Estimar que el backup comprimido será ~60% del tamaño original ESTIMATED_BACKUP_KB=$((DATA_SIZE_KB * 60 / 100)) AVAILABLE_KB=$(ssh -i ~/.ssh/id_rsa ${{ secrets.SSH_USER }}@${{ secrets.SERVER_HOST }} \ "df / | tail -1 | awk '{print \$4}'")
echo "📊 Tamaño de datos: $((DATA_SIZE_KB/1024)) MB" echo "📊 Backup estimado: $((ESTIMATED_BACKUP_KB/1024)) MB (comprimido)" echo "📊 Espacio disponible: $((AVAILABLE_KB/1024)) MB"
if [ "$ESTIMATED_BACKUP_KB" -gt "$AVAILABLE_KB" ]; then echo "❌ ERROR: El backup estimado no cabe en el disco" echo " 📦 Backup estimado: $((ESTIMATED_BACKUP_KB/1024)) MB" echo " 💾 Espacio disponible: $((AVAILABLE_KB/1024)) MB" echo " 🚨 Se necesitan al menos $((ESTIMATED_BACKUP_KB/1024)) MB libres" exit 1 else echo "✅ El backup estimado cabe en el disco" fi else echo "⚠️ No se pudo estimar el tamaño, continuando..." fi
# Crear backup que incluye archivos y base de datos echo "📦 Creando archivo tar.gz con archivos y base de datos..." ssh -i ~/.ssh/id_rsa ${{ secrets.SSH_USER }}@${{ secrets.SERVER_HOST }} \ "sudo tar -czf /tmp/${{ steps.backup_info.outputs.filename }} \ -C /opt/ghost/data --exclude='ghost/content/themes' ghost \ -C /tmp/ghost-backup-${{ steps.backup_info.outputs.timestamp }} ghost_database.sql"
echo "✅ Backup creado en el servidor" echo "⬇️ Descargando backup al runner de GitHub..."
scp -i ~/.ssh/id_rsa ${{ secrets.SSH_USER }}@${{ secrets.SERVER_HOST }}:/tmp/${{ steps.backup_info.outputs.filename }} backup/
echo "✅ Backup descargado exitosamente" echo "📊 Información del archivo:" FILE_SIZE=$(ls -lh backup/${{ steps.backup_info.outputs.filename }} | awk '{print $5}') FILE_SIZE_BYTES=$(stat -c%s backup/${{ steps.backup_info.outputs.filename }}) echo " 📏 Tamaño: ${FILE_SIZE} (${FILE_SIZE_BYTES} bytes)" echo " 📍 Ubicación: $(pwd)/backup/${{ steps.backup_info.outputs.filename }}" echo " 🗜️ Compresión: tar.gz" echo " 📦 Contenido: archivos Ghost + base de datos MySQL"
- name: Limpiar archivos temporales del servidor if: always() run: | echo "🧹 Limpiando archivos temporales del servidor..." echo "📍 Eliminando: /tmp/${{ steps.backup_info.outputs.filename }}" ssh -i ~/.ssh/id_rsa ${{ secrets.SSH_USER }}@${{ secrets.SERVER_HOST }} \ "sudo rm -f /tmp/${{ steps.backup_info.outputs.filename }}" || true echo "📍 Eliminando directorio temporal de BD: /tmp/ghost-backup-${{ steps.backup_info.outputs.timestamp }}" ssh -i ~/.ssh/id_rsa ${{ secrets.SSH_USER }}@${{ secrets.SERVER_HOST }} \ "sudo rm -rf /tmp/ghost-backup-${{ steps.backup_info.outputs.timestamp }}" || true echo "✅ Limpieza del servidor completada"
- name: Instalar Azure CLI run: | echo "⚙️ Instalando Azure CLI..." curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash echo "✅ Azure CLI instalado"
- name: Subir respaldo a Azure Storage run: | echo "☁️ Preparando subida a Azure Storage..."
# Determinar la ruta completa del blob if [ -n "${{ steps.backup_info.outputs.folder }}" ]; then BLOB_NAME="${{ steps.backup_info.outputs.folder }}/${{ steps.backup_info.outputs.filename }}" else BLOB_NAME="${{ steps.backup_info.outputs.filename }}" fi
echo "📍 Storage Account: ${{ env.AZURE_STORAGE_ACCOUNT }}" echo "📍 Container: ${{ env.CONTAINER_NAME }}" echo "📍 Blob: ${BLOB_NAME}"
# Mostrar tamaño antes de subir FILE_SIZE=$(ls -lh backup/${{ steps.backup_info.outputs.filename }} | awk '{print $5}') echo "📦 Archivo a subir: ${FILE_SIZE}" echo "⬆️ Iniciando subida a Azure Storage..."
az storage blob upload \ --account-name ${{ env.AZURE_STORAGE_ACCOUNT }} \ --container-name ${{ env.CONTAINER_NAME }} \ --name "${BLOB_NAME}" \ --file backup/${{ steps.backup_info.outputs.filename }} \ --sas-token "${{ secrets.AZURE_SAS_TOKEN }}"
echo "✅ Backup subido exitosamente a Azure Storage" echo "🌐 URL completa: https://${{ env.AZURE_STORAGE_ACCOUNT }}.blob.core.windows.net/${{ env.CONTAINER_NAME }}/${BLOB_NAME}"
- name: Limpiar respaldos antiguos en Azure Storage run: | echo "🗓️ Iniciando limpieza de backups antiguos..." # Obtener fecha límite para retención CUTOFF_DATE=$(date -d "${{ env.RETENTION_DAYS }} days ago" +%Y-%m-%d) echo "📅 Fecha límite: ${CUTOFF_DATE} (archivos anteriores serán eliminados)" echo "⏳ Retención configurada: ${{ env.RETENTION_DAYS }} días"
# Limpiar backups automáticos antiguos echo "🧹 Limpiando backups automáticos antiguos..." echo "🔍 Buscando archivos con prefijo: ghost-data-backup-" az storage blob list \ --account-name ${{ env.AZURE_STORAGE_ACCOUNT }} \ --container-name ${{ env.CONTAINER_NAME }} \ --prefix "ghost-data-backup-" \ --query "[?properties.lastModified < '${CUTOFF_DATE}'].name" \ --output tsv \ --sas-token "${{ secrets.AZURE_SAS_TOKEN }}" | while read blob_name; do if [ ! -z "$blob_name" ]; then echo "🗑️ Eliminando backup automático antiguo: $blob_name" az storage blob delete \ --account-name ${{ env.AZURE_STORAGE_ACCOUNT }} \ --container-name ${{ env.CONTAINER_NAME }} \ --name "$blob_name" \ --sas-token "${{ secrets.AZURE_SAS_TOKEN }}" fi done
# Limpiar snapshots manuales antiguos echo "🧹 Limpiando snapshots manuales antiguos..." echo "🔍 Buscando archivos con prefijo: snapshots/ghost-snapshot-" az storage blob list \ --account-name ${{ env.AZURE_STORAGE_ACCOUNT }} \ --container-name ${{ env.CONTAINER_NAME }} \ --prefix "snapshots/ghost-snapshot-" \ --query "[?properties.lastModified < '${CUTOFF_DATE}'].name" \ --output tsv \ --sas-token "${{ secrets.AZURE_SAS_TOKEN }}" | while read blob_name; do if [ ! -z "$blob_name" ]; then echo "🗑️ Eliminando snapshot manual antiguo: $blob_name" az storage blob delete \ --account-name ${{ env.AZURE_STORAGE_ACCOUNT }} \ --container-name ${{ env.CONTAINER_NAME }} \ --name "$blob_name" \ --sas-token "${{ secrets.AZURE_SAS_TOKEN }}" fi done
echo "✅ Limpieza de archivos antiguos completada"
- name: Limpiar clave SSH if: always() run: | echo "🔐 Limpiando clave SSH temporal..." rm -f ~/.ssh/id_rsa echo "✅ Clave SSH eliminada"
- name: Resumen del respaldo id: backup_summary run: | echo "✅ Backup completado exitosamente" echo "📁 Archivo: ${{ steps.backup_info.outputs.filename }}"
# Calcular tiempo total de backup END_TIME=$(date +%s) DURATION=$((END_TIME - ${{ steps.backup_info.outputs.start_time }})) DURATION_MIN=$((DURATION / 60)) DURATION_SEC=$((DURATION % 60))
echo "⏱️ Tiempo total: ${DURATION_MIN}m ${DURATION_SEC}s" echo "duration=${DURATION}" >> $GITHUB_OUTPUT
# Obtener tamaño del backup BACKUP_SIZE=$(ls -lh backup/${{ steps.backup_info.outputs.filename }} 2>/dev/null | awk '{print $5}' || echo "N/A") echo "backup_size=${BACKUP_SIZE}" >> $GITHUB_OUTPUT
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then echo "📸 Tipo: Snapshot manual" echo "☁️ Ubicación: ${{ env.AZURE_STORAGE_ACCOUNT }}/${{ env.CONTAINER_NAME }}/snapshots/" else echo "⏰ Tipo: Backup automático" echo "☁️ Ubicación: ${{ env.AZURE_STORAGE_ACCOUNT }}/${{ env.CONTAINER_NAME }}/" fi echo "🗓️ Retención configurada: ${{ env.RETENTION_DAYS }} días"
# Notificación solo en caso de fallo notificar-fallo: needs: backup runs-on: ubuntu-latest if: failure() permissions: issues: write steps: - name: Crear notificación de fallo uses: actions/github-script@v7 with: script: | const timestamp = new Date().toLocaleString('es-ES', { timeZone: 'Europe/Madrid', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });
const backupType = '${{ github.event_name }}' === 'workflow_dispatch' ? 'Snapshot Manual' : 'Backup Automático'; const title = `🚨 Error en ${backupType} - ${timestamp}`;
const body = `## 🚨 Error en el Sistema de Backups
**Fecha:** ${timestamp} **Tipo:** ${backupType} **Estado:** ❌ El backup ha fallado
### 📋 Detalles del Error
- **Workflow:** \`${{ github.workflow }}\` - **Run ID:** \`${{ github.run_id }}\` - **Commit:** \`${{ github.sha }}\` - **Rama:** \`${{ github.ref_name }}\`
### 🔗 Enlaces Útiles
- [Ver logs del workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) - [Ejecutar backup manual](https://github.com/${{ github.repository }}/actions/workflows/backup-ghost-data.yml)
### 🛠️ Acciones Recomendadas
1. **Revisar los logs** del workflow para identificar el error específico 2. **Verificar conectividad** SSH al servidor 3. **Comprobar espacio** disponible en el servidor 4. **Validar credenciales** de Azure Storage 5. **Ejecutar backup manual** una vez solucionado el problema
### ⚠️ Importante
- Los datos de Ghost no están siendo respaldados automáticamente - Se recomienda solucionar este problema lo antes posible - Considera ejecutar un backup manual mientras se resuelve el issue
---
*Notificación automática del sistema de backups*
/cc @danieljsaldana`;
await github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, title: title, body: body, assignees: ['danieljsaldana'], labels: ['backup', 'error', 'urgent'] });Beneficios de usar Azure Blob Storage para tus respaldos
- Automatización: Gracias a herramientas como Azure CLI, puedes automatizar el proceso de creación y almacenamiento de respaldos, eliminando la intervención manual.
- Escalabilidad: Puedes almacenar un número prácticamente ilimitado de respaldos sin preocuparte por quedarte sin espacio.
- Seguridad: Con Azure, tus datos están protegidos por varias capas de seguridad, incluyendo autenticación avanzada y cifrado en reposo.
- Retención eficiente: Azure te permite configurar políticas de retención para eliminar respaldos antiguos, lo que te ayuda a optimizar el uso del espacio.

Conclusión
El respaldo de tus datos no tiene por qué ser complicado. Usando Azure Blob Storage, puedes garantizar que tus datos estén siempre seguros y accesibles, al mismo tiempo que automatizas el proceso para que no tengas que preocuparte por hacerlo manualmente. No importa si trabajas con Ghost o cualquier otro sistema, Azure te ofrece la flexibilidad y escalabilidad que necesitas.


