Saltar al contenido

Capítulo 5 de 13

Datos que sobreviven y se pueden recuperar

Elegir almacenamiento, distinguir persistencia de backup y ensayar una restauración real de PostgreSQL.

#Persistir no es hacer una copia de seguridad

Un volumen separa los datos de la vida de un contenedor. Permite eliminar una instancia y montar los mismos datos en la siguiente. No te protege de borrar una fila por error, de una aplicación que corrompe datos, de perder el disco ni de perder el servidor completo. Un backup es una copia recuperable, idealmente fuera del fallo que intentas sobrevivir.

Si una aplicación permite subir imágenes y las guarda dentro del contenedor, reemplazarlo puede hacerlas desaparecer. Si guarda los datos en un volumen, reemplazarlo los conserva. Si el usuario borra por error las imágenes del volumen, necesitas una copia anterior. Son garantías distintas.

Contenedor reemplazable conectado a un volumen; backup separado; restauración hacia otro volumen para verificarla.Ampliar diagrama ↗

El volumen mantiene los datos entre instancias. La restauración comprobada demuestra que una copia sirve para recuperar el servicio.

#Cuatro formas de dar archivos a un contenedor

Mecanismo Quién controla la ubicación Uso habitual Límite importante
Capa escribible del contenedor Docker, ligada a la instancia Temporales que puedes perder Desaparece al eliminar esa instancia
Bind mount Una ruta concreta del host Código local, configuración o archivo exportado Acopla permisos y rutas del host
Volumen con nombre Docker, independiente de la instancia Datos de base de datos o archivos persistentes Sigue dependiendo de la máquina y el almacenamiento
tmpfs Memoria del entorno de ejecución Temporales que no necesitan persistir Consume memoria y se pierde al parar

Un montaje hace aparecer un almacenamiento en una ruta del contenedor. Si montas sobre una carpeta con archivos de la imagen, el montaje oculta lo que había debajo. No se han borrado necesariamente los archivos de la imagen, pero el proceso ve el contenido montado. Un volumen vacío puede poblarse inicialmente con contenido de la imagen según las reglas de Docker; no bases una migración de datos en ese efecto implícito.

docker volume create notas-practica
docker run --rm \
  --mount type=volume,src=notas-practica,dst=/datos \
  python:3.13-slim python -c \
  'from pathlib import Path; Path("/datos/nota.txt").write_text("sigue aquí")'
docker run --rm \
  --mount type=volume,src=notas-practica,dst=/datos,readonly \
  python:3.13-slim python -c \
  'from pathlib import Path; print(Path("/datos/nota.txt").read_text())'

El primer contenedor termina y se elimina por --rm. El segundo, una instancia distinta, lee sigue aquí. El archivo no pertenece a la imagen de Python ni a la segunda instancia: está en el volumen. readonly evita que el lector lo modifique a través de ese montaje.

#Permisos: importa el número, no el nombre bonito

Linux evalúa identificadores numéricos de usuario y grupo, o UID/GID. Un archivo del host propiedad del UID 1000 no pasa a pertenecer al usuario app de UID 10001 porque lo montes en su contenedor. Si no puede escribir, revisa propietario, grupo, permisos y el usuario real del proceso.

No arregles todo con chmod 777 o ejecutando la aplicación como root. Prepara el directorio con el propietario apropiado, usa un grupo compartido deliberado o deja que la imagen oficial inicialice su volumen según su contrato. Los sistemas rootless y los remapeos de usuario añaden otra traducción; verifica con id y ls -ln en ambos lados si cambian los números.

#Una base de datos no es una carpeta cualquiera

PostgreSQL es un servidor de base de datos relacional: almacena tablas y atiende consultas SQL. Una tabla organiza filas con columnas. Una transacción agrupa cambios que deben confirmarse juntos o descartarse. El servidor coordina escrituras y recuperación mediante sus propios archivos y registros.

Copiar esos archivos mientras está escribiendo puede producir una mezcla de momentos incompatibles. Para este tamaño de aplicación, un backup lógico con pg_dump es un punto de partida claro: pide a PostgreSQL un conjunto consistente de datos y estructura. Para bases grandes o recuperación a un instante concreto existen copias físicas y archivos WAL; el salto se justifica por tiempos de recuperación y pérdida de datos tolerable, no por prestigio técnico.

#Preparar una base desechable

Crea una carpeta privada secrets y otra backups en tu carpeta de práctica. Los comandos usan OpenSSL, una herramienta de criptografía disponible en muchos sistemas; aquí solo genera una contraseña aleatoria. Si no la tienes, genera una con un gestor de contraseñas y guárdala como texto en el archivo indicado.

mkdir -p secrets backups
chmod 700 secrets backups
openssl rand -hex 32 > secrets/db_password
chmod 600 secrets/db_password
docker volume create pg-practica
docker run -d --name db-practica \
  -e POSTGRES_USER=curso -e POSTGRES_DB=curso \
  -e POSTGRES_PASSWORD_FILE=/run/secrets/db_password \
  --mount type=bind,src="$(pwd)/secrets/db_password",dst=/run/secrets/db_password,readonly \
  --mount type=volume,src=pg-practica,dst=/var/lib/postgresql/data \
  postgres:17-alpine

