Docker Compose: bind o volumen con nombre
Aprenda cuándo usar bind mounts o volúmenes con nombre en Docker Compose, evite errores de permisos y sepa cómo inspeccionar, respaldar y migrar datos.
Montaje bind o volumen con nombre: respuesta breve
Los volúmenes de Docker Compose son de dos tipos, y la elección depende de quién administra los archivos. Use un montaje bind para los archivos que usted escribe y lee directamente, como archivos de configuración, plantillas y sitios estáticos. Use un volumen con nombre para los datos que administra la aplicación, como archivos de bases de datos, índices de búsqueda y archivos multimedia cargados. Un montaje bind apunta a una ruta del host que puede abrir en un editor. Un volumen con nombre es almacenamiento que Docker crea y administra, y al que accede mediante Docker.
Ambos aparecen bajo la misma clave volumes: dentro de un servicio, por lo que suelen confundirse. La diferencia está en el lado izquierdo de los dos puntos. Un lado izquierdo que comienza por . o / es una ruta del host, por lo que se trata de un montaje bind. Cualquier otro valor es un nombre, por lo que se trata de un volumen con nombre, y ese nombre también debe declararse en el bloque volumes: de nivel superior.
Las dos sintaxis de un archivo compose
services:
db:
image: postgres:17
environment:
POSTGRES_PASSWORD: changeme
volumes:
- pgdata:/var/lib/postgresql/data
web:
image: nginx:1.27
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./site:/usr/share/nginx/html:ro
volumes:
pgdata:pgdata:/var/lib/postgresql/data es un volumen con nombre. ./nginx.conf:/etc/nginx/nginx.conf es un montaje bind, y :ro lo monta como de solo lectura, que es el valor predeterminado correcto para una configuración que un contenedor nunca debe sobrescribir. Si se omite la entrada de nivel superior volumes:, Compose se detiene con service "db" refers to undefined volume pgdata.
Inícielo y muestre lo que Docker creó:
docker compose up -d
docker volume lsEl volumen no se llama pgdata. Se llama <project>_pgdata, porque el nombre del proyecto toma como valor predeterminado el nombre del directorio que contiene el archivo compose. Un directorio llamado myapp produce myapp_pgdata. Esto es importante porque, al cambiar el nombre del directorio, se crea un volumen vacío y parece que la aplicación perdió sus datos. No los perdió: el volumen anterior todavía aparece en docker volume ls. Fije el nombre con name: en el archivo compose, o defina COMPOSE_PROJECT_NAME si el directorio puede cambiar de ubicación. Los ajustes de este tipo deben estar junto con sus otros archivos de entorno y secretos de Compose.
Por qué los errores de permisos solo afectan a los montajes bind
Esta es la diferencia práctica principal. Se debe a una regla: un volumen con nombre vacío en el primer uso se inicializa a partir de la imagen, pero un montaje bind nunca se inicializa así.
Cuando Docker monta un volumen con nombre vacío sobre un directorio que ya contiene datos en la imagen, copia esos datos al volumen con el propietario y los permisos definidos por la imagen. La imagen oficial de Postgres incluye /var/lib/postgresql/data, propiedad de su usuario postgres. Por eso, el volumen queda con ese mismo id numérico como propietario y la base de datos se inicia.
Un montaje bind hace lo contrario. El contenedor ve exactamente lo que existe en el host, incluido el propietario, y el contenido de la imagen en esa ruta queda oculto. Si el directorio del host no existe, el daemon de Docker lo crea y se ejecuta como root, por lo que se obtiene un directorio propiedad de root:root. Un proceso del contenedor que se ejecuta con un usuario que no es root no puede escribir en él:
PermissionError: [Errno 13] Permission denied: '/data/app.db'La solución es hacer coincidir los números. La propiedad a través de un montaje bind se compara mediante el id de usuario numérico, no mediante el nombre, porque el contenedor tiene su propio /etc/passwd. Un usuario llamado app dentro del contenedor no tiene ningún significado en el host. Uid 1000 significa uid 1000 en ambos lados.
id -u
mkdir -p ./data
docker compose exec web id
sudo chown -R 1000:1000 ./datadocker compose exec web id muestra el uid con el que se ejecuta realmente el proceso del contenedor. Cambie el propietario del directorio del host para que coincida con ese número o asigne el contenedor a su número mediante user: "1000:1000" en el servicio. Fijar user: es más limpio para una aplicación que haya escrito usted mismo. Cambiar el propietario del directorio del host es más seguro para una imagen que no haya escrito usted, porque algunas imágenes inician un entrypoint como root, reducen sus privilegios y esperan una propiedad específica en los directorios internos.
También debe conocer otras dos situaciones problemáticas. En Fedora, RHEL y otros sistemas con SELinux (security-enhanced Linux) en modo enforcing, se deniega un montaje bind hasta que se cambia su etiqueta. Por eso, añada :z a una ruta compartida entre contenedores o :Z a una ruta que solo debe usar un contenedor, escrito como - ./data:/data:Z. Además, un montaje bind de un solo archivo, en lugar de un directorio, falla cuando un editor reemplaza el archivo en vez de escribir directamente en él, porque el montaje sigue el inode original. El contenedor continúa viendo el contenido antiguo hasta que lo reinicie. Monte el directorio padre cuando el archivo se edite con frecuencia.
Rendimiento: dónde existe realmente la diferencia
En un servidor Linux, ambos tipos pasan por la misma ruta del kernel. Por eso, la diferencia de rendimiento es lo bastante pequeña como para que no debas elegir en función de ella. Los volúmenes con nombre que usan el controlador predeterminado local se almacenan en el mismo sistema de archivos que el resto de Docker, en /var/lib/docker/volumes/. Un montaje bind se almacena donde lo hayas apuntado.
La diferencia aparece en Docker Desktop para macOS y Windows, donde los contenedores se ejecutan dentro de una máquina virtual. Allí, un montaje bind atraviesa una capa de uso compartido de archivos desde el sistema de archivos del host hasta esa máquina virtual. Las cargas de trabajo con muchas operaciones sobre archivos pequeños, como un árbol de dependencias de Node.js o la caché de un framework de PHP, se ralentizan de forma apreciable. Los volúmenes con nombre permanecen dentro de la máquina virtual y no tienen ese coste. Por eso muchos archivos de Compose para desarrollo montan el directorio de código fuente mediante bind, pero declaran un volumen con nombre sobre node_modules.
La otra diferencia real es dónde se almacenan los datos. Un montaje bind en /mnt/backup coloca los datos en ese disco. Un volumen con nombre se almacena en el sistema de archivos que contiene /var/lib/docker, que en un VPS suele ser el disco raíz. Una base de datos que crece dentro de un volumen con nombre llena el mismo disco donde se encuentran los registros del sistema. Compruébalo antes de que se convierta en un incidente:
docker system df -v
df -h /var/lib/dockerdocker system df -v muestra todos los volúmenes con su tamaño y marca aquellos a los que ningún contenedor hace referencia.
Inspección de un volumen con nombre
Un volumen con nombre no es una caja negra. Pida a Docker su ubicación:
docker volume inspect myapp_pgdataEl campo Mountpoint proporciona una ruta real del host, normalmente /var/lib/docker/volumes/myapp_pgdata/_data. Puede leerla con sudo ls, lo que resulta útil para una comprobación rápida. No la trate como un lugar para editar archivos. Escribir allí como root vuelve a crear el problema de propiedad descrito anteriormente, y la ruta es un detalle del controlador local que no comparten otros controladores de volumen.
La forma segura de examinar el contenido es usar un contenedor temporal que monte el volumen:
docker run --rm -v myapp_pgdata:/vol alpine ls -la /volEsto funciona con cualquier controlador, muestra los mismos permisos que ve el contenedor real y no deja nada atrás gracias a --rm.
Copia de seguridad de cada tipo
Un bind mount es un directorio normal, por lo que cualquier herramienta de copia de seguridad a nivel de archivo ya lo gestiona. Apunte la copia de seguridad a la ruta del host y habrá terminado. Un volumen con nombre requiere un paso adicional, porque la herramienta debe acceder a su interior. Monte el volumen y un directorio del host en el mismo contenedor temporal y, después, escriba un archivo:
docker run --rm \
-v myapp_pgdata:/data:ro \
-v "$PWD":/backup \
alpine tar czf /backup/pgdata.tar.gz -C /data .Restaure invirtiendo el proceso en un volumen nuevo:
docker volume create myapp_pgdata_restored
docker run --rm \
-v myapp_pgdata_restored:/data \
-v "$PWD":/backup \
alpine tar xzf /backup/pgdata.tar.gz -C /datatar conserva la propiedad numérica cuando se ejecuta como root dentro del contenedor. Esto permite que la aplicación utilice el volumen restaurado.
Hay una advertencia que se aplica a ambos tipos. Copiar los archivos de una base de datos mientras está en ejecución produce un archivo de una base de datos que cambia continuamente y puede restaurarse en un estado dañado. Detenga primero el servicio o genere un volcado mediante la herramienta propia de la base de datos, como en docker compose exec -T db pg_dump -U postgres appdb > appdb.sql. Esto produce un archivo normal que después puede incluir en una rutina normal de copia de seguridad cifrada con restic junto con los archivos de compose.
Migrar un bind mount a un volumen con nombre
La migración es una copia, no un cambio de nombre, y tarda aproximadamente un minuto.
docker compose down
docker volume create myapp_pgdata
docker run --rm \
-v "$PWD/data":/from \
-v myapp_pgdata:/to \
alpine sh -c 'cp -a /from/. /to/'cp -a conserva la propiedad, los permisos y las marcas de tiempo. Por eso, el usuario del contenedor que podía leer el directorio anterior también puede leer el volumen nuevo. Después, cambia el servicio para que use pgdata:/var/lib/postgresql/data, añade pgdata al bloque de nivel superior volumes:, ejecuta docker compose up -d y revisa los logs de la aplicación antes de eliminar el directorio anterior. Para hacerlo en sentido contrario, usa el mismo comando e intercambia /from y /to.
Ten en cuenta un aspecto durante las pruebas. docker compose down deja intactos los volúmenes con nombre, pero docker compose down -v elimina todos los volúmenes con nombre declarados por el proyecto y no se puede deshacer. Un bind mount sobrevive a ambos comandos porque Docker nunca fue propietario de ese directorio. Si los comandos del ciclo de vida todavía son nuevos para ti, la guía de conceptos básicos de Docker Compose para un VPS los explica paso a paso.
Elección, servicio por servicio
Pregunte quién escribe el archivo. La configuración que edita en un editor de texto y confirma en git debe estar en un montaje bind, montado en :ro, porque necesita que sea visible y tenga control de versiones. El estado de la aplicación que nunca abre manualmente debe estar en un volumen con nombre, porque Docker configura los permisos correctamente y los datos no dependen de una ruta del host.
El caso mixto son los archivos multimedia. La aplicación escribe una biblioteca de fotos, pero usted también la administra, y a menudo es lo bastante grande como para requerir un disco específico. Móntela mediante bind en una ruta de ese disco y establezca la propiedad de forma deliberada una sola vez. Este es el patrón que adoptan la mayoría de las pilas autoalojadas: volúmenes con nombre para bases de datos y cachés, y montajes bind para la configuración y para el directorio grande que le interesa.
FAQ
¿Cuál es la diferencia entre un bind mount y un volumen con nombre?
Un bind mount asigna una ruta del host al contenedor, de modo que ambos lados ven el mismo directorio y puede editarlo con las herramientas habituales. Un volumen con nombre es almacenamiento que Docker crea y administra, identificado por su nombre y declarado en el bloque volumes: de nivel superior. La diferencia práctica es la propiedad: use bind mounts para la configuración que mantiene usted y volúmenes con nombre para los datos que mantiene la aplicación.
¿Por qué aparece "permission denied" con un bind mount, pero no con un volumen con nombre?
Un volumen con nombre vacío se inicializa a partir de la imagen. Por eso hereda la propiedad definida por la imagen y el usuario del contenedor puede escribir en él. Un bind mount muestra el directorio del host exactamente como está. Si Docker tuvo que crear ese directorio, lo creó con root como propietario. Ejecute docker compose exec <service> id para ver el id numérico que usa el contenedor. Después, ejecute sudo chown -R <uid>:<gid> sobre el directorio del host o establezca user: "1000:1000" en el servicio.
¿Dónde almacena Docker los volúmenes con nombre en el disco?
Con el controlador predeterminado local, se almacenan en /var/lib/docker/volumes/<volume>/_data. docker volume inspect <volume> muestra el Mountpoint exacto. Léalo si necesita comprobar algo, pero escriba en él únicamente desde un contenedor. Editarlo como root en el host cambia la propiedad de formas que el contenedor no espera.
¿Cómo hago una copia de seguridad de un volumen con nombre?
Ejecute un contenedor temporal con el volumen y un directorio del host montados. Después, archive los datos de uno al otro con docker run --rm -v myvol:/data:ro -v "$PWD":/backup alpine tar czf /backup/myvol.tar.gz -C /data .. Para una base de datos, use la herramienta propia de la base de datos para hacer un volcado en lugar de copiar archivos activos. Una copia de archivos realizada mientras hay escrituras en curso puede restaurarse en un estado corrupto.
¿docker compose down elimina mis volúmenes?
docker compose down elimina los contenedores y las redes, pero deja los volúmenes con nombre en su lugar. docker compose down -v también elimina permanentemente todos los volúmenes con nombre declarados por el proyecto. Ninguno de los dos comandos elimina los bind mounts, porque ese directorio pertenece al host y no a Docker.