Migración de Contenedores Docker: Guía Completa para Mover Contenedores Entre Servidores

La migración de contenedores Docker es una habilidad fundamental para profesionales de DevOps modernos. Ya sea que esté consolidando infraestructura, actualizando servidores, escalando horizontalmente o migrando a entornos cloud, la capacidad de migrar sin problemas contenedores Docker entre hosts asegura continuidad del negocio y flexibilidad operacional. Esta guía completa cubre múltiples estrategias de migración, desde simples exportaciones de contenedores hasta enfoques avanzados basados en orquestación.

Entendiendo la Migración de Contenedores Docker

La migración de contenedores Docker implica transferir aplicaciones en contenedores de un host a otro mientras se preservan datos, configuraciones y estado. A diferencia de la migración tradicional de aplicaciones, la contenedorización de Docker proporciona ventajas únicas:

  • Portabilidad: Los contenedores se ejecutan consistentemente en diferentes entornos
  • Aislamiento: Las dependencias están empaquetadas dentro de los contenedores
  • Reproducibilidad: Las imágenes aseguran despliegues idénticos
  • Eficiencia: El sistema de archivos por capas reduce tamaños de transferencia
  • Gestión de Estado: Los volúmenes separan datos de lógica de aplicación

Sin embargo, la migración exitosa requiere entender la arquitectura de Docker, gestión adecuada de volúmenes, configuración de red y principios de orquestación.

Escenarios de Migración Docker

Casos de Uso Comunes de Migración

  1. Consolidación de Servidores: Mover múltiples contenedores a menos hosts
  2. Actualización de Infraestructura: Migrar a hardware o versiones de OS más nuevas
  3. Migración a la Nube: Transición de on-premises a cloud
  4. Recuperación ante Desastres: Restaurar servicios en infraestructura de respaldo
  5. Balanceo de Carga: Distribuir contenedores entre múltiples hosts
  6. Desarrollo a Producción: Promover contenedores validados
  7. Despliegue Multi-Región: Replicar servicios geográficamente

Planificación Pre-Migración

Evaluación del Entorno

Documente su entorno Docker actual:

# List all running containers
docker ps -a --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"

# Check Docker version
docker version

# List all images
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"

# List all volumes
docker volume ls

# List all networks
docker network ls

# Check disk usage
docker system df -v

# Inspect specific container
docker inspect container_name > /tmp/container_config.json

# Export running container list with details
docker ps -a --format "{{json .}}" > /tmp/containers_inventory.json

Identificar Dependencias

# Check container dependencies
docker inspect container_name | jq '.[0].HostConfig.Links'

# Check volume mounts
docker inspect container_name | jq '.[0].Mounts'

# Check network connections
docker inspect container_name | jq '.[0].NetworkSettings.Networks'

# Check environment variables
docker inspect container_name | jq '.[0].Config.Env'

# Export docker-compose configuration if available
docker-compose config > /tmp/docker-compose-current.yml

Checklist de Pre-Migración

Complete estas tareas antes de la migración:

  • Documentar todos los contenedores en ejecución y sus configuraciones
  • Identificar volúmenes de datos persistentes y sus ubicaciones
  • Exportar toda la configuración de redes personalizadas de Docker
  • Listar todas las interdependencias de contenedores
  • Respaldar imágenes Docker a registro o archivos tar
  • Probar pulls de imágenes en servidor destino
  • Verificar compatibilidad de versión de Docker
  • Asegurar espacio en disco suficiente en destino
  • Configurar reglas de firewall en nuevo servidor
  • Probar conectividad de red entre servidores
  • Preparar procedimientos de rollback
  • Configurar monitoreo en ambos servidores
  • Programar ventana de migración
  • Notificar a interesados

Método de Migración 1: Export/Import de Docker

Mejor para: Contenedores individuales, migraciones simples, sin acceso a registro

Exportar Contenedores

# Export running container to tar file
docker export container_name > /tmp/container_name.tar

# Or compress during export
docker export container_name | gzip > /tmp/container_name.tar.gz

# Export multiple containers
for container in $(docker ps -q); do
  name=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
  echo "Exporting $name..."
  docker export $container | gzip > /tmp/${name}.tar.gz
done