Esta práctica usa PostgreSQL 17. Su ruta de datos es /var/lib/postgresql/data; no traslades sin mirar esta ruta a otras versiones mayores: la imagen de PostgreSQL 18 cambió su organización de datos. _FILE es una convención soportada por esta imagen para leer la contraseña desde un archivo. No todas las imágenes reconocen esa convención.

POSTGRES_USER=curso crea aquí un superusuario de laboratorio. Una aplicación pública debe tener un rol limitado distinto del administrador; el proyecto final hace esa separación. No se publica ningún puerto: administraremos la práctica mediante docker exec.

docker exec db-practica pg_isready -h 127.0.0.1 -U curso -d curso

Espera hasta ver accepting connections; durante el primer arranque puede tardar. -h 127.0.0.1 comprueba TCP: evita confundir el servidor temporal de inicialización por socket Unix con el servidor definitivo. Luego crea datos:

docker exec db-practica psql -U curso -d curso -v ON_ERROR_STOP=1 \
  -c "CREATE TABLE notas (id integer PRIMARY KEY, texto text NOT NULL); INSERT INTO notas VALUES (1, 'Primera copia');"

psql es el cliente SQL incluido en la imagen. -c ejecuta una orden. PRIMARY KEY identifica cada fila sin duplicados y NOT NULL obliga a que tenga texto. ON_ERROR_STOP hace que un error produzca fallo del comando, importante al automatizar: no queremos anunciar una migración exitosa si una sentencia falló.

#Crear un backup cuyo resultado puedes comprobar

docker exec db-practica pg_dump -U curso -d curso -Fc > backups/curso.dump

pg_dump genera el backup; -Fc utiliza el formato de archivo de PostgreSQL, que permite restauración selectiva y compresión. La redirección > la hace tu shell en el host, así que el archivo queda fuera del contenedor. No añadas -t: un pseudo-terminal no corresponde a una transferencia binaria.

Un archivo existente no prueba que el comando haya tenido éxito: el shell puede haberlo creado antes de que pg_dump fallara. En un script, comprueba el código de salida, escribe primero a .tmp y renombra solo tras éxito. Si usas tuberías, conoce set -o pipefail para que un fallo anterior no quede oculto por la compresión final.

Este dump cubre una base, no necesariamente roles globales, configuraciones, archivos subidos por usuarios o claves. Define el conjunto que necesitas para reconstruir la aplicación. En PostgreSQL puedes respaldar roles con las herramientas adecuadas, pero una configuración declarativa de los roles del proyecto también ayuda a recrearlos.

#Restaurar en otro sitio es parte del backup

Una restauración ensayada debe evitar sobreescribir la única base que funciona. Arranca una segunda instancia y un segundo volumen:

docker volume create pg-restauracion
docker run -d --name db-restauracion \
  -e POSTGRES_USER=curso -e POSTGRES_DB=curso \
  -e POSTGRES_PASSWORD_FILE=/run/secrets/db_password \
  --mount type=bind,src="$(pwd)/secrets/db_password",dst=/run/secrets/db_password,readonly \
  --mount type=volume,src=pg-restauracion,dst=/var/lib/postgresql/data \
  postgres:17-alpine
docker exec db-restauracion pg_isready -h 127.0.0.1 -U curso -d curso

Cuando esté lista:

docker exec -i db-restauracion pg_restore \
  -U curso -d curso --exit-on-error --no-owner < backups/curso.dump
docker exec db-restauracion psql -U curso -d curso \
  -c 'SELECT id, texto FROM notas ORDER BY id;'

-i mantiene la entrada para enviar el archivo al contenedor. Esperas la fila 1 | Primera copia. --exit-on-error detiene la restauración ante errores; --no-owner evita intentar asignar propietarios originales en un destino que puede usar otros roles. En una restauración real también debes revisar permisos y hacer una prueba de lectura/escritura con el usuario de aplicación, no solo con el administrador.

#Qué medir y dónde guardar la copia

Dos números convierten "tenemos backups" en una decisión operativa: cuánto dato puedes perder —por ejemplo, como máximo una hora— y cuánto puedes tardar en volver —por ejemplo, treinta minutos—. Si haces una copia diaria, no cumples una pérdida máxima de una hora. Si restaurar tarda tres horas, una alerta en diez segundos no cambia ese límite.

Guarda copias fuera del servidor, con acceso restringido y cifrado apropiado; verifica que también podrás obtener la clave durante una incidencia. Mantén varias edades porque puedes descubrir una corrupción días después. Prueba restauraciones periódicamente y después de cambios mayores de versión o esquema. Registra duración, tamaño, fecha y resultado, sin incluir contraseñas.

#Eliminar sin confundir datos con cómputo

docker rm elimina una instancia; docker volume rm elimina almacenamiento. En Compose, down conserva por defecto los volúmenes nombrados, mientras que down -v puede eliminarlos. Nunca lo presentes como un comando inocuo de reinicio.

Cuando hayas verificado la copia, esta limpieza elimina solo los datos de esta práctica:

docker stop db-practica db-restauracion
docker rm db-practica db-restauracion
docker volume rm pg-practica pg-restauracion notas-practica

La copia backups/curso.dump permanece en el host. No reutilices nombres de una aplicación importante para hacer estos experimentos.

#Referencias para profundizar

    ↑ ↓ para recorrer · Intro para abrir · Ctrl K para buscar