SSD Nodes Learn Hosting plans →
Guías Matt ConnorPor Matt Connor · Actualizado 2026-10-28

Docker Compose: bind mount 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 tienen 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 contenido multimedia subido. 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 se 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. Si el lado izquierdo empieza por . o /, es una ruta del host y, por tanto, 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 bind mount y :ro lo monta como solo lectura, que es el valor predeterminado adecuado para una configuración que el contenedor nunca debe sobrescribir. Si olvida 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 ls

El volumen no se llama pgdata. Se llama <project>_pgdata, porque el nombre del proyecto toma por defecto el nombre del directorio que contiene el archivo Compose. Un directorio llamado myapp produce myapp_pgdata. Esto es importante porque, si cambia el nombre del directorio, obtiene un volumen nuevo y vacío, y parece que la aplicación ha perdido sus datos. No los ha perdido: el volumen antiguo todavía aparece en docker volume ls. Fije el nombre con name: en el archivo Compose o establezca COMPOSE_PROJECT_NAME si el directorio puede cambiar de ubicación. Las configuraciones de este tipo deben mantenerse junto con sus otros archivos de entorno y secretos de Compose.

Por qué los errores de permisos afectan sobre todo a los bind mounts

Esta es la diferencia práctica principal y se debe sobre todo a una regla: un volumen con nombre vacío en el primer uso se inicializa desde la imagen, pero un bind mount 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 lo que el volumen queda asignado al mismo id numérico y la base de datos se inicia.

Esa inicialización es la única ventaja inicial de un volumen con nombre. Docker gestiona la ubicación del volumen, pero los contenedores lo leen y escriben mediante operaciones normales del sistema de archivos, por lo que el propietario y los permisos se aplican igual que en un bind mount. Un volumen con nombre montado en una ruta que la imagen no tiene, o que otro usuario o imagen llena primero, no hereda ningún propietario de la imagen. Un proceso que no sea root también puede recibir un error de permisos allí. La comprobación de id siguiente funciona con ambos tipos.

Un bind mount funciona al contrario. El contenedor ve exactamente lo que hay 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. El daemon 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 distinto de root no puede escribir en él:

PermissionError: [Errno 13] Permission denied: '/data/app.db'

La solución es hacer coincidir los números. El propietario de un bind mount se compara mediante el id numérico de usuario, no mediante el nombre, porque el contenedor tiene su propio /etc/passwd. Un usuario llamado app dentro del contenedor no tiene significado en el host. Uid 1000 significa uid 1000 en ambos lados, excepto cuando el daemon se ejecuta con reasignación de espacios de nombres de usuario (userns-remap, configurada en /etc/docker/daemon.json o en la línea de comandos de dockerd). La reasignación desplaza cada id del contenedor a un rango subordinado en el host. En el rango de ejemplo de Docker, que empieza en 231072, el uid 0 del contenedor corresponde al uid 231072 del host. Por tanto, el uid 1000 del contenedor corresponde al uid 232072 del host.

id -u
mkdir -p ./data
docker compose exec web id
sudo chown -R 1000:1000 ./data

docker compose exec web id muestra el uid con el que se ejecuta realmente el proceso del contenedor. Sin reasignación, asigne el directorio del host a ese número o fije el contenedor a su número mediante user: "1000:1000" en el servicio. Con la reasignación activa, user: no puede fijar el contenedor al número de su host, porque el id configurado también se desplaza: user: "1000:1000" sigue correspondiendo al uid 232072 del host en ese rango de ejemplo. Cambie el propietario del directorio del host al id asignado en el host. Su propio rango está en /etc/subuid y /etc/subgid. La documentación de Docker indica que sólo --userns=host en docker container create, run o exec permite desactivar la reasignación para un contenedor concreto. Fijar user: es más limpio para una aplicación que usted mismo ha escrito. Cambiar el propietario del directorio del host es más seguro con una imagen que no ha escrito usted, porque algunas imágenes inician un entrypoint como root, eliminan privilegios y esperan propietarios específicos en los directorios internos.

Conviene conocer otras dos trampas. En Fedora, RHEL y otros sistemas con SELinux (security-enhanced Linux) en modo enforcing, se deniega un bind mount hasta que se cambia su etiqueta. Añada :z para una ruta compartida entre contenedores o :Z para una ruta que sólo debe usar un contenedor, escrito como - ./data:/data:Z. Además, un bind mount de un archivo individual, en lugar de un directorio, falla cuando un editor sustituye el archivo en vez de escribir sobre él, porque el montaje sigue el inode original. El contenedor continúa viendo el contenido antiguo hasta que se reinicia. Monte el directorio padre cuando el archivo se edite con frecuencia.

Rendimiento: dónde existe realmente la diferencia

