Tutorial: migrar um host Docker em produção para outra VPS com mínima indisponibilidade usando rsync, volumes e Docker Compose

Deploy & DevOps · Por · Publicado em · 9 min de leitura

Folha com gráficos coloridos e diagramas desfocados sobre mesa, com lápis ao lado
Gráficos e anotações que evocam planejamento e inventário antes da migração Foto: RDNE Stock project / Pexels

Tutorial: migrar um host Docker em produção para outra VPS com mínima indisponibilidade usando rsync, volumes e Docker Compose. Você fará uma sincronização em duas etapas, validará o destino e trocará o tráfego por DNS ou proxy. O foco é reduzir o downtime sem arriscar a integridade dos dados.

Antes de começar: pré-requisitos e janela de corte

Planeje uma janela curta para o corte final. Reduza o TTL dos registros DNS horas antes. Faça backup do que é crítico antes de mexer em dados. Tenha acesso sudo/root em ambas as VPS e autenticação por chave SSH.

Cheque espaço em disco no destino e a compatibilidade de arquitetura (amd64/arm64) das imagens. Prepare uma lista do que será migrado: Compose files, variáveis de ambiente, secrets e volumes persistentes.

Se precisar reforçar a base do servidor de destino, confira o checklist de segurança para VPS Linux.

  • Acesso SSH com chave a ambas as VPS
  • Sudo/root para manipular volumes em /var/lib/docker/volumes
  • TTL do DNS reduzido previamente
  • Backup recente dos dados críticos
  • Mesma arquitetura ou imagens multi-arquitetura

Mapeie serviços, volumes e portas no host atual

Faça um inventário do que roda hoje. Anote nomes dos serviços, portas expostas, redes e volumes. Isso evita surpresas no corte. Os comandos abaixo ajudam a extrair rapidamente a visão do ambiente atual.

  • Confirme quais volumes são stateful (banco, uploads, filas)
  • Revise healthchecks e dependências entre serviços
  • Liste variáveis sensíveis que não estão em .env (ex.: 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)

Prepare a VPS de destino (usuário, firewall, Docker/Compose)

Crie um usuário para deploy, habilite firewall e instale Docker + Compose plugin. Reaplique sysctls ou limites usados no host atual. Se preferir um guia dedicado de instalação, veja como instalar Docker e Docker Compose no Linux.

  • Separe um diretório para projetos (ex.: /opt/projetos)
  • Habilite UFW apenas nas portas necessárias
  • Garanta versão mínima compatível 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

Sincronize Compose, variáveis e arquivos de configuração

Copie o repositório do projeto, arquivos docker-compose*.yml, .env e quaisquer overrides para o destino. Não copie docker.sock nem arquivos temporários. Mantenha a mesma estrutura de diretórios para evitar ajustes desnecessários nos paths.

  • Use --exclude para ignorar diretórios grandes (ex.: node_modules)
  • Confirme permissões dos arquivos .env e secrets no destino
  • Evite credenciais hardcoded; mantenha .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

Primeira sincronização dos volumes (ambiente rodando)

Faremos duas passagens com rsync: uma a quente (app ainda atendendo) e outra rápida durante a janela de corte. Para bancos de dados, prefira replica/dump; rsync direto do _data pode corromper se houver escrita. Para volumes de uploads e logs, rsync funciona bem.

  • Crie os volumes no destino antes de copiar
  • Use --numeric-ids para preservar UIDs/GIDs
  • Evite sincronizar sockets e arquivos em uso; foque no _data
Cenário Estratégia recomendada Risco de inconsistência Observações
Uploads/arquivos estáticos Rsync em duas passagens Baixo Parar app apenas na sincronização final
Banco relacional (PostgreSQL/MySQL) Replica/backup+restore Alto se via rsync Use dump/replicação; rsync só com serviço parado
Caches/filas (Redis/RabbitMQ) Recriar vazio Baixo Perda aceitável de cache; esvazie filas antes
Bind mounts de config Rsync simples Baixo Garanta paths idênticos em 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

Teste no destino sem expor tráfego público

