Wenn Dienste in Docker laufen, ist das erneute Starten von Containern selten der nervige Teil. Die eigentlichen Kopfschmerzen beginnen, wenn eine Festplatte ausfällt, eine Maschine neu aufgebaut wird oder eine Panel-Installation verloren geht und man feststellt, dass die Daten in den Volumes nie so gesichert wurden, dass sie sich zuverlässig wiederherstellen lassen.

Darum bin ich bei meinem 1Panel-Server bei einem bewusst einfachen Backup-Modell gelandet: Docker-Volumes täglich archivieren, die letzten sieben Tage lokal aufbewahren und die komprimierten Dateien über Git LFS in ein privates GitHub-Repository synchronisieren. Das ist nicht das ausgefeilteste Docker-Backup-System, das man bauen könnte, aber eines der einfachsten Setups, das trotzdem einen echten Recovery-Pfad bietet.

Wenn du nach Docker-Volume-Backup zu GitHub, automatischen Docker-Backups oder einem praktischen 1Panel-Backup-Workflow suchst, geht es hier genau darum.

Warum ich das Backup-Modell absichtlich klein gehalten habe

Die Versuchung ist groß, sofort einen komplexeren Stack aufzubauen: Object Storage, Retention Policies, Off-Site-Replikation oder gleich ein spezialisiertes Backup-Produkt.

Später kann das sinnvoll sein. Auf einem persönlichen Server oder einer kleineren Team-Maschine liegt der erste echte Gewinn aber meist in etwas viel Einfacherem:

  • jeden Tag ein Backup erzeugen
  • aktuelle Kopien für schnelle Wiederherstellung lokal behalten
  • eine weitere Kopie außerhalb der Maschine speichern

Genau darauf basiert dieser Workflow:

Docker volumes / 1Panel config
          ↓
     docker-sync
          ↓
Create backup_<timestamp>
          ↓
Delete local backups older than 7 days
          ↓
Copy archives into Git working tree
          ↓
Track .tar.gz with Git LFS
          ↓
Push to a private GitHub repository

Er ist leicht genug, um tatsächlich dauerhaft zu laufen. Das ist wichtiger als ein theoretisch perfektes System, das nie fertiggestellt wird.

Zuerst GitHub-SSH-Vertrauen einrichten

Mit der Automation würde ich erst beginnen, wenn der Server sauber zu GitHub pushen kann.

Auf dem Server ausführen:

ssh-keygen -t ed25519 -C "[email protected]"
cat ~/.ssh/id_ed25519.pub
ssh -T [email protected]

Der erste Befehl erzeugt den Schlüssel, der zweite zeigt den öffentlichen Teil zum Eintragen in GitHub SSH Keys, und der dritte ist der wichtigste: ein manueller Verbindungstest.

Dieser Schritt spart später Zeit. Viele geplante Backup-Jobs scheitern nicht an der Backup-Logik, sondern daran, dass SSH nie vollständig eingerichtet wurde.

Ich bevorzuge außerdem ein eigenes privates Backup-Repository statt die Archive mit einem Anwendungs-Repo zu vermischen:

[email protected]:kuangyongjie/my-docker-backup.git

Privat ist hier der naheliegende Standard.

Alles in einen einzigen docker-sync-Befehl packen

Wichtige Ops-Logik sollte meiner Meinung nach nicht in irgendeinem zufälligen Skriptordner verschwinden.

Soll dieser Backup-Workflow Monate oder Jahre bestehen bleiben, braucht er einen stabilen Einstiegspunkt. Deshalb habe ich daraus einen Systembefehl gemacht:

/usr/local/bin/docker-sync

So funktioniert derselbe Befehl sowohl bei manuellen Läufen als auch in einem geplanten 1Panel-Task.

Das Kernskript:

cat << 'EOF' > /usr/local/bin/docker-sync
#!/bin/bash

GITHUB_REPO="[email protected]:kuangyongjie/my-docker-backup.git"
GIT_USER_NAME="kuangyongjie"
GIT_USER_EMAIL="[email protected]"
LOCAL_SYNC_PATH="/root/github_sync_space"

if ! command -v git-lfs &> /dev/null; then
    apt-get update && apt-get install -y git git-lfs && git lfs install
fi

if [ ! -d "$LOCAL_SYNC_PATH/.git" ]; then
    echo "--- 第一次运行:初始化同步目录 ---"
    rm -rf "$LOCAL_SYNC_PATH"
    GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no" git clone "$GITHUB_REPO" "$LOCAL_SYNC_PATH"
    cd "$LOCAL_SYNC_PATH"
    git lfs track "*.tar.gz" && git add .gitattributes
    git config user.name "$GIT_USER_NAME" && git config user.email "$GIT_USER_EMAIL"
    git commit -m "Init LFS"
else
    cd "$LOCAL_SYNC_PATH" && git pull origin main --rebase
fi

TAG=$(date +%Y%m%d_%H%M%S)
DIR="/root/docker_backups/backup_$TAG"
mkdir -p "$DIR"

echo "--- 正在抓取本地数据 ---"
for vol in $(docker volume ls -q); do
    tar -zcf "$DIR/vol_$vol.tar.gz" -C $(docker volume inspect $vol --format '{{.Mountpoint}}') .
done