En un servidor Linux, ambos tipos siguen la misma ruta del kernel. La diferencia de rendimiento es tan pequeña que no debería elegir basándose en 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 bind mount se almacena donde usted lo haya indicado.

La diferencia aparece en Docker Desktop para macOS y Windows, donde los contenedores se ejecutan dentro de una máquina virtual. Allí, un bind mount cruza desde el sistema de archivos del host hasta esa máquina virtual mediante una capa de compartición de archivos. 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 PHP, se ralentizan de forma apreciable. Los volúmenes con nombre permanecen dentro de la máquina virtual y no asumen ese coste. Por eso tantos archivos de Compose para desarrollo montan el directorio del código fuente mediante un bind mount, pero declaran un volumen con nombre sobre node_modules.

La otra diferencia real es dónde se almacenan los datos. Un bind mount en /mnt/backup guarda 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ébelo antes de que se convierta en un incidente:

docker system df -v
df -h /var/lib/docker

docker system df -v muestra todos los volúmenes con su tamaño y marca los que ya no utiliza ningún contenedor.

Inspección de un volumen con nombre

Un volumen con nombre no es una caja negra. Pregunte a Docker dónde está:

docker volume inspect myapp_pgdata

El 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 otros controladores de volúmenes no comparten.

La forma segura de inspeccionar el contenido es usar un contenedor temporal que monte el volumen:

docker run --rm -v myapp_pgdata:/vol alpine ls -la /vol

Esto 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 puede gestionarlo. 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 contenido. 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 /data

tar 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 aplicable a ambos tipos. Copiar los archivos de una base de datos mientras está en ejecución genera un archivo de una base de datos que sigue cambiando y puede restaurarse en un estado dañado. Detenga primero el servicio o haga 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 genera 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 operació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 el propietario, los permisos y las marcas de tiempo, por lo que el usuario del contenedor que podía leer el directorio anterior también puede leer el volumen nuevo. Después, cambie el servicio para que use pgdata:/var/lib/postgresql/data, añada pgdata al bloque de nivel superior volumes:, ejecute docker compose up -d y revise los registros de la aplicación antes de eliminar el directorio anterior. Para hacer la operación inversa, use el mismo comando e intercambie /from y /to.

Tenga en cuenta un aspecto durante las pruebas. docker compose down no modifica los volúmenes con nombre, pero docker compose down -v elimina todos los volúmenes con nombre declarados por el proyecto y no hay forma de deshacerlo. Un bind mount sobrevive a ambos comandos porque Docker nunca fue el propietario de ese directorio. Si los comandos del ciclo de vida todavía son nuevos para usted, la guía básica 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 bind mount, 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 inicializa los permisos a partir de la imagen 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 mount en una ruta de ese disco y establezca deliberadamente el propietario 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, bind mounts para la configuración y para el directorio grande que le interesa. Un sistema de soporte como Chatwoot ejecutándose en un VPS encaja exactamente en este modelo, con Postgres en un volumen con nombre y los archivos adjuntos subidos en una ruta que puede incluir en una copia de seguridad.

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, por lo que ambos lados ven el mismo directorio y usted puede editarlo con herramientas normales. Un volumen con nombre es almacenamiento que Docker crea y gestiona, identificado por un nombre y declarado en el bloque de nivel superior volumes:. La diferencia práctica está en el propietario: bind mounts para la configuración que mantiene usted y volúmenes con nombre para los datos que mantiene la aplicación.

¿Por qué recibo "permission denied" con un bind mount y no con un volumen con nombre?

Un volumen con nombre vacío se inicializa desde la imagen, por lo que normalmente hereda el propietario definido por la imagen y el usuario del contenedor puede escribir en él. Sigue aplicando los permisos, así que puede fallar de la misma forma si la imagen no contiene nada en esa ruta. 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 y después sudo chown -R <uid>:<gid> sobre el directorio del host. Cuando el daemon no reasigna espacios de nombres de usuario, también puede establecer user: "1000:1000" en el servicio. Si el daemon usa reasignación de espacios de nombres de usuario, cambie el propietario al id asignado en el host, porque los ids del contenedor se desplazan en el host y user: no cambia ese comportamiento.

¿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 y docker volume inspect <volume> muestra el Mountpoint exacto. Léalo si necesita comprobar algo, pero escriba en él sólo mediante un contenedor, porque editarlo como root en el host cambia el propietario 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, y después archive de uno al otro mediante docker run --rm -v myvol:/data:ro -v "$PWD":/backup alpine tar czf /backup/myvol.tar.gz -C /data .. Para una base de datos, haga un volcado con la herramienta propia de la base de datos en vez de copiar archivos activos, porque una copia 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, y 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.

¿Te resultó útil este artículo?