Tutorial: migrar un host Docker en producción a otra VPS con mínima indisponibilidad usando rsync, volúmenes y Docker Compose — paso a paso
Deploy & DevOps · Por Kevin Marcondes · Publicado el · 10 min de lectura
Tutorial: migrar un host Docker en producción a otra VPS con mínima indisponibilidad usando rsync y Docker Compose. Realizarás una sincronización en dos pasadas, validarás el destino y cambiarás el tráfico por DNS o proxy. El objetivo es reducir el downtime sin comprometer la integridad de los datos.
Antes de empezar: prerequisitos y ventana de corte
Planifica una ventana corta para el corte final. Reduce el TTL de los registros DNS varias horas antes. Haz backup de lo crítico antes de tocar datos. Ten acceso sudo/root en ambas VPS y autenticación por clave SSH.
Revisa el espacio en disco en el destino y la compatibilidad de arquitectura (amd64/arm64) de las imágenes. Prepara una lista de lo que será migrado: archivos Compose, variables de entorno, secrets y volúmenes persistentes.
Si necesitas reforzar la base del servidor destino, consulta el checklist de seguridad para VPS Linux.
- Acceso SSH con clave a ambas VPS
- Sudo/root para manipular volúmenes en /var/lib/docker/volumes
- TTL del DNS reducido previamente
- Backup reciente de los datos críticos
- Misma arquitectura o imágenes multi‑arquitectura
Mapea servicios, volúmenes y puertos en el host actual
Haz un inventario de lo que está en ejecución hoy. Anota nombres de servicios, puertos expuestos, redes y volúmenes. Esto evita sorpresas en el corte. Los comandos abajo ayudan a extraer rápidamente la visión del entorno actual.
- Confirma qué volúmenes son stateful (base de datos, uploads, colas)
- Revisa los healthchecks y dependencias entre servicios
- Lista variables sensibles que no estén en .env (p. ej.: secrets externos)
# No host de origem
# Projetos/stack e containers em execução
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}'
# Volumes Docker
docker volume ls
# Mapas de montagem por container (mostra bind mounts e volumes)
docker inspect -f '{{ .Name }} -> {{range .Mounts}}{{.Type}}:{{.Source}}:{{.Destination}} {{end}}' $(docker ps -q)
Prepara la VPS destino (usuario, firewall, Docker/Compose)
Crea un usuario para deploy, habilita firewall e instala Docker + plugin de Compose. Reaplica sysctls o límites usados en el host actual. Si prefieres una guía dedicada de instalación, consulta cómo instalar Docker y Docker Compose en Linux.
- Reserva un directorio para proyectos (ej.: /opt/proyectos)
- Habilita UFW solo en los puertos necesarios
- Garantiza una versión mínima compatible de Compose
# Na VPS de destino (Debian/Ubuntu)
sudo apt-get update
sudo apt-get install -y docker.io docker-compose-plugin rsync ufw
# Usuário de deploy e acesso ao Docker
sudo useradd -m -s /bin/bash deploy || true
sudo usermod -aG docker deploy
# Firewall básico
sudo ufw allow OpenSSH
# Abra apenas o que seu stack realmente usa (exemplo: HTTP/HTTPS)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Sincroniza Compose, variables y archivos de configuración
Copia el repositorio del proyecto, docker-compose*.yml, .env y cualquier override al destino. No copies docker.sock ni archivos temporales. Mantén la misma estructura de directorios para evitar ajustes innecesarios en los paths.
- Usa --exclude para ignorar directorios grandes (p. ej.: node_modules)
- Confirma permisos de los archivos .env y secrets en el destino
- Evita credenciales hardcoded; mantiene .env separado
# Do host de origem para o destino (ajuste caminhos/host)
DEST="deploy@IP_OU_HOST_DESTINO"
PROJ="/opt/meu-stack"
rsync -avz --progress -e ssh \
--exclude '.git' --exclude 'node_modules' \
${PROJ}/ ${DEST}:${PROJ}/
# Se .env não estiver no repo
rsync -avz -e ssh ${PROJ}/.env ${DEST}:${PROJ}/.env
Primera sincronización de volúmenes (entorno en ejecución)
Haremos dos pasadas con rsync: una en caliente (la app aún atendiendo) y otra rápida durante la ventana de corte. Para bases de datos, prefiere réplica/dump; rsync directo del _data puede corromper si hay escrituras concurrentes. Para volúmenes de uploads y logs, rsync funciona bien.
- Crea los volúmenes en el destino antes de copiar
- Usa --numeric-ids para preservar UIDs/GIDs
- Evita sincronizar sockets y archivos en uso; céntrate en el _data
| Escenario | Estrategia recomendada | Riesgo de inconsistencia | Observaciones |
|---|---|---|---|
| Uploads/archivos estáticos | Rsync en dos pasadas | Bajo | Detener la app solo en la sincronización final |
| Base relacional (PostgreSQL/MySQL) | Réplica/dump y restore | Alto si es vía rsync | Usa dump/replicación; rsync solo con servicio detenido |
| Caches/colas (Redis/RabbitMQ) | Recrear vacío | Bajo | Pérdida aceptable de cache; vacía las colas antes |
| Bind mounts de configuración | Rsync simple | Bajo | Asegura rutas idénticas en Compose |
# Do host de origem: crie e prepare volumes no destino, então rsync "quente"
DEST="deploy@IP_OU_HOST_DESTINO"
VOLS=(app_uploads db_data redis_data)
ssh -o StrictHostKeyChecking=no ${DEST} 'for v in app_uploads db_data redis_data; do sudo docker volume create $v >/dev/null; sudo mkdir -p /var/lib/docker/volumes/$v/_data; done'
for v in "${VOLS[@]}"; do
sudo rsync -aHAX --numeric-ids --info=progress2 \
-e ssh /var/lib/docker/volumes/${v}/_data/ \
${DEST}:/var/lib/docker/volumes/${v}/_data/
done
Prueba en el destino sin exponer tráfico público
Levanta el stack en el destino y valida con curl y healthchecks en localhost o mediante túnel SSH. No apuntes el DNS todavía. Si usas puertos estándar, mantén el firewall cerrado externamente mientras pruebas. Ajusta variables específicas del entorno en el .env del destino.
- Prueba dependencias: conexión a BD, colas y storage externo
- Confirma redes y nombres de host internos del Compose
- Asegura que las imágenes correctas fueron descargadas
# Na VPS de destino, no diretório do projeto
cd /opt/meu-stack
sudo docker compose --env-file .env pull
sudo docker compose --env-file .env up -d
sudo docker compose ps
# Testes locais
curl -fsS http://127.0.0.1:80/health || true
sudo docker compose logs --no-color --tail=50
Ventana de corte: detener escrituras, rsync final y cambio de DNS
Ahora hacemos el corte. Este es el único momento de indisponibilidad planificada. Para: detén los servicios en origen, realiza un rsync final con --delete y levanta en el destino. Cuidado: --delete borra archivos en el destino que no existan en el origen. Luego cambia el DNS y espera la propagación según el TTL.
- Avisa a usuarios y monitorea métricas en tiempo real
- Ten un rollback simple: mantén la origen intacta hasta validar
- Actualiza tokens y allowlists que apunten por IP
# 1) Parar serviços na ORIGEM
cd /opt/meu-stack
sudo docker compose --env-file .env down
# 2) Sincronização FINAL dos volumes (com --delete)
DEST="deploy@IP_OU_HOST_DESTINO"
for v in app_uploads db_data redis_data; do
sudo rsync -aHAX --numeric-ids --delete --info=progress2 \
-e ssh /var/lib/docker/volumes/${v}/_data/ \
${DEST}:/var/lib/docker/volumes/${v}/_data/
done
# 3) Subir no DESTINO
ssh ${DEST} 'cd /opt/meu-stack && sudo docker compose --env-file .env up -d && sudo docker compose ps'
# 4) Trocar DNS (exemplo: verifique resolução)
# Altere o A/AAAA no provedor e valide depois
# (ajuste seu domínio)
dig +short A exemplo.com
Cómo confirmar que funcionó (checks pos‑corte)
Valida la aplicación, métricas y logs. Comprueba que ningún contenedor esté en bucle de reinicios. Monitorea errores 4xx y 5xx. No desactives el host antiguo hasta completar las validaciones y tener un backup post‑migración.
- Abre la aplicación por HTTPS y verifica login y flujos críticos
- Confirma integridad de uploads y permisos de archivos
- Revisa métricas de CPU/RAM, colas y jobs programados
# Na VPS de destino
sudo docker compose ps
sudo docker compose logs --no-color --tail=100 | sed -n '1,100p'
# Endpoints de saúde e páginas críticas (ajuste URLs)
curl -I https://exemplo.com/health
curl -I https://exemplo.com/
Opciones avanzadas para reducir aún más el downtime
Si necesitas un cutover casi instantáneo, pon un proxy delante apuntando a origen y destino y cambia el tráfico por healthchecks. Revisa el tutorial de HAProxy con Let's Encrypt y healthchecks.
Para gestionar releases paralelos por stack, usa la estrategia de blue-green con Docker Compose. Para acelerar descargas de imágenes durante el corte, un registro Docker privado/mirror ayuda mucho.
- Proxy con healthchecks para cutover rápido
- Blue/green en Compose para alternancia segura
- Registry/mirror para tirar imágenes localmente
Solución de problemas comunes
Algunos errores aparecen con frecuencia tras migraciones. La lista abajo aporta síntomas y correcciones habituales. Cuidado con permisos: UIDs/GIDs distintos entre hosts rompen accesos a archivos montados en containers.
- Permission denied en uploads: ajusta el propietario del _data (chown) según el UID del proceso dentro del container.
- Contenedores reiniciando: revisa variables obligatorias en .env y el orden de dependencias.
- Arquitectura distinta (amd64/arm64): usa imágenes multi‑arch y recrea los contenedores.
- Puertos cerrados: revisa UFW/security groups y confirma reglas de entrada.
- Pulls lentos durante el corte: configura un mirror de registry local.
- Base de datos corrupta tras rsync: restaura desde backup o repite usando dump/replicación con el servicio parado.
Preguntas frecuentes
¿Cómo migrar una base de datos Docker con seguridad?
Para bases relacionales, usa réplica temporal o dump y restore. Evita rsync del directorio _data con el servicio escribiendo. Haz la primera copia con la instancia en read‑only o parada y valida la integridad antes del corte final.
¿Es posible migrar sin downtime?
Sin replicación o proxy habrá una ventana corta. Para reducirla casi a cero, usa un proxy con healthchecks, prepara el destino, sincroniza datos, congela la escritura en el corte y cambia el tráfico de forma atómica. Aun así, planifica rollback.
¿Y si la nueva VPS es de otra arquitectura (arm64 vs amd64)?
Asegúrate de que las imágenes tengan manifiestos multi‑arquitectura. Recrera los contenedores en el destino. Los volúmenes de datos no dependen de la arquitectura, pero los binarios dentro de las imágenes sí. Prueba en el destino antes del corte.
¿Cómo manejar variables de entorno y secrets?
Almacena .env fuera del repositorio o cifrado. Sincroniza por separado y verifica permisos. Secrets externos (tokens, claves) deben revisarse tras el cambio de IP o DNS para evitar bloqueos por allowlists.
¿Necesito bajar todas las imágenes otra vez?
No necesariamente. Puedes usar un registry privado o mirror para acelerar los pulls en el destino. Otra opción es usar docker save y docker load, pero suele ser más simple tirar las imágenes versionadas vía Compose en el nuevo host.
¿Quieres revisar tu plan de migración antes del corte?
Si necesitas una segunda opinión sobre el diseño del corte, volúmenes críticos y rollback, contáctame y obtén un checklist a medida para tu stack.
Conversar sobre la migraciónVer también
- Tutorial: configurar mTLS entre Traefik y servicios en Docker Compose usando smallstep CA y certificados automáticos — paso a paso
- Tutorial: integrar Sentry en el pipeline de deploy con GitHub Actions para subir source maps automáticamente y generar issues (PHP y JavaScript) — paso a paso
- Configurar retención y compresión de logs de contenedores Docker con journald + logrotate + rsyslog en VPS con poco disco
- Tutorial: configurar GitHub Actions para tests de integración con PostgreSQL en contenedores efímeros y rollback automático de migrations — guía práctica