Backup Script V2.0 für Docker Container

In der Welt der Containerisierung ist es essenziell, regelmäßige Backups zu erstellen – besonders, wenn man Docker Volumes nutzt, um persistent Daten abzulegen. Heute möchte ich mein neues Backupscript vorstellen, das genau diesen Zweck erfüllt und dabei noch ein paar weitere Features mitbringt.

Ich habe im November bereits eine Variante vorgestellt, welche per NFS die entsprechenden Dateien sichert. Hier habe ich jedoch gemerkt, dass ich nicht überall NFS-Ziele zur Verfügung habe und wollte somit eine andere Variante.

Was macht das Script?

Das Script habe ich entwickelt, um meine Docker Volumes automatisiert zu sichern und die Backups bequem über WebDAV auf meiner Nextcloud-Instanz zu speichern. Außerdem wollte ich benachrichtigt werden, sobald das Backup abgeschlossen ist – deshalb gibt’s auch eine Integration für Discord per Webhook.

Funktionen im Detail

  • Automatische Sicherung aller Docker Volumes
    • Das Script iteriert über alle bestehenden Docker Volumes auf dem System, exportiert den Inhalt und erstellt daraus komprimierte Archive. So sind alle wichtigen Daten in einem Wiederherstellungsfall schnell wieder verfügbar.
  • Upload zur Nextcloud via WebDAV
    • Die erstellten Backup-Archive werden automatisch per WebDAV auf meine Nextcloud hochgeladen. So liegen die Daten sicher in der Cloud und sind ortsunabhängig verfügbar. Die Zugangsdaten und Zielpfade lassen sich flexibel konfigurieren.
  • Benachrichtigung über Discord Webhook
    • Sobald der Upload abgeschlossen ist (oder auch, falls ein Fehler auftritt), sendet das Script eine kurze Nachricht über einen Discord Webhook in einen dafür eingerichteten Channel. So bleibe ich immer auf dem Laufenden.

Das Script

Hier das aktuelle Script, welches ich verwende.

#!/bin/bash
#########################################################################
# Name: backup_and_upload_docker_volumes.sh
# Purpose: Backup Docker volumes, upload to WebDAV, notify via Discord
#########################################################################

# ==== Configuration ====
BACKUPDIR="/backup/volumes"
DAYS=2
TIMESTAMP=$(date +"%Y%m%d%H%M")

WEBDAV_URL="https://WEBDAVLINK/$(hostname -I | awk '{print $1}')"
USERNAME="USERNAME"
PASSWORD="PASSWORD"
DISCORD_WEBHOOK_URL="WEBHOOK URL"

# ==== System Info ====
HOSTNAME=$(hostname)
IP_ADDRESS=$(hostname -I | awk '{print $1}')
OS_INFO=$(uname -a)
TIMESTAMP_HUMAN=$(date '+%Y-%m-%d %H:%M:%S')

# ==== Step 0: Verify WebDAV credentials before continuing ====
echo "🔐 Verifying WebDAV access..."

HTTP_STATUS=$(curl -u "$USERNAME:$PASSWORD" -s -o /dev/null -w "%{http_code}" "$WEBDAV_URL")

if [[ "$HTTP_STATUS" != "207" && "$HTTP_STATUS" != "200" ]]; then
  echo "❌ Authentication failed or WebDAV unavailable (HTTP $HTTP_STATUS)"
  ERROR_MESSAGE="❌ WebDAV upload failed\n**Host:** $HOSTNAME\n**IP:** $IP_ADDRESS\n**Time:** $TIMESTAMP_HUMAN\n**Error:** HTTP $HTTP_STATUS"

  curl -s -H "Content-Type: application/json" \
    -X POST \
    -d "{\"content\": \"$ERROR_MESSAGE\"}" \
    "$DISCORD_WEBHOOK_URL"
  exit 1
fi

# ==== Step 1: Create backups of Docker volumes ====
echo "📦 Starting backup of Docker volumes at $TIMESTAMP_HUMAN..."

VOLUMES=$(docker volume ls -q)

if [ ! -d "$BACKUPDIR" ]; then
  mkdir -p "$BACKUPDIR"
fi