tar -zcf "$DIR/1panel_apps_config.tar.gz" /opt/1panel/apps

echo "--- 正在清理本地 7 天前的旧备份 ---"
find /root/docker_backups/ -maxdepth 1 -name "backup_*" -type d -mtime +7 -exec rm -rf {} +

echo "--- 正在同步到 GitHub ---"
cp "$DIR"/*.tar.gz "$LOCAL_SYNC_PATH/"
cd "$LOCAL_SYNC_PATH"
git add .
git commit -m "Auto Backup $TAG"
git push origin main
echo "--- 完美!所有任务已完成 ---"
EOF

chmod +x /usr/local/bin/docker-sync

Was das Skript tatsächlich macht

Das Skript wirkt länger, als es inhaltlich ist.

Praktisch erledigt es sechs Dinge:

  1. git-lfs installieren, falls es fehlt
  2. beim ersten Lauf das Backup-Repository klonen und initialisieren
  3. alle Docker-Volumes durchlaufen und einzeln archivieren
  4. das 1Panel-App-Konfigurationsverzeichnis archivieren
  5. lokale Backups löschen, die älter als sieben Tage sind
  6. neue Archive ins Sync-Repo kopieren, committen und pushen

Genau diese Einfachheit gefällt mir an dem Aufbau. Die beweglichen Teile bleiben überschaubar.

Zwei Stellen würde ich besonders beobachten:

  • git commit schlägt fehl, wenn es nichts Neues zu committen gibt
  • docker volume ls -q sichert jedes Volume; man sollte also wissen, was sich auf der Maschine befindet

Beides ist leicht zu behandeln, wird aber relevant, sobald der Ablauf unbeaufsichtigt läuft.

Den geplanten Task in 1Panel minimal halten

Das vollständige Backup-Skript würde ich nicht direkt in den 1Panel-Task kopieren.

Sauberer ist es, zuerst den Shell-Befehl zuverlässig zu machen und 1Panel anschließend nur für Scheduling zu verwenden:

  1. docker-sync einmal manuell ausführen und Erfolg prüfen
  2. in 1Panel einen geplanten Task anlegen
  3. Shell Script auswählen
  4. einmal täglich laufen lassen, zum Beispiel um 03:00
  5. als gesamten Task-Body nur Folgendes verwenden:
docker-sync

Das macht Debugging deutlich einfacher. Scheitert der Job später, kann man sich per SSH anmelden und denselben Befehl direkt ausführen, statt ein langes Skript in der Panel-Oberfläche zu untersuchen.

Recovery ist wichtiger als ein elegantes Backup

Viele Backup-Guides hören bei „die Dateien sind jetzt irgendwo gespeichert“ auf. Das ist nicht der Teil, der zählt, wenn wirklich etwas kaputtgeht.

Der Recovery-Ablauf ist der eigentliche Test:

  1. Backup-Repository auf einem neuen Server klonen
  2. das Archiv des passenden Zeitpunkts finden
  3. das relevante vol_xxx.tar.gz entpacken
  4. das Ziel-Docker-Volume neu erstellen
  5. die Daten zurück in den Volume-Mountpoint kopieren
  6. den Container neu erstellen und das Volume wieder einbinden

Die genauen Befehle hängen vom wiederherzustellenden Dienst ab, der Ablauf bleibt aber ungefähr gleich.

Die eigentliche Reibung liegt meist nicht beim Entpacken. Unter Zeitdruck zu wissen, welches Volume zu welchem Dienst gehört, ist schwieriger. Wenn du diesen Workflow übernimmst, halte deshalb Volume-Namen und Service-Zuordnung sauber.

Die wenigen Befehle, die ich wirklich benutze

Meist interessieren mich nur diese Checks:

BedarfBefehl
Backup sofort ausführendocker-sync
Lokalen Speicherverbrauch prüfendu -sh /root/docker_backups
Lokale Backup-Ordner ansehenls -lh /root/docker_backups
Letzte Sync-Historie prüfencd /root/github_sync_space && git log -n 5
Lokale Retention ändernnano /usr/local/bin/docker-sync

Für ein anderes Aufbewahrungsfenster reicht es, die Zeile find ... -mtime +7 anzupassen. Der ganze Workflow muss dafür nicht neu entworfen werden.

Wo dieses Setup am ehesten scheitert

In der Praxis sind die fragilen Stellen selten die tar-Befehle selbst.

Typischer sind:

  • unvollständiges SSH-Vertrauen zwischen Server und GitHub
  • git-lfs fehlt oder wurde nicht initialisiert
  • falsche Berechtigungen im lokalen Sync-Verzeichnis
  • geplante Läufe ohne Änderungen, bei denen git commit mit non-zero endet

Auch die Skalierung darf man nicht ignorieren. Ein privates GitHub-Repository funktioniert gut für Konfigurationen, moderate Docker-Volume-Daten und typische App-Backups. Wachsen die Archive schnell, sind Object Storage oder ein dediziertes Backup-Ziel wahrscheinlich sinnvoller.

Darum sollte dieses Setup korrekt eingeordnet werden: Es ist nicht die endgültige Form von Disaster Recovery. Es ist ein praktischer Weg, einem Docker-/1Panel-Server eine echte automatisierte Backup-Gewohnheit zu geben, ohne sofort viel zusätzliche Infrastruktur einzuführen.