01Server Migrations Don't Have to Be Scary
I've migrated my Docker Compose stack between servers three times: once for a hardware upgrade, once when switching VPS providers, and once when recovering from a drive failure. The first time was painful — 6 hours of downtime and a missed database backup. The third time took 45 minutes with zero data loss.
The difference was having a repeatable migration playbook. This guide is that playbook, refined through real migrations. It works whether you're moving between physical servers, VPS providers, or even architectures (amd64 to arm64).
02Step 1: Inventory and Preparation
Before touching anything, document what you're running:
[terminal]
1# List all running containers and their compose projects2docker ps --format "table {{.Names}} {{.Image}} {{.Status}}"34# List all volumes and their sizes5docker system df -v | grep -A 1000 "VOLUME NAME"67# List all networks8docker network ls910# Export a list of all images (for pre-pulling on new server)11docker images --format "{{.Repository}}:{{.Tag}}" | sort -u > images.txt1213# Find all compose files14find /home -name "docker-compose.yml" -o -name "docker-compose.yaml" 2>/dev/nullCreate this inventory before you need it. I run this script monthly and save the output alongside my backups. When migration day comes, you know exactly what needs to move.
03Step 2: Backup Everything
The critical backup targets:
Compose files and .env files: These are your infrastructure definition. Copy every docker-compose.yml, .env file, and custom config file.
Database dumps: Always take logical backups (pg_dump, mysqldump) in addition to volume backups. They're more portable and can restore across different versions.
Docker volumes: Back up all named volumes. For large volumes (media libraries), consider rsync for incremental transfers.
[terminal]
1# Backup all compose files and configs2tar czf compose-configs.tar.gz ~/docker/34# Dump all PostgreSQL databases5for db in nextcloud gitea; do6 docker exec ${db}-db pg_dumpall -U postgres | \7 gzip > ${db}-postgres.sql.gz8done910# Backup volumes (stop services first for consistency)11docker compose -f ~/docker/nextcloud/docker-compose.yml stop12tar czf nextcloud-data.tar.gz \13 /var/lib/docker/volumes/nextcloud_data/14docker compose -f ~/docker/nextcloud/docker-compose.yml start04Step 3: Set Up New Server and Restore
On the new server, install Docker, restore configs, pre-pull images, restore volumes, and start services:
[terminal]
1# Install Docker on new server2curl -fsSL https://get.docker.com | sh34# Copy backups to new server5rsync -avz backups/ newserver:~/migration/67# Restore compose files8tar xzf compose-configs.tar.gz -C ~/910# Pre-pull all images (reduces startup time)11while read image; do docker pull "$image"; done < images.txt1213# Create shared networks14docker network create proxy1516# Restore volumes and start services17# (reverse proxy first, then databases, then apps)18cd ~/docker/traefik && docker compose up -d19cd ~/docker/nextcloud && docker compose up -d05Step 4: Verify and Switch DNS
Before switching DNS, verify everything works on the new server by accessing services directly via IP or by editing your local hosts file.
Check that data is intact: log into each service and verify recent data exists. Check that databases restored correctly. Verify that file uploads and media libraries are complete.
Once verified, update your DNS records to point to the new server's IP. If you're using Cloudflare Tunnels, update the tunnel to connect from the new server. Keep the old server running for at least a week in case you need to fall back.
The entire process — backup, transfer, restore, verify — can be done with under 30 minutes of downtime if you prepare everything in advance. The key is doing the data transfer while the old server is still running, then doing a final incremental sync before switching over.
Check out our operations docs and storage recipes for more backup and migration tooling.
Don't decommission the old server until you've verified everything on the new one works correctly for at least a week. Data loss during migration is usually discovered days later, not immediately.