Volúmenes Docker: Persistencia de Datos - Guía Completa

Los volúmenes Docker son el mecanismo preferido para persistir datos generados y usados por contenedores Docker. A diferencia de los contenedores mismos, los volúmenes persisten más allá del ciclo de vida del contenedor, permitiendo compartir datos, respaldos y migraciones. Esta guía completa cubre todo sobre volúmenes Docker, desde conceptos básicos hasta estrategias avanzadas para producción.

Tabla de Contenidos

Introducción a los Volúmenes Docker

Los contenedores son efímeros por diseño—cuando se eliminan, todos los datos dentro se pierden. Los volúmenes Docker resuelven esto proporcionando almacenamiento persistente que existe independientemente del ciclo de vida del contenedor. Los volúmenes son gestionados por Docker y almacenados fuera de la capa escribible del contenedor, ofreciendo mejor rendimiento y gestión más fácil.

¿Por Qué Usar Volúmenes Docker?

  • Persistencia de Datos: Sobreviven reinicios y eliminaciones de contenedores
  • Rendimiento: Mejor rendimiento de I/O que el sistema de archivos del contenedor
  • Compartir: Compartir datos entre múltiples contenedores
  • Respaldo: Más fácil de respaldar y restaurar
  • Portabilidad: Mover datos entre hosts
  • Seguridad: Pueden ser encriptados y controlados por acceso

Comparación de Tipos de Almacenamiento

CaracterísticaVolúmenesBind Mountstmpfs
Gestionado por DockerNoNo
PersistenteNo
CompartibleNo
RendimientoAltoMedioMás Alto
PortabilidadAltaBajaN/A
Acceso desde hostLimitadoDirectoNinguno

Requisitos Previos

Antes de trabajar con volúmenes Docker, asegúrate de tener:

  • Docker Engine instalado (versión 17.06 o superior)
  • Comprensión básica de contenedores Docker
  • Acceso sudo/root para algunas operaciones
  • Comprensión de conceptos de sistema de archivos

Verificar instalación de Docker:

docker --version
docker info | grep "Storage Driver"

Tipos de Persistencia de Datos

1. Volúmenes Nombrados (Recomendado)

Gestionados por Docker, almacenados en el directorio de almacenamiento de Docker:

docker volume create my-volume
docker run -v my-volume:/app/data nginx

2. Bind Mounts

Mapeo directo al sistema de archivos del host:

docker run -v /host/path:/container/path nginx

3. tmpfs Mounts

Almacenamiento temporal en memoria (solo Linux):

docker run --tmpfs /app/temp nginx

Arquitectura de Volúmenes

Capa de Contenedor (efímera)
    ↓
Punto de Montaje de Volumen
    ↓
Volumen Docker (persistente)
    ↓
Sistema de Archivos del Host (/var/lib/docker/volumes/)

Volúmenes Nombrados

Los volúmenes nombrados son unidades de almacenamiento gestionadas por Docker con nombres explícitos, haciéndolos fáciles de referenciar y gestionar.

Crear Volúmenes Nombrados

# Crear volumen básico
docker volume create my-data

# Crear con opciones de controlador
docker volume create \
  --driver local \
  --opt type=none \
  --opt device=/path/on/host \
  --opt o=bind \
  custom-volume

# Crear con etiquetas
docker volume create \
  --label environment=production \
  --label backup=daily \
  prod-data

Ubicación de Almacenamiento de Volúmenes

# Ubicación predeterminada: /var/lib/docker/volumes/
sudo ls -la /var/lib/docker/volumes/

# Inspeccionar ubicación de volumen
docker volume inspect my-data

Usar Volúmenes Nombrados

# Ejecutar contenedor con volumen nombrado
docker run -d \
  --name web \
  -v my-data:/usr/share/nginx/html \
  nginx

# Múltiples volúmenes
docker run -d \
  --name app \
  -v app-data:/app/data \
  -v app-logs:/app/logs \
  -v app-config:/app/config \
  my-app:latest

Volumen con Opciones Específicas

# Volumen de solo lectura
docker run -d -v my-data:/app/data:ro nginx

# Volumen con opción nocopy (no copiar datos del contenedor)
docker run -d -v my-data:/app/data:nocopy nginx

# Etiqueta SELinux (para sistemas con SELinux habilitado)
docker run -d -v my-data:/app/data:z nginx  # privado
docker run -d -v my-data:/app/data:Z nginx  # compartido

Compartir Volúmenes Entre Contenedores

# Crear volumen
docker volume create shared-data

