En el tiempo que llevo usando Docker para desplegar servicios —tanto en desarrollo cómo en producción— siempre me pregunté cómo migrar los volúmenes lógicos (o volúmenes nombrados, según la terminología de Docker) entre entornos, evitando largos periodos de inactividad. Tras investigar un poco, encontré una solución satisfactoria y que no me ha dado problemas.
Recomiendo experimentar sobre datos ficticios o tener un buen backup por si algo sale mal. Usa este instructivo bajo tu propia responsabilidad.
El procedimiento consiste en encapsular el volumen lógico en un contenedor temporal. En lo personal prefiero usar contenedores con bases ligeras cómo puede ser Alpine. Migraremos un volumen lógico de un contenedor PostgreSQL, un ejemplo sencillo y extrapolable a la cantidad de servicios que uno tenga.
La estructura desplegada de la que disponemos es la siguiente:
services:
postgres_db:
image: postgres:16
container_name: postgres_db
restart: always
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: db
ports:
- "5432:5432"
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:
Creación de contenedor temporal
Lo primero que debemos hacer es detener el contenedor que utiliza el volumen a respaldar y no hacerlo “en caliente”. Para ello detenemos el contenedor:
docker compose stop postgres_db
Luego haremos un contenedor temporal basado en una imagen de Alpine para que haga de “wrapper” del volumen a respaldar, es decir, que contenga la información en su interior.
docker run --rm \
--volumes-from postgres_db \
-v "$(pwd)":/backup \
alpine sh -c 'cd /var/lib/postgresql/data && tar -czf /backup/db_data.tar.gz .'
Este comando crea un contenedor temporal (no persistente) que se elimina automáticamente al detenerse. Utiliza los volúmenes de nuestro contenedor principal y ejecuta la cadena de instrucciones dentro de él.
Al montar el directorio actual (<strong>$(pwd)</strong>) como volumen, podemos obtener en nuestra máquina el archivo comprimido con la información del volumen.
La ubicación exacta de los datos depende de la aplicación que esté corriendo el contenedor. Normalmente, la página oficial de la imagen en Docker Hub indica cuál es la ruta de datos que debemos respaldar.
Despliegue en el nuevo entorno
En mi caso, el movimiento de volúmenes será de mi computadora a un servidor remoto. Para ello es de gran utilidad el comando scp, el cual usa SSH para el paso de archivos a un entorno remoto.
scp db_data.tar.gz usuario@direccion_ip:~/tmp
Una vez tengamos los datos en el servidor remoto, para restaurar el volumen debemos desplegar el mismo docker-compose.yaml. Primero desplegamos la aplicación y frenamos para que se cree el volumen y poder reemplazarlo posteriormente.
docker compose up -d postgres_db
docker compose stop postgres_db
Ahora ejecutaremos nuevamente un contenedor temporal para poder extraer la información de él y reemplazar los datos en el volumen del contenedor previamente desplegado:
docker run --rm \
--volumes-from postgres_db \
-v /tmp:/backup \
alpine sh -c 'rm -rf /var/lib/postgresql/data/* && \
tar -xzf /backup/db_data.tar.gz -C \
/var/lib/postgresql/data && \
chown -R 999:999 /var/lib/postgresql/data'
En este paso debemos eliminar los datos actuales del volumen y descomprimir la información en la ruta correspondiente. Luego es necesario asignar el UID y GID tal como se encuentran en un contenedor de PostgreSQL por defecto. Si los permisos no son correctos, el contenedor no podrá arrancar normalmente.
Finalmente levantamos el contenedor normalmente:
docker compose up postgres_db -d
El ID de usuario (UID) y el ID de grupo (GID) del contenedor de PostgreSQL es 999 en ambos casos.
Conclusiones
Si bien este proceso cumple con lo que promete, factores como los permisos de los archivos finales y su ubicación en el contenedor final son cuestiones que deben tenerse en cuenta.
Para el caso puntual de PostgreSQL, puede ser más seguro usar comandos como pg_dump o pg_restore para tener más certezas sobre la integridad de los datos. Sin embargo esto solo sirve para PostgreSQL y la idea es encontrar un procedimiento lo más genérico posible que sirva para todas las ocasiones.
Eso es todo por el momento. Un saludo!
