Cuando borras un contenedor, sus datos se van con él. Ese es el detalle que a todo el mundo le muerde la primera vez: actualizas la imagen, recreas el contenedor y de repente la base de datos está vacía. La solución es sacar los datos fuera del contenedor, y Docker te da dos herramientas para ello: los volúmenes con nombre y los bind mounts. Aquí verás en qué se diferencian, cómo montarlos en Docker Compose, cómo tratar los permisos y cómo hacer una copia de seguridad que de verdad puedas restaurar. La misma guía está disponible en inglés.

Puntos clave

  • Un contenedor es efímero: su capa de escritura desaparece al borrarlo. Para conservar datos hay que montarlos fuera, en un volumen o en un bind mount.
  • Los volúmenes en Docker los gestiona el propio motor y viven en /var/lib/docker/volumes/<nombre>/_data. Son la opción recomendada para datos de aplicaciones y bases de datos.
  • Un bind mount enlaza una carpeta concreta del host dentro del contenedor. Va perfecto para configuración, código en desarrollo y copias de seguridad.
  • En Docker Compose declaras los volúmenes con nombre en el bloque volumes: de nivel superior; los bind mounts se escriben con una ruta relativa como ./datos:/ruta.
  • Los permisos son la causa número uno de errores: muchas imágenes usan las variables PUID y PGID (por defecto 1000) para escribir con tu usuario.

¿Por qué los datos de un contenedor no persisten?

Un contenedor arranca a partir de una imagen de solo lectura y le añade encima una fina capa de escritura. Todo lo que el proceso crea (ficheros temporales, la base de datos, los ficheros subidos) vive en esa capa. En cuanto ejecutas docker rm o recreas el servicio con una imagen nueva, la capa se descarta y con ella todo lo que había dentro. Es el comportamiento por defecto desde las primeras versiones de Docker, y no es un fallo: mantiene los contenedores ligeros y reproducibles.

El problema aparece cuando el proceso guarda algo que quieres conservar. Ahí es donde entra el almacenamiento persistente. La documentación oficial lo dice sin rodeos: «Los volúmenes son el mecanismo preferido para conservar los datos generados y usados por los contenedores de Docker». Si vienes de instalar el motor, este paso encaja justo después de instalar Docker en Ubuntu 24.04 y de entender las redes en Docker.

Volúmenes con nombre frente a bind mounts

Los dos montan datos dentro del contenedor, pero se comportan de forma distinta.

Un volumen con nombre es un espacio que Docker crea y administra por ti. Según la documentación, «los volúmenes los gestiona Docker y están aislados de la funcionalidad principal de la máquina anfitriona». No te preocupas de dónde acaban los ficheros: Docker los coloca bajo /var/lib/docker/volumes/ y los identifica por un nombre. Son portables, fáciles de respaldar y no dependen de la estructura de carpetas del host.

Un bind mount es lo contrario: eliges tú la carpeta del host y Docker la enlaza dentro del contenedor. La documentación lo define así: «Cuando usas un bind mount, un fichero o directorio de la máquina anfitriona se monta desde el host dentro de un contenedor». Tienes control total sobre la ruta, lo que resulta ideal para inyectar un fichero de configuración o para editar código y verlo en vivo. El precio es acoplamiento: si esa carpeta no existe o cambia de permisos, el contenedor lo nota. Además, un bind mount tiene acceso de escritura al host por defecto, así que conviene añadir la opción :ro (solo lectura) cuando el contenedor no deba modificar nada.

La regla práctica es sencilla. Para los datos de una aplicación o de una base de datos, usa un volumen con nombre. Para configuración, para código en desarrollo o para exponer una carpeta de copias, usa un bind mount.

Cómo crear y montar un volumen

Puedes crear un volumen a mano y examinarlo:

docker volume create datos_app
docker volume inspect datos_app
docker volume ls

El comando inspect te muestra el Mountpoint, que apuntará a /var/lib/docker/volumes/datos_app/_data. En la práctica casi nunca lo creas a mano: lo declaras en docker-compose.yml y Docker lo genera al levantar el servicio. Aquí tienes un ejemplo completo con PostgreSQL 17.5 que combina un volumen con nombre para los datos y un bind mount para las copias:

services:
  db:
    image: postgres:17.5
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: cambia_esta_clave
      POSTGRES_DB: app
    volumes:
      - db_data:/var/lib/postgresql/data
      - ./backups:/backups:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d app"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  db_data:

Fíjate en dos cosas. Primero, db_data:/var/lib/postgresql/data es el volumen con nombre: Docker lo administra y sobrevive a docker compose down. Segundo, ./backups:/backups:ro es el bind mount, una carpeta real de tu proyecto montada en modo solo lectura. El bloque volumes: de nivel superior es lo que convierte db_data en un volumen gestionado y no en un simple bind mount. Sobre por qué añadimos restart y healthcheck, seguimos la convención de Compose v2 (sin clave version:) y fijamos una etiqueta concreta en lugar de :latest, que apuntaría a una versión distinta cada vez que la descargues.

