Skip to content
← writing
· 3 min read

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?

  1. Local → Cloud Replication
    • Every night, backup scripts for web servers and WordPress database are executed
    • Files are transferred via scp to the cloud server with SSH key authentication
  2. Cloud Restore
    • In the morning, cronjobs on the cloud server run restore scripts: containers are updated and the database is restored
  3. 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
  4. 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

Cloudflare automatically manages traffic based on the availability of the on-premises container or the cloud VM.