Docker Volumes: qué son, tipos y ejemplos reales

Docker Volumes: qué son, tipos y ejemplos reales

Tiempo de lectura📚

20 minutos

Si llevas un tiempo usando Docker, seguro que en algún momento has hecho esto: levantas un contenedor con una base de datos, insertas datos, lo paras... y al volver a levantarlo los datos han desaparecido. Como si nunca hubieran existido.
Eso no es un bug. Es exactamente cómo está diseñado Docker. Y los volúmenes son la solución.
En este artículo te explico qué son los volúmenes, por qué existen, cuándo usar cada tipo y cómo se configuran. Con ejemplos reales, sin rodeos.

📚 Índice

Que es un volumen en Docker

Un volumen es una carpeta que vive fuera del contenedor, gestionada por Docker, y que se monta dentro del contenedor en la ruta que tú elijas.
El contenedor puede leer y escribir en esa carpeta como si fuera suya. Pero cuando el contenedor se para, se elimina o se reemplaza por una nueva versión, los datos siguen ahí.


[Contenedor]          [Contenedor]
  └── /datos            └── /datos ──┐
       (se borra                      │ montado
        al eliminar)            [Volumen Docker]
                                  └── datos seguros
                                  

En Linux, Docker guarda los volúmenes en /var/lib/docker/volumes/. Tú no tienes que preocuparte de esa ruta, Docker la gestiona por ti.

El problema de los contenedores efimeros

Cuando Docker arranca un contenedor, crea una capa de escritura temporal por encima de la imagen. Todo lo que el contenedor escribe (archivos, datos de base de datos, logs) va a esa capa. El problema es que esa capa desaparece cuando el contenedor se elimina.

Imagínalo así: es como escribir en una pizarra que se borra automáticamente al apagar la luz.

# Demostración del problema


docker run --name prueba ubuntu bash -c "echo 'hola mundo' > /tmp/archivo.txt"

docker start prueba

docker exec prueba cat /tmp/archivo.txt   # funciona

docker rm prueba                           # eliminamos el contenedor

docker run --name prueba ubuntu bash -c "cat /tmp/archivo.txt"

# cat: /tmp/archivo.txt: No such file or directory

El archivo se fue con el contenedor. Esto es un problema enorme para bases de datos, uploads de usuarios, logs que necesitas conservar, o cualquier dato que tenga que sobrevivir más allá de la vida del contenedor.

Aquí entran los volúmenes.

Tipos de almacenamiento en Docker

💡
Docker tiene tres formas de persistir datos. Es importante conocerlas todas para saber cuál usar en cada situación.

1.Volumes

Son el método recomendado. Docker los crea y gestiona en su propio directorio del sistema. Tú solo los referencias por nombre.

# Crear un volumen

docker volume create mis-datos


# Usarlo en un contenedor

docker run -v mis-datos:/app/datos mi-imagen

¿Cuándo usarlos?

  • Bases de datos
  • Cualquier dato que necesite persistir en producción
  • datos compartidos entre contenedores

2.Bind Mounts

Montan una carpeta real de tu máquina dentro del contenedor. Lo que pasa en esa carpeta se ve en tiempo real desde dentro y desde fuera del contenedor.

# Montar la carpeta actual dentro del contenedor

docker run -v $(pwd)/src:/app/src mi-imagen

¿Cuándo usarlos?

  • Desarrollo local
  • Cuando quieras que el contenedor vea los cambios de tu código al momento sin hacer rebuild

3. tmpfs Mounts

Almacenan datos en la memoria RAM del host, no en disco. Son temporales por naturaleza: desaparecen cuando el contenedor se para.

docker run --tmpfs /tmp mi-imagen

¿Cuándo usarlos?

  • Datos sensibles que no deben tocarse en disco (tokens,secretos temporales)
  • Cachés de alto rendimiento

Como usar Docker Volumes paso a paso

Crear y gestionar volúmenes

