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:
+22
-10
@@ -7,8 +7,11 @@ cd docker
|
||||
JWT_SECRET=<zufälliger-langer-string> docker compose up -d
|
||||
```
|
||||
|
||||
`./data` (relativ zu `docker/`) wird als Volume gemountet und enthält alle Server-Daten
|
||||
(Ereignis-Logs, Snapshots, Nutzer) – bei Neustarts/Updates bleibt es erhalten.
|
||||
Alle Server-Daten (Ereignis-Logs, Snapshots, Anhänge, Nutzer) liegen im Named Volume `api-data` –
|
||||
bei Neustarts/Updates bleibt es erhalten. Bewusst **kein** Bind-Mount auf einen Host-Pfad: bei
|
||||
Git-basierten Deployments (z. B. Dokploy) wird das Checkout-Verzeichnis bei jedem Redeploy neu
|
||||
geklont, ein dort liegendes `./data` wäre dabei jedes Mal leer (siehe TODO.md 10.2.4 für den
|
||||
Vorfall, der das aufgedeckt hat).
|
||||
|
||||
## Nutzer anlegen
|
||||
|
||||
@@ -41,18 +44,27 @@ docker compose exec api dotnet LehrerApp.Api.dll set-password <benutzername> --p
|
||||
|
||||
## Backup
|
||||
|
||||
`backup.sh` sichert `./data` (Ereignis-Logs, Snapshots, Anhänge, Nutzer) als komprimiertes
|
||||
Tar-Archiv unter `docker/backups/` und räumt Archive älter als 30 Tage automatisch auf. Läuft
|
||||
direkt auf dem Host (kein Container-Zugriff nötig, `./data` liegt dort per Bind-Mount ohnehin):
|
||||
`backup.sh` sichert das Named Volume `api-data` (Ereignis-Logs, Snapshots, Anhänge, Nutzer) über
|
||||
einen kurzlebigen Alpine-Container als komprimiertes Tar-Archiv unter `docker/backups/` und räumt
|
||||
Archive älter als 30 Tage automatisch auf:
|
||||
|
||||
```bash
|
||||
./docker/backup.sh
|
||||
```
|
||||
|
||||
Der volle Docker-Volume-Name ist `<compose-projektname>_api-data`. Lokal (`cd docker && docker
|
||||
compose up`) ist das `docker_api-data` (Default im Skript), bei Dokploy der App-Slug, z. B.
|
||||
`lehrerapp-syncserver-xyz_api-data` – im Zweifel prüfen mit `docker volume ls | grep api-data`
|
||||
und per Umgebungsvariable übergeben:
|
||||
|
||||
```bash
|
||||
LEHRERAPP_DATA_VOLUME=lehrerapp-syncserver-xyz_api-data ./docker/backup.sh
|
||||
```
|
||||
|
||||
Für regelmäßige Sicherung z. B. per Cron, einmal täglich nachts:
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
||||
|
||||
## Rate Limiting
|
||||
@@ -82,7 +94,7 @@ selbst aus `docker/Dockerfile.api`.
|
||||
6. **Nutzer anlegen** nach dem ersten Deploy: über Dokploys Terminal/Exec-Tab am laufenden
|
||||
Container `dotnet LehrerApp.Api.dll create-user <name>` ausführen (siehe oben).
|
||||
|
||||
Der `./data`-Bind-Mount bleibt unverändert – Dokploy hält das Checkout-Verzeichnis der
|
||||
Compose-App über Redeploys hinweg stabil, `backup.sh` funktioniert damit unverändert weiter.
|
||||
Nach dem ersten echten Deploy einmal gegenprüfen: Redeploy anstoßen und verifizieren, dass
|
||||
Daten (z. B. ein testweise angelegter Nutzer) erhalten bleiben.
|
||||
**Wichtig:** die Daten liegen im Named Volume `api-data`, nicht in einem Bind-Mount – Dokploy
|
||||
klont das Repo bei jedem Redeploy neu, ein Bind-Mount relativ zum Checkout wäre dabei jedes Mal
|
||||
leer gewesen (das ist real passiert, siehe TODO.md 10.2.4). Named Volumes übersteht Dokploys
|
||||
`docker compose up --build --remove-orphans` dagegen unverändert.
|
||||
|
||||
+21
-8
@@ -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
|
||||
|
||||
@@ -6,8 +6,14 @@ services:
|
||||
ports:
|
||||
- "5000:5000"
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
# Named Volume statt Bind-Mount: Plattformen wie Dokploy klonen das Repo bei jedem Deploy
|
||||
# neu, ein Bind-Mount relativ zum Checkout (./data) würde dabei geleert. Das Named Volume
|
||||
# lebt im Docker-Daemon und übersteht "docker compose up --build --remove-orphans".
|
||||
- api-data:/app/data
|
||||
environment:
|
||||
- JWT_SECRET=${JWT_SECRET}
|
||||
- ASPNETCORE_ENVIRONMENT=Production
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
api-data:
|
||||
|
||||
Reference in New Issue
Block a user