# Primer contenedor escribe datos
docker run -d \
  --name writer \
  -v shared-data:/data \
  alpine sh -c 'echo "Hello" > /data/message.txt'

# Segundo contenedor lee datos
docker run --rm \
  -v shared-data:/data \
  alpine cat /data/message.txt

Ciclo de Vida del Volumen

# Crear volumen
docker volume create app-data

# Usar en contenedor
docker run -d --name app -v app-data:/data my-app

# Contenedor eliminado, volumen persiste
docker rm -f app

# Volumen aún existe
docker volume ls

# Eliminar volumen manualmente
docker volume rm app-data

Bind Mounts

Los bind mounts mapean un directorio o archivo del host directamente en un contenedor. Útil para desarrollo y cuando necesitas acceso directo al sistema de archivos del host.

Bind Mount Básico

# Montar directorio del host
docker run -d \
  -v /host/path:/container/path \
  nginx

# Usando sintaxis --mount (recomendado)
docker run -d \
  --mount type=bind,source=/host/path,target=/container/path \
  nginx

Flujo de Trabajo de Desarrollo

# Montar código fuente para desarrollo
docker run -d \
  --name dev-container \
  -v $(pwd)/app:/usr/src/app \
  -v $(pwd)/app/node_modules:/usr/src/app/node_modules \
  -p 3000:3000 \
  node:18-alpine \
  npm run dev

Bind Mount de Solo Lectura

# Montar como solo lectura
docker run -d \
  -v /host/config:/app/config:ro \
  nginx

# Usando --mount
docker run -d \
  --mount type=bind,source=/host/config,target=/app/config,readonly \
  nginx

Bind Mount de Archivo

# Montar archivo único
docker run -d \
  -v /host/nginx.conf:/etc/nginx/nginx.conf:ro \
  nginx

Permisos de Bind Mount

# Preservar permisos
docker run -d \
  -v /host/data:/container/data \
  --user $(id -u):$(id -g) \
  nginx

# Con usuario específico
docker run -d \
  -v /host/data:/container/data \
  --user 1000:1000 \
  nginx

Ejemplo de Bind Mount: Desarrollo WordPress

# Montar fuente de WordPress y base de datos
docker run -d \
  --name wordpress \
  -v $(pwd)/wordpress:/var/www/html \
  -v $(pwd)/uploads:/var/www/html/wp-content/uploads \
  -p 8080:80 \
  wordpress:latest

docker run -d \
  --name mysql \
  -v $(pwd)/mysql:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=secret \
  mysql:8.0

tmpfs Mounts

Los tmpfs mounts son almacenamiento temporal solo en memoria. Los datos se pierden cuando el contenedor se detiene.

Crear tmpfs Mount

# tmpfs mount básico
docker run -d \
  --tmpfs /app/temp \
  nginx

# Usando sintaxis --mount
docker run -d \
  --mount type=tmpfs,target=/app/temp \
  nginx

# Con límite de tamaño
docker run -d \
  --mount type=tmpfs,target=/app/temp,tmpfs-size=100m \
  nginx

Casos de Uso de tmpfs

# Caché temporal
docker run -d \
  --tmpfs /tmp:rw,noexec,nosuid,size=1g \
  nginx

# Almacenamiento de sesiones
docker run -d \
  --tmpfs /app/sessions:size=512m \
  my-app

# Artefactos de construcción (no persistir)
docker run --rm \
  --tmpfs /app/build:size=2g \
  my-builder

Rendimiento de tmpfs

Perfecto para:

  • Archivos temporales
  • Caché que no necesita persistencia
  • Datos sensibles (limpiados automáticamente)
  • Requisitos de almacenamiento de alta velocidad
# Directorio temporal de base de datos
docker run -d \
  --name postgres \
  --tmpfs /var/lib/postgresql/tmp:size=1g \
  -v postgres-data:/var/lib/postgresql/data \
  postgres:15-alpine

Controladores de Volúmenes

Docker soporta varios controladores de volúmenes para diferentes backends de almacenamiento.

Controlador Local (Predeterminado)

# Controlador local predeterminado
docker volume create my-volume

# Local con opciones
docker volume create \
  --driver local \
  --opt type=nfs \
  --opt o=addr=192.168.1.100,rw \
  --opt device=:/path/to/share \
  nfs-volume

Volumen NFS

# Crear volumen NFS
docker volume create \
  --driver local \
  --opt type=nfs \
  --opt o=addr=nfs-server.example.com,rw,nfsvers=4 \
  --opt device=:/exported/path \
  nfs-data