# Transfer to destination server
scp /tmp/container_name.tar.gz user@new-server:/tmp/

Importar Contenedores

# On destination server: Import container
docker import /tmp/container_name.tar.gz container_name:migrated

# Import with specific configurations
cat /tmp/container_name.tar.gz | docker import \
  --change "ENV DEBUG=true" \
  --change 'CMD ["nginx", "-g", "daemon off;"]' \
  - container_name:migrated

# Create and start container from imported image
docker run -d --name container_name container_name:migrated

Limitaciones: Este método no preserva volúmenes, redes o metadatos. Use para contenedores sin estado o como parte de una estrategia más amplia.

Método de Migración 2: Save/Load de Docker (Imágenes)

Mejor para: Preservar capas de imagen, migraciones offline, imágenes personalizadas

Guardar Imágenes

# Save single image
docker save -o /tmp/image_name.tar image_name:tag

# Save multiple images
docker save -o /tmp/all_images.tar image1:tag image2:tag image3:tag

# Save all images (use with caution)
docker save $(docker images -q) -o /tmp/all_docker_images.tar

# Compress saved images
docker save image_name:tag | gzip > /tmp/image_name.tar.gz

# Save images with progress indication
docker save image_name:tag | pv | gzip > /tmp/image_name.tar.gz

# Transfer to destination
rsync -avz --progress /tmp/image_name.tar.gz user@new-server:/tmp/

Cargar Imágenes

# On destination server: Load image
docker load -i /tmp/image_name.tar

# Load compressed image
gunzip -c /tmp/image_name.tar.gz | docker load

# Verify loaded images
docker images | grep image_name

# Tag loaded image if needed
docker tag old_name:tag new_name:tag

Método de Migración 3: Registro Docker

Mejor para: Entornos de producción, múltiples migraciones, equipos distribuidos

Configurar Registro Privado

# On registry server: Run Docker registry
docker run -d \
  -p 5000:5000 \
  --restart=always \
  --name registry \
  -v /mnt/registry:/var/lib/registry \
  registry:2

# With authentication
mkdir -p /mnt/registry/auth
docker run --entrypoint htpasswd registry:2 \
  -Bbn username password > /mnt/registry/auth/htpasswd

docker run -d \
  -p 5000:5000 \
  --restart=always \
  --name registry \
  -v /mnt/registry:/var/lib/registry \
  -v /mnt/registry/auth:/auth \
  -e "REGISTRY_AUTH=htpasswd" \
  -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
  -e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \
  registry:2

Push y Pull de Imágenes

# On source server: Tag image for registry
docker tag image_name:tag registry.yourdomain.com:5000/image_name:tag

# Push to registry
docker push registry.yourdomain.com:5000/image_name:tag

# Push all images to registry
for image in $(docker images --format "{{.Repository}}:{{.Tag}}" | grep -v "<none>"); do
  registry_image="registry.yourdomain.com:5000/${image}"
  docker tag $image $registry_image
  docker push $registry_image
done

# On destination server: Pull from registry
docker pull registry.yourdomain.com:5000/image_name:tag

# Use pulled image
docker run -d registry.yourdomain.com:5000/image_name:tag

Usar Docker Hub o Registros Cloud

# Login to Docker Hub
docker login

# Tag and push to Docker Hub
docker tag local_image:tag username/image_name:tag
docker push username/image_name:tag

# On destination: Pull from Docker Hub
docker pull username/image_name:tag

# For AWS ECR
aws ecr get-login-password --region region | \
  docker login --username AWS --password-stdin account.dkr.ecr.region.amazonaws.com

docker tag image:tag account.dkr.ecr.region.amazonaws.com/repository:tag
docker push account.dkr.ecr.region.amazonaws.com/repository:tag

Método de Migración 4: Migración Docker Compose

Mejor para: Aplicaciones multi-contenedor, configuraciones definidas

Exportar Configuración Docker Compose

# On source server: Create docker-compose.yml if needed
docker-compose config > docker-compose-export.yml

# Backup entire compose project
tar czf /tmp/compose-project.tar.gz \
  docker-compose.yml \
  .env \
  volumes/ \
  configs/

# Transfer to destination
scp /tmp/compose-project.tar.gz user@new-server:/tmp/