# Crear un volumen

docker volume create bbdd-datos


# Ver todos los volúmenes

docker volume ls


# Inspeccionar un volumen (dónde está, cuándo se creó...)

docker volume inspect bbdd-datos


# Eliminar un volumen (solo si ningún contenedor lo usa)

docker volume rm bbdd-datos


# Eliminar todos los volúmenes que no usa nadie

docker volume prune

Usar un volumen con un contenedor

# Con la sintaxis -v (más corta)

docker run -d \
  --name mi-postgres \
  -e POSTGRES_PASSWORD=secreto \
  -v bbdd-datos:/var/lib/postgresql/data \
  postgres:16
# Con la sintaxis --mount (más explícita, recomendada en producción)

docker run -d \
  --name mi-postgres \
  -e POSTGRES_PASSWORD=secreto \
  --mount type=volume,source=bbdd-datos,target=/var/lib/postgresql/data \
  postgres:16

Ambos comandos hacen lo mismo. La diferencia es que --mount es más verboso pero más claro: especifica el tipo, el nombre del volumen y la ruta de destino por separado.

Verificar que los datos persisten

# 1. Arrancamos PostgreSQL con volumen

docker run -d --name pg -e POSTGRES_PASSWORD=pass -v pg-datos:/var/lib/postgresql/data postgres:16
# 2. Creamos una base de datos

docker exec -it pg psql -U postgres -c "CREATE DATABASE miapp;"

# 3. Eliminamos el contenedor

docker rm -f pg

# 4. Creamos un contenedor nuevo con el mismo volumen

docker run -d --name pg-nuevo -e POSTGRES_PASSWORD=pass -v pg-datos:/var/lib/postgresql/data postgres:16
# 5. Comprobamos que la base de datos sigue ahí

docker exec -it pg-nuevo psql -U postgres -c "\l"

# miapp aparece en la lista. Los datos sobrevivieron.

Volumenes en Docker Compose

En proyectos reales casi siempre usarás Docker Compose. Aquí los volúmenes se definen en dos sitios: dentro del servicio (dónde se monta) y al final del archivo (declaración del volumen).

name: miapp

services:
  api:
    build: ./api
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: usuario
      POSTGRES_PASSWORD: contraseña
      POSTGRES_DB: mibase
    volumes:
      - pg_data:/var/lib/postgresql/data   # <- aquí se monta
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U usuario -d mibase"]
      interval: 10s
      retries: 5

# Declaración de volúmenes al final del archivo
volumes:
  pg_data:    # Docker lo crea y gestiona automáticamente

Cuando ejecutas docker compose up -d por primera vez, Docker crea el volumen pg_data automáticamente. Cuando haces docker compose down, el volumen no se elimina. Los datos sobreviven.

Para eliminarlo explícitamente

docker compose down -v   # ⚠️ Esto borra los datos

Bind Mounts en desarrollo

Durante el desarrollo, lo más cómodo es usar un bind mount para que el contenedor vea los cambios de tu código al momento, sin tener que hacer rebuild de la imagen cada vez.

services:
  api:
    build: ./api
    volumes:
      - ./api:/app          # bind mount: tu carpeta local -> contenedor
      - /app/node_modules   # excepción: node_modules viene de la imagen
    command: node --watch index.js   # recarga automática al cambiar archivos

Lo importante de este ejemplo:

  • ./api:/app monta tu carpeta local dentro del contenedor. Cualquier cambio que hagas en tu editor aparece al momento dentro del contenedor.
  • /app/node_modules es un volumen anónimo que "protege" la carpeta node_modules de ser sobreescrita por tu carpeta local. Si no haces esto, Docker montaría tu node_modules local (que puede tener diferencias de arquitectura) sobre el del contenedor.

Compartir volumenes entre contenedores

Varios contenedores pueden montar el mismo volumen al mismo tiempo. Esto es útil cuando, por ejemplo, un contenedor genera archivos y otro los sirve.

