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
- Consolidación de Servidores: Mover múltiples contenedores a menos hosts
- Actualización de Infraestructura: Migrar a hardware o versiones de OS más nuevas
- Migración a la Nube: Transición de on-premises a cloud
- Recuperación ante Desastres: Restaurar servicios en infraestructura de respaldo
- Balanceo de Carga: Distribuir contenedores entre múltiples hosts
- Desarrollo a Producción: Promover contenedores validados
- 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:
- Elija el método correcto: Registro para producción, export/import para casos simples
- No olvide los volúmenes: La persistencia de datos es crítica para aplicaciones con estado
- Pruebe exhaustivamente: Verifique funcionalidad antes del cambio final
- Minimice downtime: Use balanceadores de carga y operaciones paralelas
- Documente todo: Registre configuraciones y procedimientos
- Monitoree continuamente: Vigile ambos entornos antiguo y nuevo
- 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.


