docker.recipes
Operations11 min read

How to Migrate a Docker Compose Stack to a New Server

Step-by-step guide to moving your Docker Compose services to a new server with minimal downtime.

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 state
2# 1. List all containers
3docker ps -a > containers.txt
4
5# 2. List all volumes
6docker volume ls > volumes.txt
7
8# 3. List all networks
9docker network ls > networks.txt
10
11# 4. Export docker-compose configs
12find / -name "docker-compose.yml" 2>/dev/null > compose-files.txt
13
14# 5. Document environment variables
15cat .env > env-backup.txt
16
17# 6. Verify backup integrity
18# Restore backups to test environment first!
19
20# 7. Note external dependencies
21# - DNS records
22# - SSL certificates
23# - External databases
24# - API endpoints

Never 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 Docker
2curl -fsSL https://get.docker.com | sh
3sudo usermod -aG docker $USER
4
5# Install Docker Compose (v2)
6sudo apt install docker-compose-plugin
7
8# Verify installation
9docker --version
10docker compose version
11
12# Create same directory structure
13mkdir -p /opt/docker
14mkdir -p /data/backups
15
16# Copy configuration files
17scp -r user@old-server:/opt/docker/myapp /opt/docker/
18
19# Install same reverse proxy
20# (Traefik, Caddy, NPM - whatever you're using)

04Backup All Volumes

Create comprehensive backups of all Docker volumes. This is the critical step.
1#!/bin/bash
2# backup-volumes.sh - Run on old server
3
4BACKUP_DIR="/data/migration-backup"
5mkdir -p "$BACKUP_DIR"
6
7# Stop containers for consistent backup
8docker compose stop
9
10# Backup each volume
11for volume in $(docker volume ls -q); do
12 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 .
17done
18
19# Backup bind mounts (if any)
20tar -czf "$BACKUP_DIR/bind-mounts.tar.gz" /path/to/bind/mounts
21
22# Create checksum file
23cd "$BACKUP_DIR"
24sha256sum *.tar.gz > checksums.txt
25
26echo "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/
5
6# Option 2: scp (simpler, no resume)
7scp -r user@old-server:/data/migration-backup /data/
8
9# Option 3: For very large transfers, use rclone with cloud intermediate
10# On old server:
11rclone copy /data/migration-backup remote:migration/
12# On new server:
13rclone copy remote:migration/ /data/migration-backup/
14
15# Verify checksums after transfer
16cd /data/migration-backup
17sha256sum -c checksums.txt
18# Should show "OK" for all files

For 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/bash
2# restore-volumes.sh - Run on new server
3
4BACKUP_DIR="/data/migration-backup"
5
6# Restore each volume
7for backup in "$BACKUP_DIR"/*.tar.gz; do
8 volume=$(basename "$backup" .tar.gz)
9
10 # Skip non-volume backups
11 [[ "$volume" == "bind-mounts" ]] && continue
12
13 echo "Restoring $volume..."
14
15 # Create volume
16 docker volume create "$volume"
17
18 # Restore data
19 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"
23done
24
25# Restore bind mounts
26tar -xzf "$BACKUP_DIR/bind-mounts.tar.gz" -C /
27
28echo "Restore complete"
29docker volume ls

07Update Configuration

Adjust configuration for the new server. IP addresses, hostnames, and paths may need updating.
1# Common things to update:
2
3# 1. Environment files
4# Update any hardcoded IPs or hostnames
5vim .env
6
7# 2. Volume paths (if different)
8vim docker-compose.yml
9
10# 3. Network configuration
11# Recreate custom networks if needed
12docker network create myapp-network
13
14# 4. Traefik/Caddy configuration
15# Update domain configurations if IP changed
16
17# 5. SSL certificates
18# Let's Encrypt will need revalidation
19# Or copy acme.json if keeping same domain
20
21# 6. Cron jobs
22crontab -e # Recreate backup/maintenance jobs

08DNS Cutover Strategy

Plan the DNS switch to minimize downtime. Lower TTL before migration.
1# 1. One week before: Lower TTL to 5 minutes
2# In your DNS provider, set TTL to 300 seconds
3
4# 2. Verify both servers are working
5# Old: https://app.example.com (current)
6# New: https://NEW-IP (direct IP access)
7
8# 3. Stop old server containers
9ssh old-server "docker compose stop"
10
11# 4. Update DNS to new server IP
12# This propagates within TTL (5 minutes)
13
14# 5. Verify DNS change
15dig +short app.example.com
16# Should show new server IP
17
18# 6. Monitor for issues
19# Watch logs on new server
20docker compose logs -f
21
22# 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 checklist
2
3# 1. All containers running
4docker compose ps
5# All should show "Up"
6
7# 2. No error logs
8docker compose logs | grep -i error
9
10# 3. Data integrity
11# Check a few records in databases
12docker exec db psql -U postgres -c "SELECT count(*) FROM users;"
13
14# 4. External connectivity
15curl -I https://app.example.com
16
17# 5. Internal networking
18docker exec app ping db
19
20# 6. SSL certificate valid
21echo | openssl s_client -connect app.example.com:443 2>/dev/null | openssl x509 -noout -dates
22
23# 7. Backups working
24# Run backup script, verify it completes
25
26# 8. Monitoring reconnected
27# Check your monitoring dashboard

10Rollback Plan

If something goes wrong, have a tested rollback procedure ready.
1# Rollback procedure
2
3# 1. Revert DNS to old server
4# Update A record back to old IP
5
6# 2. Start old server containers
7ssh old-server "docker compose start"
8
9# 3. Wait for DNS propagation (5-15 minutes with low TTL)
10
11# 4. Verify old server is handling traffic
12ssh old-server "docker compose logs -f"
13
14# 5. Investigate issues on new server
15
16# 6. Preserve state for analysis
17# Don't destroy new server volumes yet
18# Compare data between old and new
19
20# Know your rollback window
21# - 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 cleanup
2
3# On old server:
4# 1. Stop all containers
5docker compose down
6
7# 2. Remove Docker data (if not needed)
8docker system prune -a --volumes
9
10# 3. Archive final backups (just in case)
11tar -czf final-backup.tar.gz /opt/docker
12
13# 4. Securely wipe if decommissioning
14# shred -vfz -n 5 /dev/sda
15
16# On new server:
17# 1. Remove migration backup files
18rm -rf /data/migration-backup
19
20# 2. Set up regular backup schedule
21crontab -e
22# 0 3 * * * /opt/docker/scripts/backup.sh
23
24# 3. Update documentation
25# - New server IP/hostname
26# - Any configuration changes
27# - Lessons learned