services:
  generador:
    image: mi-procesador
    volumes:
      - archivos-compartidos:/output

  servidor:
    image: nginx:alpine
    volumes:
      - archivos-compartidos:/usr/share/nginx/html:ro   # :ro = solo lectura
    ports:
      - "80:80"

volumes:
  archivos-compartidos:

El generador escribe en /output. El servidor lee esos archivos y los sirve. El :ro al final del bind mount del servidor le impide escribir en el volumen, solo puede leer.

Como hacer backup de un volumen

Los volúmenes están en el sistema del host, pero no en una carpeta fácilmente accesible. La forma más limpia de hacer backup es con un contenedor temporal:

# Crear backup del volumen pg_data en un archivo .tar.gz
docker run --rm \
  -v pg_data:/datos \
  -v $(pwd):/backup \
  ubuntu \
  tar czf /backup/backup-pg.tar.gz -C /datos .

# Restaurar el backup en un volumen nuevo
docker volume create pg_data_restaurado

docker run --rm \
  -v pg_data_restaurado:/datos \
  -v $(pwd):/backup \
  ubuntu \
  tar xzf /backup/backup-pg.tar.gz -C /datos

Este patrón usa un contenedor Ubuntu temporal que monta tanto el volumen como una carpeta de tu máquina, y ejecuta tar para comprimir o descomprimir. El --rm hace que el contenedor se elimine solo al terminar.

Errores frecuentes

"Eliminé el contenedor y perdí los datos"

Si usaste docker run sin -v, los datos estaban en la capa del contenedor. Solo los volúmenes persisten. Solución: siempre usa -v nombre:/ruta para cualquier dato que necesite sobrevivir.

"Hice docker compose down -v sin querer"

El flag -v elimina los volúmenes. Sin él, docker compose down conserva los datos. Si ya lo ejecutaste, los datos se han borrado y no hay forma de recuperarlos a menos que tengas un backup.

"El contenedor no arranca porque el volumen tiene datos de una versión anterior"

Esto pasa al actualizar la imagen de una base de datos. Solución: elimina el volumen y deja que la nueva versión lo inicialice desde cero, o sigue el proceso de migración que indica la imagen.

"No sé qué volúmenes están ocupando espacio"

docker system df          # resumen general de espacio usado

docker volume ls          # lista todos los volúmenes

docker volume prune       # elimina los que no usa ningún contenedor

Cuando usar cada tipo

Docker Volumes - Cuándo usar cada tipo

¿Cuándo usar cada tipo de almacenamiento?

Situación Tipo recomendado
Base de datos en producción Volume nombrado
Código en desarrollo local Bind mount
Datos sensibles temporales tmpfs
Compartir archivos entre contenedores Volume nombrado
Ver logs en tiempo real desde el host Bind mount
Caché de alta velocidad tmpfs

Base de datos en producción

Volume nombrado

Código en desarrollo local

Bind mount

Datos sensibles temporales

tmpfs

Compartir archivos entre contenedores

Volume nombrado

Ver logs en tiempo real desde el host

Bind mount

Caché de alta velocidad

tmpfs
Volume nombrado — gestionado por Docker
Bind mount — carpeta de tu máquina
tmpfs — solo en memoria RAM

La regla general es: si el dato tiene que sobrevivir, usa un volume nombrado. Si necesitas que tu editor y el contenedor vean la misma carpeta, usa un bind mount.

Los volúmenes son uno de esos conceptos que, una vez los entiendes de verdad, te cambian la forma de construir aplicaciones con Docker. Dejan de ser ese "flag misterioso" del compose.yml y pasan a ser una herramienta que usas con intención.
Si quieres seguir profundizando en Docker Compose y ver cómo los volúmenes encajan en un proyecto real desde cero, tengo una guía completa en el blog donde construimos una aplicación paso a paso.

Read more