01Introduction
Server migrations are stressful. You're moving years of data and configuration to new hardware. This guide provides a systematic approach to migrating Docker Compose stacks safely.
02Pre-Migration Checklist
Before touching anything, document your current setup and verify backups.
1# Document current state2# 1. List all containers3docker ps -a > containers.txt45# 2. List all volumes6docker volume ls > volumes.txt78# 3. List all networks9docker network ls > networks.txt1011# 4. Export docker-compose configs12find / -name "docker-compose.yml" 2>/dev/null > compose-files.txt1314# 5. Document environment variables15cat .env > env-backup.txt1617# 6. Verify backup integrity18# Restore backups to test environment first!1920# 7. Note external dependencies21# - DNS records22# - SSL certificates23# - External databases24# - API endpointsNever start migration without tested backups. Verify you can restore before proceeding.
03Prepare the New Server
Set up Docker and prepare the new server to match your current environment.
1# Install Docker2curl -fsSL https://get.docker.com | sh3sudo usermod -aG docker $USER45# Install Docker Compose (v2)6sudo apt install docker-compose-plugin78# Verify installation9docker --version10docker compose version1112# Create same directory structure13mkdir -p /opt/docker14mkdir -p /data/backups1516# Copy configuration files17scp -r user@old-server:/opt/docker/myapp /opt/docker/1819# Install same reverse proxy20# (Traefik, Caddy, NPM - whatever you're using)04Backup All Volumes
Create comprehensive backups of all Docker volumes. This is the critical step.
1#!/bin/bash2# backup-volumes.sh - Run on old server34BACKUP_DIR="/data/migration-backup"5mkdir -p "$BACKUP_DIR"67# Stop containers for consistent backup8docker compose stop910# Backup each volume11for volume in $(docker volume ls -q); do12 echo "Backing up $volume..."13 docker run --rm \14 -v "$volume":/source:ro \15 -v "$BACKUP_DIR":/backup \16 alpine tar -czf "/backup/${volume}.tar.gz" -C /source .17done1819# Backup bind mounts (if any)20tar -czf "$BACKUP_DIR/bind-mounts.tar.gz" /path/to/bind/mounts2122# Create checksum file23cd "$BACKUP_DIR"24sha256sum *.tar.gz > checksums.txt2526echo "Backups complete in $BACKUP_DIR"27ls -lh "$BACKUP_DIR"05Transfer Data to New Server
Securely transfer backups and verify integrity after transfer.
1# Option 1: rsync (resume-capable, recommended for large transfers)2rsync -avz --progress \3 user@old-server:/data/migration-backup/ \4 /data/migration-backup/56# Option 2: scp (simpler, no resume)7scp -r user@old-server:/data/migration-backup /data/89# Option 3: For very large transfers, use rclone with cloud intermediate10# On old server:11rclone copy /data/migration-backup remote:migration/12# On new server:13rclone copy remote:migration/ /data/migration-backup/1415# Verify checksums after transfer16cd /data/migration-backup17sha256sum -c checksums.txt18# Should show "OK" for all filesFor large volumes (100GB+), consider physical disk shipping or using cloud storage as intermediate.
06Restore Volumes on New Server
Create volumes and restore data before starting containers.
1#!/bin/bash2# restore-volumes.sh - Run on new server34BACKUP_DIR="/data/migration-backup"56# Restore each volume7for backup in "$BACKUP_DIR"/*.tar.gz; do8 volume=$(basename "$backup" .tar.gz)910 # Skip non-volume backups11 [[ "$volume" == "bind-mounts" ]] && continue1213 echo "Restoring $volume..."1415 # Create volume16 docker volume create "$volume"1718 # Restore data19 docker run --rm \20 -v "$volume":/target \21 -v "$BACKUP_DIR":/backup:ro \22 alpine sh -c "cd /target && tar -xzf /backup/${volume}.tar.gz"23done2425# Restore bind mounts26tar -xzf "$BACKUP_DIR/bind-mounts.tar.gz" -C /2728echo "Restore complete"29docker volume ls07Update Configuration
Adjust configuration for the new server. IP addresses, hostnames, and paths may need updating.
1# Common things to update:23# 1. Environment files4# Update any hardcoded IPs or hostnames5vim .env67# 2. Volume paths (if different)8vim docker-compose.yml910# 3. Network configuration11# Recreate custom networks if needed12docker network create myapp-network1314# 4. Traefik/Caddy configuration15# Update domain configurations if IP changed1617# 5. SSL certificates18# Let's Encrypt will need revalidation19# Or copy acme.json if keeping same domain2021# 6. Cron jobs22crontab -e # Recreate backup/maintenance jobs08DNS Cutover Strategy
Plan the DNS switch to minimize downtime. Lower TTL before migration.
1# 1. One week before: Lower TTL to 5 minutes2# In your DNS provider, set TTL to 300 seconds34# 2. Verify both servers are working5# Old: https://app.example.com (current)6# New: https://NEW-IP (direct IP access)78# 3. Stop old server containers9ssh old-server "docker compose stop"1011# 4. Update DNS to new server IP12# This propagates within TTL (5 minutes)1314# 5. Verify DNS change15dig +short app.example.com16# Should show new server IP1718# 6. Monitor for issues19# Watch logs on new server20docker compose logs -f2122# 7. After 48 hours: Restore normal TTL (3600+)Keep the old server running (but containers stopped) for a week. Easy rollback if issues arise.
09Post-Migration Verification
Systematically verify everything works on the new server.
1# Verification checklist23# 1. All containers running4docker compose ps5# All should show "Up"67# 2. No error logs8docker compose logs | grep -i error910# 3. Data integrity11# Check a few records in databases12docker exec db psql -U postgres -c "SELECT count(*) FROM users;"1314# 4. External connectivity15curl -I https://app.example.com1617# 5. Internal networking18docker exec app ping db1920# 6. SSL certificate valid21echo | openssl s_client -connect app.example.com:443 2>/dev/null | openssl x509 -noout -dates2223# 7. Backups working24# Run backup script, verify it completes2526# 8. Monitoring reconnected27# Check your monitoring dashboard10Rollback Plan
If something goes wrong, have a tested rollback procedure ready.
1# Rollback procedure23# 1. Revert DNS to old server4# Update A record back to old IP56# 2. Start old server containers7ssh old-server "docker compose start"89# 3. Wait for DNS propagation (5-15 minutes with low TTL)1011# 4. Verify old server is handling traffic12ssh old-server "docker compose logs -f"1314# 5. Investigate issues on new server1516# 6. Preserve state for analysis17# Don't destroy new server volumes yet18# Compare data between old and new1920# Know your rollback window21# - How long can you operate on old server?22# - What data might be lost during cutover?23# - Who needs to be notified?11Post-Migration Cleanup
After successful migration, clean up the old server.
1# Wait at least one week before cleanup23# On old server:4# 1. Stop all containers5docker compose down67# 2. Remove Docker data (if not needed)8docker system prune -a --volumes910# 3. Archive final backups (just in case)11tar -czf final-backup.tar.gz /opt/docker1213# 4. Securely wipe if decommissioning14# shred -vfz -n 5 /dev/sda1516# On new server:17# 1. Remove migration backup files18rm -rf /data/migration-backup1920# 2. Set up regular backup schedule21crontab -e22# 0 3 * * * /opt/docker/scripts/backup.sh2324# 3. Update documentation25# - New server IP/hostname26# - Any configuration changes27# - Lessons learned