---
title: "Volúmenes en Docker: cómo persistir y respaldar datos"
description: "Guarda datos persistentes en Docker con volúmenes, bind mounts y tmpfs, úsalos en Docker Compose y haz copias de seguridad y restauraciones en Ubuntu 24.04."
category: "Docker Kubernetes"
language: es
source: https://cubepath.com/es/docs/docker-kubernetes/volumenes-docker-persistencia-de-datos
updated: 2026-09-25
---
# Volúmenes en Docker: cómo persistir y respaldar datos
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 `sudo` y miembro del grupo `docker`.
- 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:
```bash
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:
```bash
docker run --rm alpine cat /datos.txt
```
```output
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:
```bash
docker volume create pgdata
```
Compruébalo y consulta dónde lo guarda Docker:
```bash
docker volume inspect pgdata
```
```output
[
{
"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:
```bash
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.
> Nota: La ruta `/var/lib/postgresql/data` es la de las imágenes de PostgreSQL hasta la versión 17. A partir de `postgres:18` la 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:
```bash
docker exec pg psql -U postgres -c "CREATE TABLE notas (texto text); INSERT INTO notas VALUES ('dato persistente');"
```
```output
CREATE TABLE
INSERT 0 1
```
## Paso 3: Verificar que los datos sobreviven al contenedor
Elimina el contenedor por completo:
```bash
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:
```bash
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:
```bash
docker exec pg psql -U postgres -c "SELECT * FROM notas;"
```
```output
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:
```bash
mkdir -p ~/sitio
echo '
Servido desde el host
' > ~/sitio/index.html
```
Móntalo en modo solo lectura en el directorio que sirve Nginx:
```bash
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:
```bash
curl http://127.0.0.1:8080
```
```output
Servido desde el host
```
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:
```bash
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:
```bash
docker run --rm --mount type=tmpfs,dst=/cache,tmpfs-size=64m alpine df -h /cache
```
```output
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:
```bash
mkdir -p ~/pg-compose && cd ~/pg-compose
nano compose.yaml
```
```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:
```bash
mkdir -p init
POSTGRES_PASSWORD=your_strong_password docker compose up -d
docker volume ls
```
```output
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:
```bash
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:
```bash
ls -lh ~/backups
tar tzf ~/backups/pgdata-*.tar.gz | head -n 5
```
```output
-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
```
> Consejo: Si 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:
```bash
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:
```bash
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:
```bash
docker volume ls --filter dangling=true
```
Elimina un volumen concreto (falla si algún contenedor, aunque esté detenido, lo usa):
```bash
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`:
```bash
docker volume prune -a
```
> Advertencia: `docker volume prune -a` borra 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 con `down`. 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.