Replication and Failover of a Docker Environment: From Proxmox Container to Cloud
Managing a website on a local server (on-premises) offers many advantages in terms of control and customization, but it also brings an important risk: operational continuity. A home or office server cannot guarantee 100% uptime — just one blackout, a hardware issue, or, as in my case, being away during holidays, and the site could remain offline for hours or days.
Managing a website on a local server (on-premises) offers many advantages in terms of control and customization, but it also brings an important risk: operational continuity. A home or office server cannot guarantee 100% uptime — just one blackout, a hardware issue, or, as in my case, being away during holidays, and the site could remain offline for hours or days.
For this reason, I decided to implement a Replication + Cloud Failover solution, which allows:
- Periodic Replication of WordPress content and database from the local server to the cloud
- Automatic (or Semi-Automatic) Failover in case the local server is offline
- The ability to manage and update services remotely as well
How Does My Solution Work?
- Local → Cloud Replication
- Every night, backup scripts for web servers and WordPress database are executed
- Files are transferred via
scpto the cloud server with SSH key authentication
- Cloud Restore
- In the morning, cronjobs on the cloud server run restore scripts: containers are updated and the database is restored
- Failover
- If the local server is online, Cloudflare load balances traffic
- If the local server is offline, traffic automatically switches to the cloud server without manual intervention
- Cloud Management
- Portainer for Docker management
- Fail2Ban and basic hardening for security
When the Local Container Is UP:
✅ Handles normal requests
When the Local Container Is DOWN:
✅ Cloudflare redirects requests to the cloud VM
✅ The site remains online, services continue to function
Tutorial:
OnPremise:
a) Backup of Docker Volumes of Web Servers
For each dockerized service (like NGINX or others), I create a backup of the volumes using a temporary Alpine container. At the end, everything is transferred to the cloud via SCP.
Example — Web Server Example:
#!/bin/bash
DATE=$(date +"%Y%m%d-%H%M")
BACKUP_DIR="/root/backups"
CLOUD_USER="insert_username"
CLOUD_IP="insert_cloud_ip"
SSH_KEY="insert_ssh_key"
REMOTE_PATH="/home/insert_username/webserver_backup_${DATE}.tar.gz"
mkdir -p ${BACKUP_DIR}
cd ${BACKUP_DIR}
# Backup Docker volumes
docker run --rm -v insert_volume_id1:/volume -v $(pwd):/backup alpine sh -c "cd /volume && tar czf /backup/nginx_backup.tar.gz ."
docker run --rm -v insert_volume_id2:/volume -v $(pwd):/backup alpine sh -c "cd /volume && tar czf /backup/html_backup.tar.gz ."
# Create a single archive and transfer to the cloud
tar czf webserver_backup_${DATE}.tar.gz nginx_backup.tar.gz html_backup.tar.gz
scp -i ${SSH_KEY} webserver_backup_${DATE}.tar.gz ${CLOUD_USER}@${CLOUD_IP}:${REMOTE_PATH}
rm -f nginx_backup.tar.gz html_backup.tar.gz
b) WordPress Database Dump
I execute a SQL dump from the MySQL/MariaDB container and transfer it to the cloud:
#!/bin/bash
DATE=$(date +"%Y%m%d-%H%M")
LOCAL_DUMP="/root/wordpress_db_backup_${DATE}.sql"
CLOUD_USER="insert_username"
CLOUD_IP="insert_cloud_ip"
SSH_KEY="insert_ssh_key"
DB_USER="insert_db_user"
DB_PASS="insert_db_password"
DB_NAME="insert_db_name"
docker exec insert_container_name_db mysqldump --no-tablespaces -u ${DB_USER} -p"${DB_PASS}" ${DB_NAME} > ${LOCAL_DUMP}
scp -i ${SSH_KEY} ${LOCAL_DUMP} ${CLOUD_USER}@${CLOUD_IP}:/home/insert_username/
c) Automation with Cronjob
Example of crontab:
0 3 * * * /root/scripts/replica_webserver.sh 0 4 * * * /root/scripts/replica_wordpress.sh
Cloud:
a) Restore of Web Server Volumes
On the cloud, I unpack the received backups and restore them to Docker volumes:
#!/bin/bash
BACKUP_FILE=$(ls -t /home/insert_username/webserver_backup_*.tar.gz | head -n 1)
RESTORE_DIR="/root/restore_temp"
mkdir -p ${RESTORE_DIR}
tar xzf ${BACKUP_FILE} -C ${RESTORE_DIR}
docker run --rm -v insert_volume_id1:/data -v ${RESTORE_DIR}:/backup alpine sh -c "rm -rf /data/* && tar xzf /backup/nginx_backup.tar.gz -C /data"
docker run --rm -v insert_volume_id2:/data -v ${RESTORE_DIR}:/backup alpine sh -c "rm -rf /data/* && tar xzf /backup/html_backup.tar.gz -C /data"
rm -rf ${RESTORE_DIR}
b) Restore of WordPress Database
#!/bin/bash
DUMP_PATH=$(ls -t /home/insert_username/wordpress_db_backup_*.sql | head -n 1)
CONTAINER_DB="insert_container_name_db"
DB_USER="insert_db_user"
DB_PASS="insert_db_password"
DB_NAME="insert_db_name"
docker cp "$DUMP_PATH" "$CONTAINER_DB:/wordpress_db_backup.sql"
docker exec -i "$CONTAINER_DB" mysql -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "SET FOREIGN_KEY_CHECKS=0;DROP TABLE IF EXISTS wp_commentmeta, wp_comments, wp_links, wp_options, wp_postmeta, wp_posts, wp_term_relationships, wp_terms, wp_term_taxonomy, wp_usermeta, wp_users;SET FOREIGN_KEY_CHECKS=1;"
docker exec -i "$CONTAINER_DB" sh -c "mysql -u $DB_USER -p\"$DB_PASS\" $DB_NAME < /wordpress_db_backup.sql"
c) Automation with Cronjob on Cloud
Example of crontab:
0 5 * * * /root/scripts/restore_webserver.sh
0 6 * * * /root/scripts/restore_wordpress.sh
Cloudflare:
On both machines (Proxmox and Cloud), I activate the Cloudflare tunnel:
cloudflared service install insert_tunnel_token