# Usar volumen NFS
docker run -d \
  -v nfs-data:/app/data \
  nginx

Plugins de Volúmenes de Terceros

# Instalar plugin (ejemplo: REX-Ray)
docker plugin install rexray/s3fs

# Crear volumen con plugin
docker volume create \
  --driver rexray/s3fs \
  --opt bucket=my-bucket \
  s3-volume

Plugins de volúmenes populares:

  • REX-Ray: AWS EBS, S3, etc.
  • Portworx: Almacenamiento nativo de contenedores
  • GlusterFS: Sistema de archivos distribuido
  • Ceph: Almacenamiento distribuido

Comandos de Gestión de Volúmenes

Crear Volúmenes

# Crear volumen
docker volume create my-volume

# Crear con nombre
docker volume create --name production-data

# Con etiquetas
docker volume create \
  --label app=myapp \
  --label env=prod \
  app-data

Listar Volúmenes

# Listar todos los volúmenes
docker volume ls

# Filtrar por nombre
docker volume ls -f name=app

# Filtrar por etiqueta
docker volume ls -f label=env=prod

# Mostrar volúmenes colgantes
docker volume ls -f dangling=true

Inspeccionar Volúmenes

# Inspeccionar volumen
docker volume inspect my-volume

# Obtener campo específico
docker volume inspect --format '{{.Mountpoint}}' my-volume

# Inspeccionar múltiples volúmenes
docker volume inspect volume1 volume2

Eliminar Volúmenes

# Eliminar volumen específico
docker volume rm my-volume

# Eliminar múltiples volúmenes
docker volume rm volume1 volume2 volume3

# Eliminar todos los volúmenes no usados
docker volume prune

# Forzar eliminación de todos los volúmenes (¡peligroso!)
docker volume rm $(docker volume ls -q)

Información de Volúmenes

# Obtener tamaño de volumen (requiere sudo)
sudo du -sh /var/lib/docker/volumes/my-volume

# Listar volúmenes con tamaño
docker system df -v

# Verificar uso de volumen
docker volume inspect my-volume | grep Mountpoint

Uso de Volúmenes con Contenedores

Múltiples Volúmenes

# Contenedor con múltiples volúmenes
docker run -d \
  --name app \
  -v app-data:/app/data \
  -v app-logs:/var/log/app \
  -v app-cache:/app/cache \
  -v app-config:/etc/app:ro \
  my-app:latest

Volumen desde Otro Contenedor

# Crear contenedor de datos
docker create -v /data --name data-container alpine

# Usar volúmenes del contenedor de datos
docker run -d \
  --volumes-from data-container \
  --name app \
  nginx

Poblar Volumen desde Contenedor

# La imagen del contenedor tiene datos en /app/data
# La primera ejecución copia datos a volumen vacío
docker run -d \
  --name app \
  -v app-data:/app/data \
  my-app:latest

# Ejecuciones subsiguientes usan datos de volumen existentes
docker rm -f app
docker run -d \
  --name app \
  -v app-data:/app/data \
  my-app:latest

Volúmenes en Docker Compose

Configuración Básica de Volúmenes

version: '3.8'

services:
  web:
    image: nginx
    volumes:
      - html-data:/usr/share/nginx/html

  database:
    image: postgres:15-alpine
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  html-data:
  db-data:

Volúmenes Nombrados con Opciones

version: '3.8'

services:
  app:
    image: my-app
    volumes:
      - app-data:/app/data

volumes:
  app-data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /path/on/host

Bind Mounts en Compose

version: '3.8'

services:
  web:
    image: nginx
    volumes:
      - ./html:/usr/share/nginx/html
      - ./nginx.conf:/etc/nginx/nginx.conf:ro

Volumen con NFS

version: '3.8'

services:
  app:
    image: my-app
    volumes:
      - nfs-data:/app/data

volumes:
  nfs-data:
    driver: local
    driver_opts:
      type: nfs
      o: addr=nfs-server.example.com,rw
      device: ":/exported/path"

Volúmenes Externos

version: '3.8'

services:
  app:
    image: my-app
    volumes:
      - existing-volume:/app/data

volumes:
  existing-volume:
    external: true

Ejemplo Completo de Compose

version: '3.8'

services:
  wordpress:
    image: wordpress:latest
    volumes:
      - wordpress-html:/var/www/html
      - wordpress-uploads:/var/www/html/wp-content/uploads
    environment:
      WORDPRESS_DB_HOST: database
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}

  database:
    image: mysql:8.0
    volumes:
      - db-data:/var/lib/mysql
      - ./mysql-init:/docker-entrypoint-initdb.d:ro
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}

  backup:
    image: alpine
    volumes:
      - wordpress-html:/backup/html:ro
      - db-data:/backup/db:ro
      - ./backups:/backups
    command: sh -c "tar czf /backups/backup-$(date +%Y%m%d-%H%M%S).tar.gz /backup"

