#mysql #database #githubactions #backup
Creado por:
Soanny Geraldine Rivas Gutierrez
En una aplicación de inventario, perder un registro puede alterar el stock disponible y el valor de los productos. Para recuperar esa información hace falta conservar el estado de la base en un momento anterior y comprobar que la copia puede importarse correctamente.
El repositorio del código permite reconstruir la aplicación; el respaldo permite recuperar sus datos.
En este proyecto desarrollé un inventario con Node.js, Express y MySQL, junto con scripts de exportación y recuperación. La solución configura copias completas con mysqldump, las comprime, las cifra y prepara su ejecución diaria con GitHub Actions.
Para la publicación se plantea alojar la aplicación en Railway y utilizar MySQL administrado en Aiven.
La validación realizada es local: se utilizó un servidor MySQL real y se recuperaron los registros en una base independiente.
La configuración del repositorio público, el despliegue en Railway, la conexión a Aiven y las ejecuciones remotas deben completarse desde las cuentas del estudiante antes de presentar sus enlaces como evidencia.
Diseño de la solución
La aplicación permite consultar productos, agregar registros y eliminarlos.
Cada producto tiene:
- Identificador
- Nombre
- Precio
- Stock
- Fecha de creación
Esta información proporciona un conjunto pequeño y verificable para estudiar el comportamiento de un respaldo.
La siguiente tabla presenta los componentes y responsabilidades de la solución.
Tabla 1. Componentes y responsabilidades de la solución.
El entorno local sirve para desarrollar y verificar la recuperación.
En el entorno de nube, la API de Railway se conecta a MySQL en Aiven. GitHub Actions puede conectarse al mismo servicio para generar el respaldo y conservar un artefacto cifrado.
El código y las copias tienen ciclos de vida diferentes: el código permanece en Git y los artefactos expiran según la retención configurada.
Política de respaldo
Se eligió un respaldo lógico completo porque la base es pequeña y su contenido puede revisarse mediante SQL.
Cada copia incluye la estructura y las filas de la base de demostración.
No se implementaron respaldos incrementales ni recuperación a un instante arbitrario mediante binlogs.
La siguiente tabla presenta la política de respaldo y recuperación.
Tabla 2. Política de respaldo y recuperación.
La configuración contempla:
- Una ejecución diaria.
- Ejecuciones manuales para validación.
- Horario previsto de
06:00 UTC, equivalente a01:00en Lima. - Retención de siete días.
- Archivos comprimidos y cifrados con extensión
.sql.gz.enc. - Verificación mediante SHA-256.
- Restauración en una base vacía distinta de la original.
Si las ejecuciones diarias terminan correctamente, la pérdida potencial corresponde aproximadamente a los cambios desde la última copia, que puede rondar un día.
Una ejecución fallida o retrasada amplía ese intervalo.
El tiempo de recuperación debe medirse con el volumen real de información; no se establece una garantía de RTO para esta demostración.
Archivos del proyecto y configuración local
El repositorio mysql-backup-strategy reúne la aplicación, el esquema SQL y los mecanismos de automatización.
Los principales archivos del proyecto se presentan en la siguiente tabla.
Tabla 3. Archivos principales del proyecto.
Entre los archivos principales se encuentran:
-
srcypublic database/database.sqlscripts/backup.shscripts/restore.shscripts/crypto.js.github/workflowsDockerfiledocker-compose.yml.env.example
Para utilizar la ruta de Docker en Windows, se copia la plantilla de variables, se reemplazan las claves y se inicia Docker Desktop con contenedores Linux.
Los comandos se ejecutan desde la carpeta del proyecto.
Copy-Item .env.example .env
notepad .env
docker compose up --build -d
docker compose ps
Invoke-RestMethod http://localhost:3000/health
MySQL conserva la información en un volumen.
Las variables:
MYSQL_DATABASE
MYSQL_USER
MYSQL_PASSWORD
inicializan un volumen nuevo.
Modificar el archivo de variables después no cambia automáticamente las cuentas ya creadas.
La aplicación utiliza las variables:
DB_HOST
DB_PORT
DB_USER
DB_PASSWORD
DB_NAME
para establecer la conexión.
En el laboratorio se permite:
DB_SSL=false
dentro del entorno local.
Para conectar con Aiven se utiliza:
DB_SSL=true
junto con el certificado CA del servicio.
El proyecto verifica tanto la confianza del certificado como la identidad del servidor.
Modelo de datos y aplicación Node
La tabla de demostración utiliza InnoDB, precios decimales y valores de stock no negativos.
El script se ejecuta dentro de una base existente, por lo que no fija el nombre de la base mediante USE.
Esto facilita recuperar la información en un destino distinto.
CREATE TABLE IF NOT EXISTS productos (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL,
precio DECIMAL(10,2) NOT NULL,
stock INT UNSIGNED NOT NULL,
fecha_creacion TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT precio_no_negativo CHECK (precio >= 0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci;
INSERT IGNORE INTO productos (id, nombre, precio, stock) VALUES
(1, 'Cuaderno', 12.50, 30),
(2, 'Lapicero', 2.00, 50),
(3, 'Carpeta', 8.00, 15);
Express expone las operaciones del inventario y un endpoint de salud.
Las consultas de lectura son públicas.
Para crear o eliminar productos se requiere:
X-API-Key
La clave se configura mediante:
ADMIN_API_KEY
El precio debe ser numérico, tener como máximo dos decimales y no ser negativo.
El stock debe ser un entero no negativo.
Las rutas disponibles en la aplicación se presentan en la siguiente tabla.
Tabla 4. Rutas de la aplicación de inventario.
Las principales rutas son:
GET /
GET /health
GET /productos
POST /productos
DELETE /productos/:id
Las inserciones y eliminaciones utilizan consultas parametrizadas.
Las credenciales de MySQL se mantienen en el servidor y nunca forman parte de la respuesta al navegador.
La siguiente figura muestra la interfaz propia del inventario conectada al servidor MySQL local.
Figura 1. Interfaz propia del inventario conectada al servidor MySQL local.
Exportación del respaldo con mysqldump
El script backup.sh verifica las variables, prepara un directorio temporal privado y ejecuta la exportación.
La contraseña se escribe en un archivo temporal de opciones del cliente y no se coloca como argumento visible del comando.
El archivo temporal se elimina al terminar.
El núcleo de la exportación utiliza las siguientes opciones:
mysqldump --defaults-extra-file="$WORK_DIR/client.cnf" \
--single-transaction --quick --no-tablespaces \
--set-gtid-purged=OFF --column-statistics=0 \
--hex-blob --default-character-set=utf8mb4 \
"$DB_NAME" | gzip -c > "$WORK_DIR/dump.sql.gz"
La opción:
--single-transaction
proporciona una instantánea consistente para tablas transaccionales como InnoDB.
Durante el dump deben evitarse modificaciones de estructura como:
ALTER
DROP
TRUNCATE
La demostración exporta la base utilizada por la aplicación.
No conserva las cuentas y permisos del servidor ni incorpora procedimientos o eventos.
Después de verificar el archivo comprimido, crypto.js deriva una clave con scrypt y una sal aleatoria.
El cifrado utiliza:
AES 256 GCM
junto con un nonce aleatorio y una etiqueta de autenticación.
El resultado se guarda con fecha y un identificador aleatorio, acompañado de un checksum SHA-256.
La frase secreta debe conservarse por separado durante todo el periodo de retención.
Desde PowerShell, la ejecución utiliza el contenedor de herramientas del proyecto.
docker compose --profile tools run --rm tools scripts/backup.sh
Get-ChildItem backups
Los archivos:
.sql.gz.enc
no deben abrirse como si fueran archivos SQL sin cifrar.
El archivo cifrado y su checksum deben conservarse juntos.
La política de siete días se refiere a antigüedad, no a una cantidad exacta de archivos.
Varias ejecuciones manuales pueden producir varias copias durante el mismo día.
Recuperación en una base independiente
La recuperación se diseñó para revisar la copia sin sobrescribir el inventario activo.
El script restore.sh exige un nombre de destino diferente de DB_NAME.
Además:
- Verifica el checksum.
- Autentica el descifrado.
- Comprueba la compresión.
- Confirma que el destino exista.
- Confirma que el destino no contenga tablas.
En el laboratorio, una cuenta con permisos de administración crea la base de recuperación y concede acceso al usuario de la aplicación.
CREATE DATABASE backup_demo_restore
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
GRANT ALL PRIVILEGES
ON backup_demo_restore.*
TO 'demo'@'%';
Después se configura:
RESTORE_DB_NAME=backup_demo_restore
A continuación se selecciona una copia de seguridad.
$backup = Get-ChildItem backups/*.sql.gz.enc |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
Finalmente se ejecuta la restauración.
docker compose --profile tools run --rm tools scripts/restore.sh `
"backups/$($backup.Name)"
La prueba realizada con MySQL 8.0.46 creó un cuarto producto antes de respaldar.
La recuperación importó las cuatro filas y comprobó la coincidencia de:
- Identificador
- Nombre
- Precio
- Stock
- Fecha
Posteriormente se eliminó el producto de prueba desde la API y se verificó que siguiera presente en la copia restaurada.
Los productos recuperados se muestran en la siguiente tabla.
Tabla 5. Productos recuperados en la prueba local.
Los registros recuperados fueron:
ID 1 - Cuaderno - 12.50 - Stock 30
ID 2 - Lapicero - 2.00 - Stock 50
ID 3 - Carpeta - 8.00 - Stock 10
ID 4 - Prueba recuperación - 25.50 - Stock 15
La prueba también confirmó el rechazo de:
- Un archivo modificado.
- Una base de destino con tablas.
- Un destino con el mismo nombre de la base original.
La importación de una base completa no es atómica.
Si falla después de crear objetos, debe revisarse el destino antes de repetir la operación.
MySQL en Aiven y aplicación en Railway
Aiven proporciona el servicio de MySQL.
Desde su consola se obtienen:
- Host
- Puerto
- Usuario
- Contraseña
- Nombre de base
- Certificado CA
Puede utilizarse una base creada para el proyecto o la base disponible en el servicio, como defaultdb si ese es su nombre real.
No se debe asumir que el puerto remoto sea 3306.
Railway aloja la aplicación Node.js.
El servicio utiliza el Dockerfile y las variables de conexión para llegar a Aiven.
railway.json prepara la inicialización de la tabla antes del despliegue y define:
/health
como comprobación de funcionamiento.
Si la tabla ya existe, un nuevo despliegue conserva sus datos.
Las variables necesarias para Railway se presentan en la siguiente tabla.
Tabla 6. Variables de conexión del servicio Railway.
Las variables utilizadas son:
DB_HOST
DB_PORT
DB_USER
DB_PASSWORD
DB_NAME
DB_SSL
DB_SSL_CA
ADMIN_API_KEY
Para la conexión externa:
DB_SSL=true
Después de crear el servicio se genera un dominio público y se comprueban:
/
/health
/productos
La aplicación escucha en:
0.0.0.0
y utiliza el puerto proporcionado por el entorno.
La conexión desde GitHub Actions también necesita alcanzar Aiven.
Si el servicio limita las direcciones de acceso, debe autorizarse el origen del runner o utilizarse un runner con salida permitida.
Las claves se guardan como Secrets y el certificado se configura sin desactivar la verificación TLS.
Pruebas y despliegue automatizado
El proyecto separa el ciclo de publicación del proceso que respalda los datos.
El archivo:
ci.yml
está configurado para:
- Ejecutar pruebas de API.
- Ejecutar pruebas de cifrado.
- Iniciar MySQL 8.4.
- Comprobar una recuperación de laboratorio.
El archivo:
deploy.yml
escucha la finalización de CI para la rama principal y despliega el commit que terminó correctamente.
El evento se define de esta manera:
on:
workflow_run:
workflows: ["CI - MySQL y recuperación"]
types: [completed]
branches: [main]
El job comprueba:
- Que CI haya concluido correctamente.
- Que proceda de un push del mismo repositorio.
- Que
DEPLOY_ENABLEDseatrue.
Posteriormente utiliza un Project Token de Railway y el ID del servicio.
El despliegue se ejecuta mediante:
railway up --service "$RAILWAY_SERVICE_ID"
El comando espera el resultado del despliegue.
Después se consulta la URL pública de:
/health
Para demostrar la automatización, se debe:
- Cambiar un texto de la interfaz.
- Hacer commit.
- Hacer push a
main. - Observar la ejecución de CI.
- Observar el despliegue posterior.
- Comprobar el cambio en la aplicación pública.
Conviene mantener un solo mecanismo de despliegue y desactivar el autodeploy nativo de Railway para evitar publicaciones duplicadas.
Backup remoto programado con GitHub Actions
El archivo:
backup.yml
prepara un contenedor con cliente MySQL 8.4 y ejecuta el mismo procedimiento de exportación, compresión y cifrado utilizado en el laboratorio.
Los parámetros de Aiven y la frase de cifrado se obtienen mediante Secrets.
No se imprime la contraseña ni se sube un dump sin cifrar.
La programación utiliza:
on:
schedule:
- cron: "0 6 * * *"
workflow_dispatch:
Esto configura una ejecución diaria a las 06:00 UTC y también permite ejecutarla manualmente.
Los artefactos se guardan mediante:
- uses: actions/upload-artifact@v4
with:
name: mysql-backup-${{ github.run_id }}
path: |
backups/*.sql.gz.enc
backups/*.sql.gz.enc.sha256
retention-days: 7
if-no-files-found: error
La condición del job requiere:
BACKUP_ENABLED=true
Los Secrets incluyen:
DB_HOST
DB_PORT
DB_USER
DB_PASSWORD
DB_NAME
DB_SSL_CA
BACKUP_PASSPHRASE
Para Aiven también se utiliza:
BACKUP_DB_SSL=true
El usuario del dump necesita los permisos de lectura y de los objetos que exporta el procedimiento.
Para validar la configuración se ejecuta primero:
Run workflow
Después se descarga el artefacto cifrado y se restaura en una base vacía compatible utilizando la misma frase del respaldo.
El historial también deberá revisarse para confirmar una ejecución:
Scheduled
Una prueba manual exitosa no demuestra por sí sola que se haya ejecutado el cron.
GitHub puede retrasar las ejecuciones programadas.
Evidencia y estado de los resultados
La interfaz también se probó en navegador mediante el registro de un producto.
La siguiente figura muestra ese producto dentro del inventario local.
No representa una aplicación ya publicada en Railway.
Figura 2. Registro de un producto desde la interfaz del proyecto local.
Las validaciones realizadas y las actividades pendientes se presentan en la siguiente tabla.
Tabla 7. Validaciones realizadas y actividades pendientes.
Los principales resultados fueron:
- API, validación y cifrado: cuatro pruebas automáticas aprobadas.
- Respaldo y recuperación MySQL local: cuatro filas recuperadas con todos los campos iguales.
- Protecciones de restauración: archivo alterado, destino ocupado y destino original rechazados.
- Navegador: registro de producto y vista móvil comprobados.
- Docker y CI MySQL 8.4: configurados, pendientes de ejecución en la cuenta.
- GitHub público, Aiven y Railway: publicación y configuración externa pendientes.
- Backup remoto manual y Scheduled: pendientes de ejecutar y documentar.
- Video: archivo local de 4 minutos 58 segundos, publicación pendiente.
Los resultados de MySQL corresponden a una prueba real con la versión:
MySQL 8.0.46
Las imágenes de Docker y los workflows están configurados para MySQL 8.4, pero no se atribuyen ejecuciones remotas a esa configuración.
Al terminar la publicación deberán añadirse capturas propias de:
- Repositorio.
- CI.
- Railway.
- URL pública.
- Historial de backups de Aiven.
Conclusiones
La recuperación comprobó que el archivo producido conserva información utilizable y permite revisar un estado anterior del inventario. El destino separado hizo posible validar las filas recuperadas sin importar el dump sobre la base activa.
La solución reúne tres tareas diferentes: probar la aplicación, publicar sus cambios y respaldar los datos. GitHub Actions permite describir cada una por separado. Railway ejecuta la API y Aiven proporciona el MySQL remoto al que deben conectarse tanto la aplicación como el proceso de respaldo.
Esta estrategia resulta adecuada para una base pequeña de demostración. Para producción se necesitarían alertas de fallos, simulacros periódicos, permisos mínimos y otra copia en almacenamiento independiente.
Enlaces del proyecto
Repositorio público del proyecto:
https://github.com/Soanny/mysql-backup-strategy_Stevie-Marca_Soanny-Rivas.git
Aplicación pública en Railway:
https://mysql-backup-strategystevie-marcasoanny-rivas-production.up.railway.app/
Video público:
https://youtu.be/ViPQl8gZ20U
Referencias
Aiven. (s. f.-a). Connect to Aiven for MySQL® from the command line. Recuperado el 3 de octubre de 2026.
https://aiven.io/docs/products/mysql/howto/connect-from-cli
Aiven. (s. f.-b). Connect to Aiven for MySQL® with MySQL Workbench. Recuperado el 3 de octubre de 2026.
https://aiven.io/docs/products/mysql/howto/connect-from-mysql-workbench
GitHub. (s. f.). Downloading workflow artifacts. Recuperado el 3 de octubre de 2026.
https://docs.github.com/en/actions/how-tos/manage-workflow-runs/download-workflow-artifacts
Oracle. (s. f.). 6.5.4 mysqldump — A database backup program. Recuperado el 3 de octubre de 2026.
https://dev.mysql.com/doc/refman/8.4/en/mysqldump.html
Railway. (s. f.-a). Deploying with the CLI. Recuperado el 3 de octubre de 2026.
https://docs.railway.com/cli/deploying
Railway. (s. f.-b). railway up. Recuperado el 3 de octubre de 2026.
https://docs.railway.com/cli/up









Top comments (0)