Saltar al contenido principal

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.

TipoEjemplo¿Debe sobrevivir al contenedor?
Efímerocaché recreable o archivo temporalNo necesariamente.
Persistenteregistros de una aplicación o datos de usuarioSí.

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.

$PWD representa 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​

NecesidadElección habitualMotivo
Datos de una base de datosVolumenDocker gestiona el almacenamiento y su ciclo de vida separado.
Código que editamos localmenteBind mountNecesitamos una ruta conocida y editable en el host.
Archivo temporal recreableZona escribible del contenedorNo 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:

  1. identifica qué proceso intenta acceder;
  2. comprueba la ruta y si el montaje es de solo lectura;
  3. revisa propietario y permisos en el host y dentro del contenedor;
  4. 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 COPY refleje ediciones posteriores: requiere reconstruir la imagen.
  • Montar sobre una ruta con contenido: el montaje lo oculta temporalmente.
  • Usar una ruta de host equivocada: inspecciona src y 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​

  1. Crea un volumen llamado reto-datos.
  2. Monta el volumen en /datos dentro de un contenedor A.
  3. Escribe dos líneas en /datos/registro.txt.
  4. Elimina el contenedor A.
  5. Crea un contenedor B distinto con el mismo volumen.
  6. Demuestra que ambas líneas permanecen.
  7. Explica por qué iniciar de nuevo A no era necesario.

Parte B — Archivos editados en el host​

  1. Crea una carpeta reto-web con un index.html.
  2. Móntala en Nginx mediante type=bind y publica un puerto libre.
  3. Comprueba la página.
  4. Edita el archivo desde el host y observa el cambio sin reconstruir.
  5. Repite con un montaje readonly y explica qué protege.
  6. 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.