Migración Completa Compose

# Example docker-compose.yml with named volumes
cat > docker-compose.yml << 'EOF'
version: '3.8'

services:
  webapp:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - webapp_data:/usr/share/nginx/html
      - ./configs:/etc/nginx/conf.d
    networks:
      - frontend
    restart: unless-stopped

  database:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_DATABASE: ${DB_NAME}
    volumes:
      - db_data:/var/lib/mysql
    networks:
      - backend
    restart: unless-stopped

volumes:
  webapp_data:
  db_data:

networks:
  frontend:
  backend:
EOF

# Backup volumes before migration
docker-compose down
docker run --rm \
  -v compose_webapp_data:/source \
  -v /backup:/backup \
  alpine tar czf /backup/webapp_data.tar.gz -C /source .

docker run --rm \
  -v compose_db_data:/source \
  -v /backup:/backup \
  alpine tar czf /backup/db_data.tar.gz -C /source .

# Transfer backups
scp /backup/*.tar.gz user@new-server:/backup/

# On destination server: Extract project
cd /opt/docker-projects
tar xzf /tmp/compose-project.tar.gz

# Create volumes and restore data
docker volume create compose_webapp_data
docker volume create compose_db_data

docker run --rm \
  -v compose_webapp_data:/target \
  -v /backup:/backup \
  alpine sh -c "cd /target && tar xzf /backup/webapp_data.tar.gz"

docker run --rm \
  -v compose_db_data:/target \
  -v /backup:/backup \
  alpine sh -c "cd /target && tar xzf /backup/db_data.tar.gz"

# Start services
docker-compose up -d

Estrategias de Migración de Volúmenes

Respaldo y Restauración Directa de Volúmenes

# Method 1: Backup volume to tar file
docker run --rm \
  -v volume_name:/data \
  -v /backup:/backup \
  alpine tar czf /backup/volume_name.tar.gz -C /data .

# Transfer backup
scp /backup/volume_name.tar.gz user@new-server:/backup/

# On destination: Create volume and restore
docker volume create volume_name
docker run --rm \
  -v volume_name:/data \
  -v /backup:/backup \
  alpine sh -c "cd /data && tar xzf /backup/volume_name.tar.gz"

# Method 2: Using rsync for incremental sync
# Find volume location
docker volume inspect volume_name | jq -r '.[0].Mountpoint'

# Sync volume data
sudo rsync -avz \
  /var/lib/docker/volumes/volume_name/_data/ \
  user@new-server:/var/lib/docker/volumes/volume_name/_data/

Migración de Volúmenes con Mínimo Downtime

# Create migration script for live sync
cat > /root/sync-docker-volumes.sh << 'EOF'
#!/bin/bash

SOURCE_VOLUME=$1
SOURCE_HOST=$2
DEST_VOLUME=$3

# Get volume mount point
SOURCE_PATH=$(docker volume inspect $SOURCE_VOLUME -f '{{.Mountpoint}}')

# Initial sync (while container running)
rsync -avz --progress \
  $SOURCE_PATH/ \
  user@$SOURCE_HOST:/var/lib/docker/volumes/$DEST_VOLUME/_data/

# Stop container
docker stop container_name

# Final sync (fast, only changes)
rsync -avz --delete \
  $SOURCE_PATH/ \
  user@$SOURCE_HOST:/var/lib/docker/volumes/$DEST_VOLUME/_data/

echo "Volume synced successfully"
EOF

chmod +x /root/sync-docker-volumes.sh

Migración de Configuración de Red

# Export network configuration
docker network inspect network_name > /tmp/network_config.json

# Create network on destination server
docker network create \
  --driver bridge \
  --subnet 172.20.0.0/16 \
  --gateway 172.20.0.1 \
  network_name

# For custom networks with specific options
docker network create \
  --driver bridge \
  --opt "com.docker.network.bridge.name"="docker1" \
  --opt "com.docker.network.bridge.enable_ip_masquerade"="true" \
  --subnet 192.168.10.0/24 \
  custom_network

Migración con Cero Downtime con Balanceador de Carga

# Setup HAProxy for load balancing during migration
cat > haproxy.cfg << 'EOF'
global
    daemon
    maxconn 256

defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms

frontend http_front
    bind *:80
    default_backend servers

backend servers
    balance roundrobin
    server old_server old-server-ip:80 check
    server new_server new-server-ip:80 check backup
EOF

# Run HAProxy
docker run -d \
  --name haproxy \
  -p 80:80 \
  -v $(pwd)/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro \
  haproxy:latest

# Gradually shift traffic by changing server weights
# Update config to: weight 1 for old, weight 9 for new
# Then: weight 0 for old, weight 10 for new

Migración Docker Swarm

# For Swarm services, use stack deployment
# On source Swarm manager: Export stack
docker stack deploy --compose-file docker-compose.yml stack_name

# Save stack configuration
docker service ls > /tmp/services.txt
docker stack services stack_name > /tmp/stack_services.txt

# On destination: Initialize Swarm
docker swarm init --advertise-addr new-server-ip

# Deploy stack to new Swarm
docker stack deploy --compose-file docker-compose.yml stack_name

# Drain old node and remove from Swarm
docker node update --availability drain old-node
docker node rm old-node

Migración Kubernetes (Docker a K8s)

# Convert Docker Compose to Kubernetes manifests
# Install kompose
curl -L https://github.com/kubernetes/kompose/releases/download/v1.31.2/kompose-linux-amd64 -o kompose
chmod +x kompose
sudo mv kompose /usr/local/bin/

# Convert compose file
kompose convert -f docker-compose.yml

# Apply to Kubernetes cluster
kubectl apply -f .

# Or use Helm chart
helm create myapp
# Edit values.yaml with container configurations
helm install myapp ./myapp

Verificación Post-Migración

# Verify containers are running
docker ps -a

# Check container logs
docker logs container_name --tail 100

# Test container connectivity
docker exec container_name ping google.com

# Verify volumes are mounted
docker inspect container_name | jq '.[0].Mounts'

# Check volume data
docker run --rm -v volume_name:/data alpine ls -lah /data

# Test application functionality
curl http://new-server-ip
curl -I https://new-server-ip

# Monitor resource usage
docker stats

# Check container health
docker inspect container_name | jq '.[0].State.Health'

Script de Migración Automatizada

# Complete migration script
cat > /root/migrate-docker-containers.sh << 'EOF'
#!/bin/bash

set -e

SOURCE_HOST=$1
DEST_HOST=$2
CONTAINER_NAME=$3

echo "=== Docker Container Migration Script ==="
echo "Source: $SOURCE_HOST"
echo "Destination: $DEST_HOST"
echo "Container: $CONTAINER_NAME"

# Step 1: Get container configuration
echo "Fetching container configuration..."
ssh $SOURCE_HOST "docker inspect $CONTAINER_NAME" > /tmp/container_config.json

IMAGE=$(jq -r '.[0].Config.Image' /tmp/container_config.json)
VOLUMES=$(jq -r '.[0].Mounts[].Name' /tmp/container_config.json)

echo "Image: $IMAGE"
echo "Volumes: $VOLUMES"

# Step 2: Save and transfer image
echo "Saving image..."
ssh $SOURCE_HOST "docker save $IMAGE | gzip" | \
  ssh $DEST_HOST "gunzip | docker load"

# Step 3: Migrate volumes
for volume in $VOLUMES; do
  echo "Migrating volume: $volume"

  # Create volume on destination
  ssh $DEST_HOST "docker volume create $volume"

  # Sync volume data
  ssh $SOURCE_HOST "docker run --rm -v $volume:/data alpine tar c -C /data ." | \
    ssh $DEST_HOST "docker run --rm -i -v $volume:/data alpine tar x -C /data"
done

# Step 4: Generate run command
echo "Generating docker run command..."
RUN_CMD=$(jq -r '
  "docker run -d " +
  "--name " + .[0].Name[1:] + " " +
  (if .[0].HostConfig.RestartPolicy.Name != "" then
    "--restart=" + .[0].HostConfig.RestartPolicy.Name + " "
  else "" end) +
  (.[0].HostConfig.PortBindings | to_entries | map(
    "-p " + (.value[0].HostPort) + ":" + (.key | split("/")[0]) + " "
  ) | join("")) +
  (.[0].Mounts | map(
    "-v " + .Name + ":" + .Destination + " "
  ) | join("")) +
  (.[0].Config.Env | map(
    "-e \"" + . + "\" "
  ) | join("")) +
  .[0].Config.Image
' /tmp/container_config.json)

echo "Run command: $RUN_CMD"

# Step 5: Stop source container
echo "Stopping source container..."
ssh $SOURCE_HOST "docker stop $CONTAINER_NAME"

# Step 6: Start container on destination
echo "Starting container on destination..."
ssh $DEST_HOST "$RUN_CMD"

# Step 7: Verify
echo "Verifying migration..."
ssh $DEST_HOST "docker ps | grep $CONTAINER_NAME"

echo "=== Migration completed successfully ==="
EOF

chmod +x /root/migrate-docker-containers.sh

# Usage
# ./migrate-docker-containers.sh user@old-server user@new-server container_name

Procedimientos de Rollback

# Emergency rollback script
cat > /root/rollback-docker-migration.sh << 'EOF'
#!/bin/bash

CONTAINER_NAME=$1

echo "Rolling back container: $CONTAINER_NAME"

# Stop container on new server
docker stop $CONTAINER_NAME
docker rm $CONTAINER_NAME

# Start container on old server (if stopped)
ssh user@old-server "docker start $CONTAINER_NAME"

# Update load balancer or DNS if applicable
# Revert application configuration

echo "Rollback completed"
EOF

chmod +x /root/rollback-docker-migration.sh

Monitoreo Durante Migración

# Monitor both servers during migration
cat > /root/monitor-docker-migration.sh << 'EOF'
#!/bin/bash

while true; do
  echo "=== $(date) ==="

  echo "Old Server:"
  ssh old-server "docker ps --format 'table {{.Names}}\t{{.Status}}'"

  echo ""
  echo "New Server:"
  ssh new-server "docker ps --format 'table {{.Names}}\t{{.Status}}'"

  echo ""
  echo "Network Test:"
  curl -o /dev/null -s -w "%{http_code}\n" http://new-server-ip

  sleep 10
done
EOF

chmod +x /root/monitor-docker-migration.sh

Mejores Prácticas y Recomendaciones

Consideraciones de Seguridad

# Use secure registries with TLS
# Generate certificates for private registry
openssl req -newkey rsa:4096 -nodes -sha256 -keyout domain.key \
  -x509 -days 365 -out domain.crt

# Configure Docker to trust registry
sudo mkdir -p /etc/docker/certs.d/registry.domain.com:5000
sudo cp domain.crt /etc/docker/certs.d/registry.domain.com:5000/ca.crt

# Scan images for vulnerabilities before migration
docker scan image_name:tag

# Use secrets management
echo "my_secret_data" | docker secret create my_secret -

Optimización de Rendimiento

# Enable Docker build cache for faster rebuilds
docker build --cache-from registry.com/image:latest -t image:new .

# Use multi-stage builds to reduce image size
# Optimize layer ordering in Dockerfiles

# Configure Docker storage driver for performance
sudo nano /etc/docker/daemon.json
{
  "storage-driver": "overlay2",
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

sudo systemctl restart docker

Conclusión

La migración de contenedores Docker es un proceso versátil con múltiples enfoques dependiendo de sus requisitos específicos. Conclusiones clave:

  1. Elija el método correcto: Registro para producción, export/import para casos simples
  2. No olvide los volúmenes: La persistencia de datos es crítica para aplicaciones con estado
  3. Pruebe exhaustivamente: Verifique funcionalidad antes del cambio final
  4. Minimice downtime: Use balanceadores de carga y operaciones paralelas
  5. Documente todo: Registre configuraciones y procedimientos
  6. Monitoree continuamente: Vigile ambos entornos antiguo y nuevo
  7. Planifique para rollback: Siempre tenga una ruta de escape

Ya sea moviendo un solo contenedor o una arquitectura completa de microservicios, seguir estas estrategias asegura una migración exitosa con mínima interrupción. La portabilidad de Docker hace la migración directa, pero la atención al detalle en gestión de volúmenes, configuración de red y orquestación separa las migraciones exitosas de las problemáticas.

Al dominar estas técnicas, estará equipado para manejar cualquier escenario de migración Docker con confianza y eficiencia.