# Service Migration Runbook ## Overview This runbook guides you through migrating a containerized service from one host to another in the homelab. The procedure minimizes downtime and ensures data integrity throughout the migration. ## Prerequisites - [ ] SSH access to both source and target hosts - [ ] Sufficient disk space on target host - [ ] Network connectivity between hosts (Tailscale recommended) - [ ] Service backup completed and verified - [ ] Maintenance window scheduled (if downtime required) - [ ] Portainer access for both hosts ## Metadata - **Estimated Time**: 1-3 hours (depending on data size) - **Risk Level**: Medium-High (data migration involved) - **Requires Downtime**: Yes (typically 15-60 minutes) - **Reversible**: Yes (can roll back to source host) - **Tested On**: 2026-02-14 ## When to Migrate Services Common reasons for service migration: | Scenario | Example | Recommended Target | |----------|---------|-------------------| | **Resource constraints** | NAS running out of CPU | Move to NUC or VM | | **Storage constraints** | Running out of disk space | Move to larger NAS | | **Performance issues** | High I/O affecting other services | Move to dedicated host | | **Host consolidation** | Reducing number of active hosts | Consolidate to primary hosts | | **Hardware maintenance** | Planned hardware upgrade | Temporary or permanent move | | **Improved organization** | Group related services | Move to appropriate host | ## Migration Types ### Type 1: Simple Migration (Stateless Service) - No persistent data - Can be redeployed from scratch - Example: Nginx, static web servers - **Downtime**: Minimal (5-15 minutes) ### Type 2: Standard Migration (Small Data) - Persistent data < 10GB - Configuration and databases - Example: Uptime Kuma, AdGuard Home - **Downtime**: 15-30 minutes ### Type 3: Large Data Migration - Persistent data > 10GB - Media libraries, large databases - Example: Plex, Immich, Jellyfin - **Downtime**: 1-4 hours (depending on size) ## Pre-Migration Planning ### Step 1: Assess the Service ```bash # SSH to source host ssh [source-host] # Identify container and volumes docker ps | grep [service-name] docker inspect [service-name] | grep -A 10 Mounts # Check data size docker exec [service-name] du -sh /config /data # List all volumes used by service docker volume ls | grep [service-name] # Check volume sizes docker system df -v | grep [service-name] ``` Document findings: - Container name: ___________ - Image and tag: ___________ - Data size: ___________ - Volume count: ___________ - Network dependencies: ___________ - Port mappings: ___________ ### Step 2: Check Target Host Capacity ```bash # SSH to target host ssh [target-host] # Check available resources df -h # Disk space free -h # RAM nproc # CPU cores docker ps | wc -l # Current container count # Check port conflicts netstat -tlnp | grep [required-port] ``` ### Step 3: Create Migration Plan **Downtime Window**: - Start: ___________ - End: ___________ - Duration: ___________ **Dependencies**: - Services that depend on this: ___________ - Services this depends on: ___________ **Notification**: - Who to notify: ___________ - When to notify: ___________ ## Migration Procedure ### Method A: GitOps Migration (Recommended) Best for: Most services with proper version control #### Step 1: Backup Current Service ```bash # SSH to source host ssh [source-host] # Create backup docker stop [service-name] docker export [service-name] > /tmp/[service-name]-backup.tar # Backup volumes for vol in $(docker volume ls -q | grep [service-name]); do docker run --rm -v $vol:/source -v /tmp:/backup alpine tar czf /backup/$vol.tar.gz -C /source . done # Copy backups to safe location scp /tmp/[service-name]*.tar* [backup-location]:~/backups/ ``` #### Step 2: Export Configuration ```bash # Get current docker-compose configuration cd ~/Documents/repos/homelab cat hosts/[source-host]/[service-name].yaml > /tmp/service-config.yaml # Note environment variables docker inspect [service-name] | grep -A 50 Env ``` #### Step 3: Copy Data to Target Host **For Small Data (< 10GB)**: Use SCP ```bash # From your workstation scp -r [source-host]:/volume1/docker/[service-name] /tmp/ scp -r /tmp/[service-name] [target-host]:/path/to/docker/ ``` **For Large Data (> 10GB)**: Use Rsync ```bash # From source host to target host via Tailscale ssh [source-host] rsync -avz --progress /volume1/docker/[service-name]/ \ [target-host-tailscale-ip]:/path/to/docker/[service-name]/ # Monitor progress watch -n 5 'du -sh /path/to/docker/[service-name]' ``` **For Very Large Data (> 100GB)**: Consider physical transfer ```bash # Copy to USB drive, physically move, then copy to target # Or use network-attached storage as intermediate ``` #### Step 4: Stop Service on Source Host ```bash # SSH to source host ssh [source-host] # Stop the container docker stop [service-name] # Verify it's stopped docker ps -a | grep [service-name] ``` #### Step 5: Update Git Configuration ```bash # On your workstation cd ~/Documents/repos/homelab # Move service definition to new host git mv hosts/[source-host]/[service-name].yaml \ hosts/[target-host]/[service-name].yaml # Update paths in the configuration file if needed nano hosts/[target-host]/[service-name].yaml # Update volume paths for target host # Atlantis/Calypso: /volume1/docker/[service-name] # NUC/VM: /home/user/docker/[service-name] # Raspberry Pi: /home/pi/docker/[service-name] # Commit changes git add hosts/[target-host]/[service-name].yaml git commit -m "Migrate [service-name] from [source-host] to [target-host] - Move service configuration - Update volume paths for target host - Migration date: $(date +%Y-%m-%d) Co-Authored-By: Claude Sonnet 4.5 " git push origin main ``` #### Step 6: Deploy on Target Host **Via Portainer UI**: 1. Open Portainer → Select target host endpoint 2. Go to **Stacks** → **Add stack** → **Git Repository** 3. Configure: - Repository URL: Your git repository - Compose path: `hosts/[target-host]/[service-name].yaml` - Enable GitOps (optional) 4. Click **Deploy the stack** **Via GitOps Auto-Sync**: - Wait 5-10 minutes for automatic deployment - Monitor Portainer for new stack appearance #### Step 7: Verify Migration ```bash # SSH to target host ssh [target-host] # Check container is running docker ps | grep [service-name] # Check logs for errors docker logs [service-name] --tail 100 # Test service accessibility curl http://localhost:[port] # Internal curl https://[service].vish.gg # External (if applicable) # Verify data integrity docker exec [service-name] ls -lah /config docker exec [service-name] ls -lah /data # Check resource usage docker stats [service-name] --no-stream ``` #### Step 8: Update DNS/Reverse Proxy (If Applicable) ```bash # Update Nginx Proxy Manager or reverse proxy configuration # Point [service].vish.gg to new host IP # Update Cloudflare DNS if using Cloudflare Tunnels # Update local DNS (AdGuard Home) if applicable ``` #### Step 9: Remove from Source Host **Only after verifying target is working correctly!** ```bash # SSH to source host ssh [source-host] # Remove container and volumes docker stop [service-name] docker rm [service-name] # Optional: Remove volumes (only if data copied successfully) # docker volume rm $(docker volume ls -q | grep [service-name]) # Remove data directory rm -rf /volume1/docker/[service-name] # BE CAREFUL! # Remove from Portainer if manually managed # Portainer UI → Stacks → Remove stack ``` ### Method B: Manual Export/Import Best for: Quick migrations without git changes, or when testing #### Step 1: Stop and Export ```bash # SSH to source host ssh [source-host] # Stop service docker stop [service-name] # Export container and volumes docker run --rm \ -v [service-name]_data:/source \ -v /tmp:/backup \ alpine tar czf /backup/[service-name]-data.tar.gz -C /source . # Export configuration docker inspect [service-name] > /tmp/[service-name]-config.json ``` #### Step 2: Transfer to Target ```bash # Copy data to target host scp /tmp/[service-name]-data.tar.gz [target-host]:/tmp/ scp /tmp/[service-name]-config.json [target-host]:/tmp/ ``` #### Step 3: Import on Target ```bash # SSH to target host ssh [target-host] # Create volume docker volume create [service-name]_data # Import data docker run --rm \ -v [service-name]_data:/target \ -v /tmp:/backup \ alpine tar xzf /backup/[service-name]-data.tar.gz -C /target # Create and start container using saved configuration # Adjust paths and ports as needed docker create --name [service-name] \ [options-from-config.json] \ [image:tag] docker start [service-name] ``` ## Post-Migration Tasks ### Update Documentation ```bash # Update service inventory nano docs/services/VERIFIED_SERVICE_INVENTORY.md # Update the host column for migrated service # | Service | Host | Port | URL | Status | # | Service | [NEW-HOST] | 8080 | https://service.vish.gg | ✅ Active | ``` ### Update Monitoring ```bash # Update Prometheus configuration if needed nano prometheus/prometheus.yml # Update target host IP for scraped metrics # Restart Prometheus if configuration changed ``` ### Test Backups ```bash # Verify backups work on new host ./backup.sh --test # Ensure service data is included in backup ls -lah /path/to/backups/[service-name] ``` ### Performance Baseline ```bash # Document baseline performance on new host docker stats [service-name] --no-stream # Monitor for 24 hours to ensure stability ``` ## Verification Checklist - [ ] Service running on target host: `docker ps` - [ ] All data migrated correctly - [ ] Configuration preserved - [ ] Logs show no errors: `docker logs [service]` - [ ] External access works (if applicable) - [ ] Internal service connectivity works - [ ] Reverse proxy updated (if applicable) - [ ] DNS records updated (if applicable) - [ ] Monitoring updated - [ ] Documentation updated - [ ] Backups include new location - [ ] Old host cleaned up - [ ] Users notified of any URL changes ## Rollback Procedure If migration fails or causes issues: ### Quick Rollback (Within 24 hours) ```bash # SSH to source host ssh [source-host] # Restore from backup docker import /tmp/[service-name]-backup.tar [service-name]:backup # Or redeploy from git (revert git changes) cd ~/Documents/repos/homelab git revert HEAD git push origin main # Restart service on source host # Via Portainer or: docker start [service-name] ``` ### Full Rollback (After cleanup) ```bash # Restore from backup ./restore.sh [backup-date] # Redeploy to original host # Follow original deployment procedure ``` ## Troubleshooting ### Issue: Data Transfer Very Slow **Symptoms**: Rsync taking hours for moderate data **Solutions**: ```bash # Use compression for better network performance rsync -avz --compress-level=6 --progress /source/ [target]:/dest/ # Or use parallel transfer tools # Install: sudo apt-get install parallel find /source -type f | parallel -j 4 scp {} [target]:/dest/{} # For extremely large transfers, consider: # 1. Physical USB drive transfer # 2. NFS mount between hosts # 3. Transfer during off-peak hours ``` ### Issue: Service Won't Start on Target Host **Symptoms**: Container starts then immediately exits **Solutions**: ```bash # Check logs docker logs [service-name] # Common issues: # 1. Path issues - Update volume paths in compose file # 2. Permission issues - Check PUID/PGID # 3. Port conflicts - Check if port already in use # 4. Missing dependencies - Ensure all required services running # Fix permissions docker exec [service-name] chown -R 1000:1000 /config /data ``` ### Issue: Lost Configuration Data **Symptoms**: Service starts but settings are default **Solutions**: ```bash # Check if volumes mounted correctly docker inspect [service-name] | grep -A 10 Mounts # Restore configuration from backup docker stop [service-name] docker run --rm -v [service-name]_config:/target -v /tmp:/backup alpine \ tar xzf /backup/config-backup.tar.gz -C /target docker start [service-name] ``` ### Issue: Network Connectivity Problems **Symptoms**: Service can't reach other services **Solutions**: ```bash # Check network configuration docker network ls docker network inspect [network-name] # Add service to required networks docker network connect [network-name] [service-name] # Verify DNS resolution docker exec [service-name] ping [other-service] ``` ## Migration Examples ### Example 1: Migrate Uptime Kuma from Calypso to Homelab VM ```bash # 1. Backup on Calypso ssh calypso docker stop uptime-kuma tar czf /tmp/uptime-kuma-data.tar.gz /volume1/docker/uptime-kuma # 2. Transfer scp /tmp/uptime-kuma-data.tar.gz homelab-vm:/tmp/ # 3. Update git cd ~/Documents/repos/homelab git mv hosts/synology/calypso/uptime-kuma.yaml \ hosts/vms/homelab-vm/uptime-kuma.yaml # Update paths in file sed -i 's|/volume1/docker/uptime-kuma|/home/user/docker/uptime-kuma|g' \ hosts/vms/homelab-vm/uptime-kuma.yaml # 4. Deploy on target git add . && git commit -m "Migrate Uptime Kuma to Homelab VM" && git push # 5. Verify and cleanup Calypso ``` ### Example 2: Migrate AdGuard Home between Hosts ```bash # AdGuard Home requires DNS configuration updates # 1. Note current DNS settings on clients # 2. Migrate service (as above) # 3. Update client DNS to point to new host IP # 4. Test DNS resolution from clients ``` ## Related Documentation - [Add New Service](add-new-service.md) - [Infrastructure Overview](../infrastructure/INFRASTRUCTURE_OVERVIEW.md) - [Backup Strategies](../admin/backup-strategies.md) - [Deployment Workflow](../admin/DEPLOYMENT_WORKFLOW.md) ## Change Log - 2026-02-14 - Initial creation with multiple migration methods - 2026-02-14 - Added large data migration strategies