volumes:
  wordpress-html:
  wordpress-uploads:
  db-data:

Respaldo y Restauración

Respaldar Volumen Nombrado

# Respaldar volumen a archivo tar
docker run --rm \
  -v my-volume:/source:ro \
  -v $(pwd):/backup \
  alpine \
  tar czf /backup/my-volume-backup.tar.gz -C /source .

# Respaldar con marca de tiempo
docker run --rm \
  -v my-volume:/source:ro \
  -v $(pwd):/backup \
  alpine \
  tar czf /backup/backup-$(date +%Y%m%d-%H%M%S).tar.gz -C /source .

Restaurar Volumen desde Respaldo

# Crear nuevo volumen
docker volume create my-volume-restored

# Restaurar desde respaldo
docker run --rm \
  -v my-volume-restored:/target \
  -v $(pwd):/backup \
  alpine \
  sh -c "cd /target && tar xzf /backup/my-volume-backup.tar.gz"

Respaldar Volumen de Base de Datos

# Respaldo PostgreSQL
docker run --rm \
  -v postgres-data:/var/lib/postgresql/data:ro \
  -v $(pwd):/backup \
  postgres:15-alpine \
  tar czf /backup/postgres-backup.tar.gz -C /var/lib/postgresql/data .

# Respaldo MySQL
docker exec mysql-container \
  mysqldump -u root -p${DB_PASSWORD} --all-databases \
  > mysql-backup.sql

Script de Respaldo Automatizado

#!/bin/bash
# backup-volumes.sh

BACKUP_DIR="/backup"
DATE=$(date +%Y%m%d-%H%M%S)

# Respaldar múltiples volúmenes
for volume in app-data db-data logs-data; do
  docker run --rm \
    -v ${volume}:/source:ro \
    -v ${BACKUP_DIR}:/backup \
    alpine \
    tar czf /backup/${volume}-${DATE}.tar.gz -C /source .
done

# Limpiar respaldos antiguos (mantener últimos 7 días)
find ${BACKUP_DIR} -name "*.tar.gz" -mtime +7 -delete

Migrar Volumen a Otro Host

# En host fuente: Exportar volumen
docker run --rm \
  -v my-volume:/source:ro \
  alpine \
  tar c -C /source . | ssh user@destination-host 'cat > /tmp/volume-export.tar'

# En host destino: Importar volumen
docker volume create my-volume
cat /tmp/volume-export.tar | docker run --rm -i \
  -v my-volume:/target \
  alpine \
  tar x -C /target

Mejores Prácticas para Producción

Usar Volúmenes Nombrados

# Bueno: Volumen nombrado
docker run -d -v postgres-data:/var/lib/postgresql/data postgres

# Evitar: Volumen anónimo
docker run -d -v /var/lib/postgresql/data postgres

Convención de Nombres de Volúmenes

# Incluir entorno y propósito
docker volume create prod-app-data
docker volume create prod-db-data
docker volume create staging-cache

Etiquetar Volúmenes

docker volume create \
  --label environment=production \
  --label backup=daily \
  --label app=myapp \
  --label retention=30d \
  prod-app-data

Montajes de Solo Lectura

# La configuración debe ser de solo lectura
docker run -d \
  -v config-data:/etc/app:ro \
  -v app-data:/app/data \
  my-app

Monitoreo de Volúmenes

# Verificar tamaños de volúmenes
docker system df -v

# Monitorear volumen específico
watch -n 5 'sudo du -sh /var/lib/docker/volumes/my-volume/_data'

Estrategia de Respaldo

# Agregar servicio de respaldo a compose
services:
  backup:
    image: alpine
    volumes:
      - app-data:/data:ro
      - ./backups:/backups
    command: >
      sh -c "while true; do
        tar czf /backups/backup-$$(date +%Y%m%d-%H%M%S).tar.gz /data;
        sleep 86400;
      done"

Consideraciones de Seguridad

# Ejecutar con usuario específico
docker run -d \
  --user 1000:1000 \
  -v app-data:/app/data \
  my-app

# Usar secretos para datos sensibles (Swarm)
echo "password" | docker secret create db_password -
docker service create \
  --secret db_password \
  --mount source=db-data,target=/var/lib/mysql \
  mysql