for VOL in $VOLUMES; do
  echo "📦 Backing up volume: $VOL"
  docker run --rm \
    -v "$BACKUPDIR:/backup" \
    -v "$VOL:/data:ro" \
    -e TIMESTAMP="$TIMESTAMP" \
    -e i="$VOL" \
    alpine sh -c "cd /data && /bin/tar -czf /backup/${VOL}-${TIMESTAMP}.tar.gz ."

  OLD_BACKUPS=$(ls -1 "$BACKUPDIR"/${VOL}*.tar.gz 2>/dev/null | wc -l)
  if [ "$OLD_BACKUPS" -gt "$DAYS" ]; then
    find "$BACKUPDIR" -name "${VOL}*.tar.gz" -daystart -mtime +"$DAYS" -delete
  fi
done

echo "✅ Backup completed."

# ==== Step 2: Clean up WebDAV folder ====
echo "🧹 Cleaning up WebDAV directory..."

FILES_TO_DELETE=$(curl -s -u "$USERNAME:$PASSWORD" -X PROPFIND "$WEBDAV_URL" -H "Depth: 1" \
  | grep -oP '(?<=<d:href>).*?(?=</d:href>)' \
  | grep -v '/$')

for FILE_URL in $FILES_TO_DELETE; do
  FULL_URL="https://cloud.familie-flint.de${FILE_URL}"
  echo "Deleting $FULL_URL"
  curl -s -u "$USERNAME:$PASSWORD" -X DELETE "$FULL_URL" > /dev/null
done

echo "✅ WebDAV cleanup complete."

# ==== Step 3: Upload backups to WebDAV ====
echo "📤 Uploading backups to WebDAV..."

SUCCESS_COUNT=0
FAIL_COUNT=0
MESSAGE=""

for FILE in "$BACKUPDIR"/*; do
  if [[ -f "$FILE" ]]; then
    FILENAME=$(basename "$FILE")
    DEST_URL="${WEBDAV_URL}/${FILENAME}"
    echo "Uploading $FILENAME..."

    curl -s -u "$USERNAME:$PASSWORD" -T "$FILE" "$DEST_URL"

    if [[ $? -eq 0 ]]; then
      echo "✅ $FILENAME uploaded successfully."
      MESSAGE+="✅ ${FILENAME} uploaded successfully\n"
      ((SUCCESS_COUNT++))
    else
      echo "❌ Failed to upload $FILENAME."
      MESSAGE+="❌ Failed to upload ${FILENAME}\n"
      ((FAIL_COUNT++))
    fi
  fi
done

# ==== Step 4: Send Discord notification ====
TOTAL=$((SUCCESS_COUNT + FAIL_COUNT))
SUMMARY="**Docker Volume Backup Summary**\n**Host:** $HOSTNAME\n**IP:** $IP_ADDRESS\n**Time:** $TIMESTAMP_HUMAN\n**OS:** $OS_INFO\n\nTotal files: $TOTAL\n✅ Success: $SUCCESS_COUNT\n❌ Failed: $FAIL_COUNT\n\n${MESSAGE}"

SUMMARY_ESCAPED=$(echo -e "$SUMMARY" | sed ':a;N;$!ba;s/\n/\\n/g')

curl -s -H "Content-Type: application/json" \
  -X POST \
  -d "{\"content\": \"$SUMMARY_ESCAPED\"}" \
  "$DISCORD_WEBHOOK_URL"

echo "📣 Discord notification sent."

In der Sektion „Configuration“ lässt sich das Script noch entsprechend anpassen.

# ==== Configuration ====
BACKUPDIR="/backup/volumes"
DAYS=2
TIMESTAMP=$(date +"%Y%m%d%H%M")

WEBDAV_URL="https://WEBDAVLINK/$(hostname -I | awk '{print $1}')"
USERNAME="USERNAME"
PASSWORD="PASSWORD"
DISCORD_WEBHOOK_URL="WEBHOOK URL"

Und so sieht die Ausgabe auf dem Linux Host aus:

Es findet vorab ein Cleanup innerhalb des Webdav Verzeichnisses statt, welches alle alten Backups löscht. So ist immer nur das neueste enthalten. Dies gilt es zu beachten – kann aber im Bedarfsfall auch angepasst werden.

Anschließend kommt via Discord die entsprechende Nachricht:

Automatisierung über Crontab

Damit das Script automatisch läuft nutzen wir die Crontab und passen das Intervall für unsere Bedürfnisse an.

Beispiel für ein Backup, welches täglich um 20:00 Uhr durchgeführt wird:
0 20 * * * /root/backupcomplete.sh >/dev/null 2>&1

Nutzt dafür gerne den Crontab Generator unter folgendem Link:
https://crontab-generator.org/

Schreibe einen Kommentar