Persistencia: volúmenes y bind mounts
Hasta ahora hemos tratado los contenedores como recursos reemplazables: podemos detenerlos, eliminarlos y crear otros desde la misma imagen. Eso funciona bien para la aplicación, pero abre una pregunta nueva: ¿qué ocurre con los datos creados después de iniciar el contenedor?
contenedor reemplazable
↓
¿dónde guardamos lo que debe sobrevivir?
El filesystem escribible del contenedor
Una imagen aporta el punto de partida. Cuando creamos un contenedor, este dispone además de una zona escribible donde sus procesos pueden crear o modificar archivos.
imagen: estado inicial
+
contenedor: cambios escritos durante su vida
Esos cambios pertenecen al contenedor. Detenerlo no los elimina: al volver a iniciar el mismo contenedor, siguen ahí. Pero al eliminarlo, eliminamos también esa zona escribible. Crear otro contenedor desde la misma imagen no recupera los datos del anterior.
Los datos temporales pueden vivir allí. Los datos importantes necesitan otro ciclo de vida.
| Tipo | Ejemplo | ¿Debe sobrevivir al contenedor? |
|---|---|---|
| Efímero | caché recreable o archivo temporal | No necesariamente. |
| Persistente | registros de una aplicación o datos de usuario | Sí. |
Separar aplicación y datos nos permite reemplazar el contenedor sin perder aquello que debe permanecer.
Volúmenes: datos gestionados por Docker
Un volumen es almacenamiento gestionado por Docker con identidad propia. Un contenedor puede montarlo en una ruta y leer o escribir allí.
contenedor A ── monta ──┐
▼
volumen datos
▲
contenedor B ── monta ──┘
El volumen no pertenece al contenedor. Ambos pueden eliminarse por separado.
Crear e inspeccionar un volumen
docker volume create tastematch-datos
docker volume ls
docker volume inspect tastematch-datos
create crea el volumen, ls permite localizarlo e inspect muestra información detallada. No necesitas manipular directamente la ruta interna que pueda aparecer: trabajaremos mediante montajes Docker.
Montar con --mount
Vamos a montar el volumen en /data dentro de un contenedor:
Si utilizas PowerShell, escribe este comando en una sola línea, como indica la nota sobre comandos multilínea.
docker run --name escritor \
--mount type=volume,src=tastematch-datos,dst=/data \
alpine:3.22 \
sh -c 'echo "receta favorita: ramen" > /data/favoritas.txt'
Lee el montaje:
type=volume → montamos un volumen
src=tastematch-datos → volumen de origen
dst=/data → ruta dentro del contenedor
El proceso escribe el archivo y termina. Comprueba su estado:
docker ps -a
El experimento decisivo
Elimina el contenedor A:
docker rm escritor
El volumen continúa existiendo:
docker volume ls
Crea un contenedor B distinto y monta el mismo volumen:
docker run --rm --name lector \
--mount type=volume,src=tastematch-datos,dst=/data \
alpine:3.22 \
cat /data/favoritas.txt
La salida esperada es:
receta favorita: ramen
contenedor escritor → escribe → se elimina
│
volumen permanece
│
contenedor lector → lee ────┘
El dato sobrevivió porque estaba en el volumen, no en la zona escribible del contenedor eliminado.
Eliminar el volumen conscientemente
Cuando ya no necesites esos datos y ningún contenedor use el volumen:
docker volume rm tastematch-datos
docker volume ls
Eliminar un volumen puede destruir datos importantes. Inspecciona el nombre y entiende su contenido antes de hacerlo; no uses limpiezas masivas como respuesta automática.
Bind mounts: una ruta concreta del host
Ahora queremos editar una página con nuestro editor y verla desde un contenedor Nginx. Los archivos ya tienen un lugar concreto en el host. Aquí encaja un bind mount.
Crea esta estructura:
sitio/
└── index.html
Contenido inicial:
<h1>TasteMatch en desarrollo</h1>
Desde la carpeta que contiene sitio, ejecuta:
docker run -d --name sitio-dev -p 8080:80 \
--mount type=bind,src="$PWD/sitio",dst=/usr/share/nginx/html \
nginx
type=bind → montamos una ruta del host
src="$PWD/sitio" → origen en nuestro equipo
dst=/usr/share/nginx/html → destino dentro del contenedor
Abre http://localhost:8080. Después modifica sitio/index.html en el host:
<h1>TasteMatch actualizado desde el host</h1>
Recarga la página. El contenedor ve el mismo conjunto de archivos montado; no hemos reconstruido la imagen.
$PWDrepresenta el directorio actual en shells habituales de Linux y macOS. En PowerShell utiliza una ruta absoluta adecuada, por ejemplo${PWD}/sitio, según el entorno. Verifica siempre el origen antes de ejecutar.
COPY no es un bind mount
COPY sitio/ /usr/share/nginx/html/
COPY incorpora archivos durante docker build. La imagen conserva el resultado construido y editar después el host no la modifica.
COPY bind mount
build ejecución
archivo incorporado ruta del host conectada
requiere reconstruir cambios visibles sin rebuild
No son mecanismos intercambiables. COPY prepara la imagen; un bind mount conecta una ruta al crear el contenedor.
Elegir entre volumen y bind mount
| Necesidad | Elección habitual | Motivo |
|---|---|---|
| Datos de una base de datos | Volumen | Docker gestiona el almacenamiento y su ciclo de vida separado. |
| Código que editamos localmente | Bind mount | Necesitamos una ruta conocida y editable en el host. |
| Archivo temporal recreable | Zona escribible del contenedor | No necesita persistencia independiente. |
Son criterios, no reglas universales. Pregunta quién debe gestionar los datos, desde dónde deben editarse y cuánto deben vivir.
Montajes de solo lectura
Si el contenedor solo debe leer los archivos del host, limita el montaje:
docker run -d --name sitio-lectura -p 8081:80 \
--mount type=bind,src="$PWD/sitio",dst=/usr/share/nginx/html,readonly \
nginx
readonly impide que el contenedor escriba mediante ese montaje. El host sigue pudiendo editar sus archivos.
Montar puede ocultar contenido existente
La imagen de Nginx ya contiene archivos en /usr/share/nginx/html. Al montar sitio sobre esa ruta, durante la vida del contenedor vemos el contenido del montaje en ese punto.
ruta en la imagen
↓ se monta encima
contenido del volumen o bind mount
Los archivos originales no se borran de la imagen, pero quedan ocultos en esa ruta mientras existe el montaje. Un directorio de origen vacío puede hacer que parezca que «han desaparecido» archivos.
Rutas y permisos
Un bind mount depende de una ruta real del host. Una ruta equivocada, inexistente o no compartida con Docker Desktop puede producir un error o montar algo distinto de lo esperado.
Los procesos también necesitan permisos compatibles para leer o escribir. Ante Permission denied:
- identifica qué proceso intenta acceder;
- comprueba la ruta y si el montaje es de solo lectura;
- revisa propietario y permisos en el host y dentro del contenedor;
- realiza el cambio mínimo coherente con la necesidad.
No uses chmod 777 como solución general: concede permisos a cualquiera y oculta la causa real.
Errores habituales
- Guardar datos importantes solo dentro del contenedor: desaparecerán al eliminar ese contenedor.
- Confundir volumen y bind mount: el primero lo gestiona Docker; el segundo señala una ruta concreta del host.
- Esperar que
COPYrefleje ediciones posteriores: requiere reconstruir la imagen. - Montar sobre una ruta con contenido: el montaje lo oculta temporalmente.
- Usar una ruta de host equivocada: inspecciona
srcy el directorio actual. - Eliminar el contenedor esperando eliminar el volumen: tienen ciclos de vida distintos.
- Eliminar un volumen sin revisar sus datos: la pérdida puede ser irreversible.
Mini reto: dos necesidades, dos mecanismos
Parte A — Datos que sobreviven
- Crea un volumen llamado
reto-datos. - Monta el volumen en
/datosdentro de un contenedor A. - Escribe dos líneas en
/datos/registro.txt. - Elimina el contenedor A.
- Crea un contenedor B distinto con el mismo volumen.
- Demuestra que ambas líneas permanecen.
- Explica por qué iniciar de nuevo A no era necesario.
Parte B — Archivos editados en el host
- Crea una carpeta
reto-webcon unindex.html. - Móntala en Nginx mediante
type=bindy publica un puerto libre. - Comprueba la página.
- Edita el archivo desde el host y observa el cambio sin reconstruir.
- Repite con un montaje
readonlyy explica qué protege. - Retira únicamente los contenedores del reto.
Entrega los comandos, las comprobaciones y una comparación final:
volumen → datos gestionados por Docker y separados del contenedor
bind → ruta concreta del host conectada al contenedor
COPY → archivos incorporados al construir la imagen
Antes de eliminar reto-datos, decide si necesitas conservar su contenido. La siguiente unidad conectará contenedores entre sí; aquí el objetivo es que sus datos tengan el ciclo de vida correcto.