Límites de Recursos

# Limitar tamaño de volumen (requiere controlador de almacenamiento específico)
docker volume create \
  --driver local \
  --opt type=tmpfs \
  --opt device=tmpfs \
  --opt o=size=100m,uid=1000 \
  limited-volume

Resolución de Problemas

Volumen No Se Monta

# Verificar si el volumen existe
docker volume ls | grep my-volume

# Inspeccionar volumen
docker volume inspect my-volume

# Verificar montaje del contenedor
docker inspect container-name | grep -A 10 Mounts

# Verificar permisos
sudo ls -la /var/lib/docker/volumes/my-volume/_data

Errores de Permiso Denegado

# Verificar propiedad
docker exec container-name ls -la /app/data

# Corregir permisos desde contenedor
docker exec container-name chown -R appuser:appuser /app/data

# O usar usuario apropiado
docker run -d --user $(id -u):$(id -g) -v my-volume:/data my-app

Datos del Volumen No Persisten

# Verificar que el volumen se usa realmente
docker inspect container-name | grep -A 20 Mounts

# Verificar si se usa volumen anónimo
docker volume ls -f dangling=true

# Asegurar que el contenedor escribe en ruta montada
docker exec container-name touch /data/test.txt
docker run --rm -v my-volume:/data alpine ls -la /data

Volumen Lleno

# Verificar tamaño de volumen
docker system df -v

# Encontrar archivos grandes
docker run --rm -v my-volume:/data alpine du -sh /data/*

# Limpiar datos antiguos
docker exec container-name find /data -mtime +30 -delete

No Se Puede Eliminar Volumen

# Encontrar contenedores usando volumen
docker ps -a --filter volume=my-volume

# Detener y eliminar contenedores
docker rm -f $(docker ps -a -q --filter volume=my-volume)

# Eliminar volumen
docker volume rm my-volume

Problemas de Rendimiento

# Verificar controlador de almacenamiento
docker info | grep "Storage Driver"

# Usar tmpfs para almacenamiento temporal de alto rendimiento
docker run -d --tmpfs /app/temp:size=1g my-app

# Considerar opciones de controlador de volumen
docker volume create \
  --driver local \
  --opt type=none \
  --opt o=bind \
  --opt device=/fast/storage/path \
  fast-volume

Conclusión

Los volúmenes Docker proporcionan persistencia de datos robusta y flexible para aplicaciones contenerizadas. Comprender volúmenes, bind mounts y tmpfs mounts es esencial para despliegues de producción.

Puntos Clave

  • Volúmenes Nombrados: Método preferido, gestionado por Docker, portable
  • Bind Mounts: Flujos de trabajo de desarrollo, acceso directo al host
  • tmpfs: Almacenamiento temporal, alto rendimiento, solo en memoria
  • Persistencia: Los datos sobreviven al ciclo de vida del contenedor
  • Compartir: Múltiples contenedores pueden usar el mismo volumen
  • Respaldo: Los respaldos regulares son esenciales para producción

Referencia Rápida

# Gestión de Volúmenes
docker volume create my-volume              # Crear volumen
docker volume ls                            # Listar volúmenes
docker volume inspect my-volume             # Inspeccionar volumen
docker volume rm my-volume                  # Eliminar volumen
docker volume prune                         # Eliminar no usados

# Usar Volúmenes
docker run -v my-volume:/data nginx         # Volumen nombrado
docker run -v /host:/container nginx        # Bind mount
docker run --tmpfs /tmp nginx               # tmpfs mount
docker run -v my-volume:/data:ro nginx      # Solo lectura

# Respaldo y Restauración
# Respaldar
docker run --rm -v my-volume:/source:ro -v $(pwd):/backup alpine tar czf /backup/backup.tar.gz -C /source .
# Restaurar
docker run --rm -v my-volume:/target -v $(pwd):/backup alpine tar xzf /backup/backup.tar.gz -C /target

Próximos Pasos

  1. Implementar: Usar volúmenes en tus aplicaciones
  2. Respaldar: Configurar estrategia de respaldo automatizada
  3. Monitorear: Rastrear tamaños y uso de volúmenes
  4. Asegurar: Implementar permisos y encriptación adecuados
  5. Optimizar: Elegir controladores de almacenamiento apropiados
  6. Escalar: Explorar soluciones de almacenamiento distribuido
  7. Documentar: Mantener inventario y procedimientos de volúmenes

La gestión adecuada de volúmenes asegura persistencia de datos, portabilidad y confiabilidad en tu infraestructura contenerizada.