Para arrancar y comprobar el estado:

docker compose up -d
docker compose ps
docker volume ls

Copias de seguridad y restauración de volúmenes

Un volumen con nombre no es un fichero que puedas copiar sin más, porque vive dentro de /var/lib/docker. El patrón habitual es lanzar un contenedor efímero de Alpine que monte el volumen y lo empaquete en un .tar.gz sobre una carpeta del host:

docker run --rm \
  -v miproyecto_db_data:/data:ro \
  -v "$(pwd)":/backup \
  alpine tar czf /backup/db_data.tar.gz -C /data .

El contenedor monta el volumen en solo lectura, comprime su contenido y deja db_data.tar.gz en tu directorio actual. Cuando termina, --rm lo elimina. Restaurar es el mismo movimiento a la inversa: montas el volumen destino con permiso de escritura y descomprimes dentro.

docker run --rm \
  -v miproyecto_db_data:/data \
  -v "$(pwd)":/backup \
  alpine sh -c "cd /data && tar xzf /backup/db_data.tar.gz"

Ese .tar.gz ya lo puedes subir a un destino externo. Si quieres automatizar copias cifradas y programadas hacia S3 o una carpeta remota, un servicio como Duplicati hace justo eso apoyándose en este mismo mecanismo.

Permisos y usuario (PUID/PGID)

El error más frecuente con volúmenes no tiene que ver con Docker, sino con los permisos de Unix. El proceso dentro del contenedor escribe con un identificador de usuario (UID) que quizá no coincide con el tuyo en el host, y entonces aparecen los temidos «Permission denied» sobre un bind mount.

Muchas imágenes populares, sobre todo las de LinuxServer.io, resuelven esto con dos variables de entorno: PUID y PGID. Le dices al contenedor con qué usuario y grupo debe escribir, y así los ficheros aparecen en el host con tu propietario. El valor por defecto suele ser 1000, que es el primer usuario que crea Linux:

services:
  app:
    image: lscr.io/linuxserver/bookstack:latest
    environment:
      PUID: 1000
      PGID: 1000
    volumes:
      - ./config:/config

Para conocer tu UID y GID reales, ejecuta id -u e id -g en el host. Con un volumen con nombre este problema es menos habitual, porque Docker gestiona el propietario, pero con bind mounts conviene tenerlo siempre presente.

tmpfs y datos efímeros

No todo lo que un contenedor escribe merece sobrevivir. Un caché, un fichero de sesión o unos datos sensibles que no quieres que toquen el disco encajan mejor en un montaje tmpfs, que vive solo en la memoria RAM del host y desaparece al parar el contenedor. En Compose se declara así:

services:
  web:
    image: nginx:1.29-alpine
    tmpfs:
      - /tmp
      - /var/cache/nginx

Como tmpfs nunca escribe en disco, es más rápido y deja menos rastro. La contrapartida es obvia: si el contenedor se reinicia, ese contenido se pierde. Úsalo para lo que de verdad es temporal, no para datos que necesites mañana.

Preguntas frecuentes

¿Cuál es la diferencia entre un volumen y un bind mount?

Un volumen con nombre lo crea y gestiona Docker en /var/lib/docker/volumes, y no depende de la estructura de carpetas del host. Un bind mount enlaza una carpeta concreta que eliges tú. Los volúmenes son la opción recomendada para datos de aplicaciones; los bind mounts, para configuración y código en desarrollo.

¿Dónde guarda Docker los volúmenes con nombre?

En Linux, bajo /var/lib/docker/volumes/<nombre>/_data. Puedes ver la ruta exacta con docker volume inspect <nombre>, en el campo Mountpoint. No deberías editar esos ficheros a mano: interactúa siempre a través del contenedor o de un contenedor auxiliar.

¿Se borran los datos al hacer docker compose down?

No, si están en un volumen con nombre. docker compose down para y elimina los contenedores, pero respeta los volúmenes declarados. Solo se borran si añades la opción -v (docker compose down -v), que sí destruye los volúmenes del proyecto. Por eso conviene tener copias.

Conclusión

Persistir datos en Docker se reduce a una decisión: volumen con nombre para lo que la aplicación necesita conservar, bind mount para lo que tú controlas desde el host, y tmpfs para lo que puede evaporarse. Con la versión 29.6.1 del motor (junio de 2026) el modelo sigue siendo el mismo que hace años, precisamente porque funciona. El siguiente paso natural es asegurar esos datos con copias automáticas antes de poner cualquier servicio en producción.

Fuentes: [1] Docker Docs: volúmenes[1], [2] Docker Docs: bind mounts[2], [3] Docker Docs: CLI de docker volume[3], [4] Moby, repositorio del motor de Docker[4], [5] Docker Hub, imagen oficial de PostgreSQL[5].

Fuentes

  1. Docker Docs: volúmenes
  2. Docker Docs: bind mounts
  3. Docker Docs: CLI de docker volume
  4. Moby, repositorio del motor de Docker
  5. Docker Hub, imagen oficial de PostgreSQL

Ruta: Self-hosting con Docker: de cero a producción