Suba o stack no destino e valide com curl e healthchecks em localhost ou via túnel SSH. Não aponte DNS ainda. Se usar portas padrão, mantenha firewall fechado externamente enquanto testa. Ajuste variáveis específicas do ambiente no .env do destino.

  • Teste dependências: conexão a banco, filas e storage externo
  • Confirme redes e nomes de host internos do Compose
  • Garanta que imagens corretas foram baixadas
# 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

Janela de corte: parar escrita, rsync final e troca de DNS

Agora faremos o corte. Este é o único momento de indisponibilidade planejada. Pare os serviços na origem, faça um rsync final com --delete e suba no destino. Cuidado: --delete apaga arquivos no destino que não existirem na origem. Em seguida, troque o DNS e aguarde a propagação conforme o TTL.

  • Avise usuários e monitore métricas em tempo real
  • Tenha rollback simples: manter origem intacta até validar
  • Atualize tokens e allowlists que apontem 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

Como confirmar que funcionou (checks pós-corte)

Valide aplicação, métricas e logs. Confira que nenhum container está reiniciando em loop. Monitore erros 4xx e 5xx. Não desative o host antigo até concluir validações e ter um backup pós-migração.

  • Abra a aplicação por HTTPS e verifique login e fluxos críticos
  • Confirme integridade de uploads e permissões de arquivos
  • Cheque métricas de CPU/RAM, filas e jobs agendados
# 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/

Opções avançadas para reduzir ainda mais o downtime

Se precisa de troca quase instantânea, coloque um proxy na frente apontando para origem e destino e mude o tráfego por healthcheck. Veja o passo a passo de HAProxy com Let's Encrypt e healthchecks.

Para orquestrar releases paralelos por stack, use a estratégia de deploy blue-green com Docker Compose. Para acelerar downloads de imagens durante o corte, um registry Docker privado/mirror ajuda muito.

  • Proxy com healthchecks para cutover rápido
  • Blue/green em Compose para alternância segura
  • Registry/mirror para puxar imagens localmente

Solução de problemas comuns

Alguns erros aparecem com frequência após migrações. A lista abaixo traz sintomas e correções usuais. Cuidado com permissões: UIDs/GIDs diferentes entre hosts quebram acessos a arquivos montados nos containers.

  • Permission denied em uploads: ajuste dono do _data (chown) conforme UID do processo no container.
  • Containers reiniciando: verifique variáveis obrigatórias no .env e ordem de dependências.
  • Arquitetura diferente (amd64/arm64): use imagens multi-arch e recrie containers.
  • Portas fechadas: revise UFW/security groups e confirme regras de entrada.
  • Pulls lentos no corte: configure um mirror de registry local.
  • Banco corrompido após rsync: restaure de backup ou repita usando dump/replicação com serviço parado.

Perguntas frequentes

Como migrar um banco de dados Docker com segurança?

Para bancos relacionais, use réplica temporária ou dump e restore. Evite rsync do diretório _data com o serviço escrevendo. Faça a primeira cópia com a instância em read-only ou parada e valide a integridade antes do corte final.

É possível migrar sem downtime?

Sem replicação ou proxy, haverá uma janela curta. Para reduzir quase a zero, use um proxy com healthchecks, prepare o destino, sincronize dados, congele a escrita no corte e mude o tráfego de forma atômica. Ainda assim, planeje rollback.

E se a nova VPS for de outra arquitetura (arm64 vs amd64)?

Certifique-se de que as imagens tenham manifestos multi-arquitetura. Recrie containers no destino. Volumes de dados não dependem da arquitetura, mas os binários dentro das imagens sim. Teste no destino antes do corte.

Como lidar com variáveis de ambiente e secrets?

Armazene .env fora do repositório ou criptografado. Sincronize separadamente e verifique permissões. Secrets externos (tokens, chaves) devem ser revisados após a troca de IP ou DNS para evitar bloqueios por allowlists.

Preciso baixar todas as imagens novamente?

Não necessariamente. Você pode usar um registry privado ou mirror para acelerar pulls no destino. Outra opção é usar docker save e docker load, mas costuma ser mais simples puxar as imagens versionadas pelo Compose no novo host.

Quer revisar seu plano de migração antes do corte?

Se você precisa de uma segunda opinião sobre o desenho do corte, volumes críticos e rollback, fale comigo e leve um checklist sob medida para seu stack.

Conversar sobre a migração

Veja também