fix: Server-Daten in Named Volume statt Bind-Mount

./data war ein Bind-Mount relativ zum Git-Checkout - bei Dokploy wird
das Repo pro Deploy frisch geklont, ein per CLI angelegter Nutzer war
nach einem reinen Routing-Redeploy spurlos verschwunden. Betraf nicht
nur Nutzer, sondern denselben Api:DataPath für Ereignis-Log/Snapshots/
Anhänge - also jeden Server-Datenbestand bei jedem Redeploy.

- docker-compose.yml: Named Volume "api-data" statt Bind-Mount, lebt
  unabhängig vom Checkout im Docker-Daemon.
- backup.sh: auf volume-basiertes Backup umgeschrieben (Alpine-
  Hilfscontainer statt direktem Host-Pfad).
- docker/README.md, TODO.md 10.2.4: Vorfall dokumentiert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 00:02:56 +02:00
co-authored by Claude Sonnet 5
parent a68bb933c5
commit cdac335ad1
4 changed files with 63 additions and 19 deletions
+21 -8
View File
@@ -1,25 +1,38 @@
#!/bin/sh
# Sichert das komplette ./data-Verzeichnis (Ereignis-Logs, Snapshots, Anhänge, Nutzer) als
# komprimiertes Tar-Archiv. Läuft außerhalb des Containers direkt auf dem Host, da ./data per
# Bind-Mount ohnehin dort liegt (siehe docker-compose.yml) — kein Zugriff auf den Container nötig.
# Sichert das Docker-Volume "api-data" (Ereignis-Logs, Snapshots, Anhänge, Nutzer) als
# komprimiertes Tar-Archiv. Läuft über einen kurzlebigen Alpine-Container mit Zugriff auf das
# Volume - kein direkter Host-Pfad, seit das Volume kein Bind-Mount mehr ist (siehe
# docker-compose.yml: Dokploy-Redeploys klonen das Repo neu, ein Bind-Mount relativ zum
# Checkout würde dabei geleert, siehe TODO.md 10.2.4).
#
# Der volle Docker-Volume-Name ist "<compose-projektname>_api-data" - bei einem lokalen
# "cd docker && docker compose up" ist das "docker_api-data" (Default unten), bei Dokploy der
# App-Slug, z.B. "lehrerapp-syncserver-xyz_api-data". Im Zweifel prüfen mit:
# docker volume ls | grep api-data
# und den echten Namen per LEHRERAPP_DATA_VOLUME übergeben:
# LEHRERAPP_DATA_VOLUME=lehrerapp-syncserver-xyz_api-data /pfad/zu/docker/backup.sh
#
# Aufruf z.B. per Cron:
# 0 3 * * * /pfad/zu/docker/backup.sh >> /pfad/zu/docker/backup.log 2>&1
# 0 3 * * * LEHRERAPP_DATA_VOLUME=... /pfad/zu/docker/backup.sh >> /pfad/zu/docker/backup.log 2>&1
set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
DATA_DIR="$SCRIPT_DIR/data"
BACKUP_DIR="$SCRIPT_DIR/backups"
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
KEEP_DAYS=30
VOLUME_NAME="${LEHRERAPP_DATA_VOLUME:-docker_api-data}"
if [ ! -d "$DATA_DIR" ]; then
echo "Kein Datenverzeichnis unter $DATA_DIR gefunden - nichts zu sichern." >&2
if ! docker volume inspect "$VOLUME_NAME" >/dev/null 2>&1; then
echo "Docker-Volume '$VOLUME_NAME' nicht gefunden (docker volume ls | grep api-data) - nichts zu sichern." >&2
exit 1
fi
mkdir -p "$BACKUP_DIR"
tar -czf "$BACKUP_DIR/lehrerapp-data-$TIMESTAMP.tar.gz" -C "$SCRIPT_DIR" data
docker run --rm \
-v "$VOLUME_NAME":/data:ro \
-v "$BACKUP_DIR":/backup \
alpine:3 \
tar -czf "/backup/lehrerapp-data-$TIMESTAMP.tar.gz" -C /data .
# Alte Backups jenseits von KEEP_DAYS aufräumen, damit das Verzeichnis nicht unbegrenzt wächst.
find "$BACKUP_DIR" -name 'lehrerapp-data-*.tar.gz' -mtime "+$KEEP_DAYS" -delete