Todo lo que un contenedor escribe en su sistema de archivos desaparece cuando el contenedor se elimina, algo que ocurre cada vez que actualizas su imagen. Para bases de datos, archivos subidos o cualquier dato que deba sobrevivir, Docker ofrece volúmenes, bind mounts y montajes tmpfs. En este tutorial usarás una base de datos PostgreSQL en Ubuntu 24.04 para comprobar cómo funciona cada tipo, cuándo elegir uno u otro, y cómo hacer y restaurar copias de seguridad de un volumen.
Requisitos previos
- Un servidor con Ubuntu 24.04 LTS y Docker Engine instalado desde el repositorio oficial, por ejemplo un VPS de CubePath.
- Un usuario no root con privilegios
sudoy miembro del grupodocker. - Conocer los comandos básicos de Docker (
docker run,docker ps,docker exec).
Tipos de almacenamiento en Docker
| Tipo | Dónde viven los datos | Uso recomendado |
|---|---|---|
| Volumen con nombre | /var/lib/docker/volumes/, gestionado por Docker | Datos de aplicaciones y bases de datos en producción |
| Bind mount | Cualquier ruta del host que elijas | Archivos de configuración, código en desarrollo |
| tmpfs | Memoria RAM, nunca en disco | Datos temporales o sensibles que no deben persistir |
Los volúmenes son la opción por defecto: Docker gestiona sus permisos, funcionan igual en cualquier servidor, se listan y respaldan con sus propios comandos y no dependen de la estructura de directorios del host.
Paso 1: Comprobar que los datos del contenedor no persisten
Arranca un contenedor, escribe un archivo y elimínalo:
docker run --name prueba alpine sh -c 'echo hola > /datos.txt'
docker rm prueba
Crea otro contenedor a partir de la misma imagen y busca el archivo:
docker run --rm alpine cat /datos.txt
cat: can't open '/datos.txt': No such file or directory
Cada contenedor tiene su propia capa de escritura, que se borra con él. Los datos deben ir a un montaje.
Paso 2: Crear y usar un volumen con nombre
Crea un volumen para los datos de PostgreSQL:
docker volume create pgdata
Compruébalo y consulta dónde lo guarda Docker:
docker volume inspect pgdata
[
{
"CreatedAt": "2026-09-25T10:20:14Z",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/pgdata/_data",
"Name": "pgdata",
"Options": null,
"Scope": "local"
}
]
No modifiques archivos directamente en Mountpoint; accede a los datos siempre a través de un contenedor.
Arranca PostgreSQL montando el volumen en su directorio de datos. La sintaxis --mount es más explícita que -v y es la que recomienda Docker:
docker run -d --name pg \
-e POSTGRES_PASSWORD=your_strong_password \
--mount type=volume,src=pgdata,dst=/var/lib/postgresql/data \
postgres:17
Sustituye your_strong_password por una contraseña segura.
NotaLa ruta
/var/lib/postgresql/dataes la de las imágenes de PostgreSQL hasta la versión 17. A partir depostgres:18la imagen espera el volumen en/var/lib/postgresql. Consulta siempre en la documentación de cada imagen qué ruta debes montar.
Espera unos segundos a que arranque y crea una tabla con un registro:
docker exec pg psql -U postgres -c "CREATE TABLE notas (texto text); INSERT INTO notas VALUES ('dato persistente');"
CREATE TABLE
INSERT 0 1
Paso 3: Verificar que los datos sobreviven al contenedor
Elimina el contenedor por completo:
docker rm -f pg
Crea uno nuevo con el mismo volumen. Como el directorio de datos ya está inicializado, PostgreSQL arranca con la base de datos existente:
docker run -d --name pg \
-e POSTGRES_PASSWORD=your_strong_password \
--mount type=volume,src=pgdata,dst=/var/lib/postgresql/data \
postgres:17
Consulta la tabla:
docker exec pg psql -U postgres -c "SELECT * FROM notas;"
texto
------------------
dato persistente
(1 row)
Este es el mismo proceso que sigues al actualizar una imagen: eliminas el contenedor, descargas la nueva versión y lo recreas con el mismo volumen.
Un detalle útil: si montas un volumen vacío en un directorio de la imagen que ya contiene archivos, Docker copia esos archivos al volumen. Los bind mounts no hacen esto; ocultan el contenido original del directorio.
Paso 4: Montar archivos del host con bind mounts
Un bind mount monta una ruta concreta del host dentro del contenedor. Es la forma natural de proporcionar un archivo de configuración que editas desde el servidor. Crea un directorio con una página para Nginx:
mkdir -p ~/sitio
echo '<h1>Servido desde el host</h1>' > ~/sitio/index.html
Móntalo en modo solo lectura en el directorio que sirve Nginx:
docker run -d --name web -p 127.0.0.1:8080:80 \
--mount type=bind,src="$HOME/sitio",dst=/usr/share/nginx/html,readonly \
nginx:stable
Comprueba la respuesta:
curl http://127.0.0.1:8080
<h1>Servido desde el host</h1>
Modifica el archivo en el host y vuelve a lanzar curl: el cambio se ve al instante, sin reiniciar el contenedor.
Ten en cuenta estas diferencias con los volúmenes:
- La ruta de origen debe ser absoluta. Con
--mount, si no existe, Docker devuelve un error; con-v, crea un directorio vacío propiedad de root, lo que suele ocultar un error tipográfico. - Los permisos son los del host. Si el proceso del contenedor se ejecuta con un UID sin permiso de lectura o escritura sobre esa ruta, falla con
Permission denied. - El contenedor puede modificar archivos del host si no montas con
readonly.
Limpia el ejemplo:
docker rm -f web
Paso 5: Usar tmpfs para datos temporales
Un montaje tmpfs vive solo en memoria y desaparece al detener el contenedor. Es adecuado para cachés o archivos temporales que no quieres escribir en disco. Limita siempre su tamaño:
docker run --rm --mount type=tmpfs,dst=/cache,tmpfs-size=64m alpine df -h /cache
Filesystem Size Used Available Use% Mounted on
tmpfs 64.0M 0 64.0M 0% /cache
Paso 6: Declarar volúmenes en Docker Compose
En Compose, los volúmenes con nombre se declaran en la sección volumes de nivel superior y se referencian desde cada servicio. Crea un proyecto de ejemplo:
mkdir -p ~/pg-compose && cd ~/pg-compose
nano compose.yaml
services:
db:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?define POSTGRES_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data
- ./init:/docker-entrypoint-initdb.d:ro
volumes:
db_data:
db_data es un volumen con nombre, y ./init un bind mount relativo al directorio del proyecto (la imagen de PostgreSQL ejecuta los scripts .sql de esa carpeta la primera vez que inicializa la base de datos). Compose antepone el nombre del proyecto al volumen, que se llamará pg-compose_db_data.
Crea la carpeta init y arranca el servicio pasando la contraseña como variable:
mkdir -p init
POSTGRES_PASSWORD=your_strong_password docker compose up -d
docker volume ls
DRIVER VOLUME NAME
local pg-compose_db_data
local pgdata
docker compose down conserva el volumen; solo docker compose down -v lo elimina.
Paso 7: Hacer una copia de seguridad de un volumen
La forma estándar de respaldar un volumen es montarlo en un contenedor temporal junto a un directorio del host y empaquetarlo con tar. Para bases de datos, detén antes el contenedor que lo usa para que los archivos sean coherentes:
docker stop pg
mkdir -p ~/backups
docker run --rm \
--mount type=volume,src=pgdata,dst=/data,readonly \
--mount type=bind,src="$HOME/backups",dst=/backup \
alpine tar czf /backup/pgdata-$(date +%F).tar.gz -C /data .
docker start pg
Comprueba que el archivo existe y contiene los datos:
ls -lh ~/backups
tar tzf ~/backups/pgdata-*.tar.gz | head -n 5
-rw-r--r-- 1 root root 6.4M Sep 25 10:42 pgdata-2026-09-25.tar.gz
./
./PG_VERSION
./base/
./base/1/
./base/1/112
ConsejoSi no puedes detener la base de datos, usa su herramienta de volcado en caliente, por ejemplo
docker exec pg pg_dumpall -U postgres > ~/backups/pg-$(date +%F).sql. Es la opción preferida para copias diarias programadas.
Paso 8: Restaurar un volumen
Para restaurar, crea un volumen vacío y extrae la copia en él. El ejemplo lo restaura en un volumen nuevo, pgdata_restaurado, para no sobrescribir el original:
docker volume create pgdata_restaurado
docker run --rm \
--mount type=volume,src=pgdata_restaurado,dst=/data \
--mount type=bind,src="$HOME/backups",dst=/backup,readonly \
alpine tar xzf /backup/pgdata-2026-09-25.tar.gz -C /data
Sustituye la fecha por la de tu archivo. Verifica la restauración arrancando un PostgreSQL temporal sobre el volumen:
docker run -d --name pg-test \
-e POSTGRES_PASSWORD=your_strong_password \
--mount type=volume,src=pgdata_restaurado,dst=/var/lib/postgresql/data \
postgres:17
docker exec pg-test psql -U postgres -c "SELECT * FROM notas;"
Debe mostrar el registro dato persistente. tar conserva los propietarios numéricos, así que PostgreSQL puede leer los archivos sin ajustar permisos. Elimina el contenedor de prueba al terminar con docker rm -f pg-test.
Paso 9: Limpiar volúmenes que ya no se usan
Lista los volúmenes que no están montados en ningún contenedor:
docker volume ls --filter dangling=true
Elimina un volumen concreto (falla si algún contenedor, aunque esté detenido, lo usa):
docker volume rm pgdata_restaurado
docker volume prune elimina solo los volúmenes anónimos sin uso. Para incluir también los volúmenes con nombre sin uso, añade -a:
docker volume prune -a
Advertencia
docker volume prune -aborra de forma permanente todos los volúmenes con nombre que no estén montados en ningún contenedor, incluidos los de pilas de Compose que tengas detenidas condown. Revisa antes la lista.
Solución de problemas
Permission denied al escribir en un bind mount. El UID del proceso del contenedor no tiene permisos sobre la ruta del host. Consulta el UID con docker exec nombre id y ajusta el propietario de la carpeta con sudo chown, o usa un volumen con nombre.
bind source path does not exist. La ruta de src en --mount type=bind no existe o no es absoluta. Créala antes o usa "$HOME/..." en lugar de ~.
volume is in use al eliminar un volumen. Un contenedor, aunque esté detenido, lo referencia. Localízalo con docker ps -a --filter volume=nombre_volumen y elimínalo primero.
El disco se llena en /var/lib/docker. Revisa el espacio por volumen con docker system df -v y elimina los que no uses.
Conclusión
Has comprobado que los datos del contenedor son efímeros, has guardado una base de datos en un volumen con nombre que sobrevive a la recreación del contenedor, has usado bind mounts y tmpfs para sus casos concretos, y sabes hacer copias de seguridad de un volumen y restaurarlas. Como siguientes pasos, programa las copias con un timer de systemd, guárdalas fuera del servidor en un almacenamiento de objetos compatible con S3 y revisa periódicamente que se pueden restaurar.
