RFC-005: Backup-Konzept fuer OVH-Server (Borg/borgmatic)¶
| Feld | Wert |
|---|---|
| Status | draft |
| Datum | 2026-04-02 |
| Betrifft | novabrands-mgmt, homelab-external, homelab-documentation |
| Tracking-Issues | noch keine |
| Verwandt | — |
Problem¶
Der OVH RISE-S Server (Frankfurt, Ryzen 9700X, 64 GB RAM) betreibt produktive Dienste fuer Novabrands (OpenProject, Nextcloud, Coder, Speakr) ohne jegliche Backup-Strategie. Ein Datenverlust durch Hardware-Defekt, Software-Fehler oder menschliches Versagen wuerde zu vollstaendigem Verlust der Projektdaten, Dateien und Konfigurationen fuehren.
Konkrete Risiken:
- Kein Backup: Aktuell existiert kein automatisiertes Backup
- 3 PostgreSQL-Datenbanken: OpenProject, Nextcloud und Coder speichern geschaeftskritische Daten in Datenbanken ohne Sicherung
- Nextcloud-Dateien: Benutzerdateien liegen ausschliesslich auf dem Server
- Keine Georedundanz: Alle Daten befinden sich an einem Standort (OVH Frankfurt)
- Kein definiertes RPO/RTO: Keine Vorgabe wie viel Datenverlust akzeptabel ist
Ist-Zustand¶
Server¶
| Parameter | Wert |
|---|---|
| Anbieter | OVH RISE-S (Dedicated) |
| Standort | Frankfurt |
| Hardware | AMD Ryzen 7 9700X, 64 GB DDR5, 2x512 GB NVMe RAID-1 |
| IP | 51.77.84.41 |
| OS | Ubuntu |
| Repo-Pfad | /opt/containers/novabrands-mgmt/ |
Dienste und Datenbanken¶
| Dienst | Datenbank | Volumes |
|---|---|---|
| Traefik | — | Certs (/opt/containers/traefik/certs), Config |
| OpenProject | PostgreSQL 18 (openproject-db) |
op-assets |
| Nextcloud | PostgreSQL 18 (nextcloud-db) |
nc-html, nc-data |
| Collabora | — | — (stateless) |
| Coder | PostgreSQL 18 (coder-db) |
— |
| Speakr | SQLite (in Volume) | speakr-uploads, speakr-instance |
| WhisperX ASR | — | speakr-asr-cache (regenerierbar) |
Bestehende Vorarbeit¶
Ein dump-databases.sh Skript existiert bereits und erstellt pg_dump-Exports
aller drei PostgreSQL-Datenbanken nach /opt/containers/novabrands-mgmt/db-dumps/.
Fuer Nextcloud wird korrekt der Maintenance-Mode aktiviert/deaktiviert.
Vorschlag¶
Implementierung einer 3-2-1-Backup-Strategie mit BorgBackup und borgmatic:
- 3 Kopien: Produktionsdaten + OVH Backup Storage + Hetzner Storage Box
- 2 Medien: Lokaler NFS-Mount (OVH) + Remote SSH (Hetzner)
- 1 Offsite: Hetzner Storage Box (Falkenstein)
Das Konzept orientiert sich am bewaehrten Backup-Setup des Homeservers (borgmatic 1.8.14, Hetzner Storage Box, 3-2-1-Strategie).
Architektur¶
OVH RISE-S Server (Frankfurt)
├── borgmatic (Host-nativ, systemd-Timer)
│ ├── Pre-Hook: dump-databases.sh (pg_dump x3)
│ ├── Borg create → OVH Backup Storage (NFS)
│ ├── Borg create → Hetzner Storage Box (SSH)
│ ├── Borg prune + compact (beide Repos)
│ └── Post-Hook: Healthchecks-Ping
│
├── /mnt/backup-ovh/borg/ ← NFS-Mount, lokales Borg-Repo
│ └── OVH Backup Storage (500 GB, Frankfurt)
│
└── ssh://uXXXXXX@uXXXXXX.your-storagebox.de:23/./novabrands/
└── Hetzner Storage Box BX11 (1 TB, Falkenstein)
Zwei unabhaengige Borg-Repos statt eines gespiegelten Repos:
| Repo | Ziel | Protokoll | Zweck |
|---|---|---|---|
ovh-local |
OVH Backup Storage (500 GB) | NFS-Mount → lokales Repo | Schnelles Backup + Restore |
hetzner-offsite |
Hetzner Storage Box BX11 (1 TB) | Borg nativ via SSH (Port 23) | Offsite, Georedundanz |
Vorteile gegenueber einem gespiegelten Repo:
- Repo-Korruption in einem Repo betrifft nicht das andere
- Unabhaengige Prune-/Retention-Policies moeglich
- Kein Konsistenzproblem durch rsync auf laufendes Repo
Backup-Scope¶
Datenbank-Dumps (Pre-Hook):
| Datenbank | Dump-Methode | Besonderheit |
|---|---|---|
openproject-db |
docker exec → pg_dump --format=custom |
— |
nextcloud-db |
docker exec → pg_dump --format=custom |
Maintenance-Mode ein/aus |
coder-db |
docker exec → pg_dump --format=custom |
— |
Dumps werden nach /opt/containers/novabrands-mgmt/db-dumps/ geschrieben
(bestehendes Skript dump-databases.sh als Basis).
Docker-Volumes:
| Volume | Dienst | Inhalt |
|---|---|---|
op-assets |
OpenProject | Anhaenge, Avatare |
nc-html |
Nextcloud | Anwendungsdateien |
nc-data |
Nextcloud | Benutzerdateien |
speakr-uploads |
Speakr | Hochgeladene Audiodateien |
speakr-instance |
Speakr | SQLite-DB, Konfiguration |
Host-Pfade:
| Pfad | Inhalt |
|---|---|
/opt/containers/novabrands-mgmt/ |
docker-compose.yml, .env, Traefik-Config |
/opt/containers/novabrands-mgmt/db-dumps/ |
Datenbank-Dumps |
/opt/containers/traefik/certs |
Let's Encrypt Zertifikate |
/etc/ |
System-Konfiguration |
/root/.ssh/ |
SSH-Keys (root) |
/home/ubuntu/.ssh/ |
SSH-Keys (ubuntu) |
/var/spool/cron/ |
Cronjobs |
Explizit ausgeschlossen:
| Ausschluss | Begruendung |
|---|---|
op-pgdata, nc-pgdata, coder-pgdata |
Konsistente pg_dumps statt roher DB-Dateien |
speakr-asr-cache |
Regenerierbares ML-Modell-Cache (~4-8 GB) |
/var/log/ |
Logs sind nicht backup-relevant |
/tmp/, /var/tmp/ |
Temporaere Dateien |
Zeitplan und RPO¶
Recovery Point Objective (RPO): 12 Stunden
| Uhrzeit | Backup | Begruendung |
|---|---|---|
| 02:00 | Nacht-Backup | Geringste Last, konsistenteste Daten |
| 14:00 | Nachmittag-Backup | Sichert Aenderungen des Arbeitstages |
Reihenfolge pro Lauf:
- Pre-Hook:
dump-databases.sh(DB-Dumps erstellen) borg create→ OVH-Repo (NFS, schnell)borg create→ Hetzner-Repo (SSH, langsamer)borg prune+borg compact(beide Repos)- Post-Hook: Healthchecks-Ping (Success/Failure)
Retention-Policy¶
Geschaetzter Speicherbedarf bei unter 50 GB Quelldaten und Borg-Deduplizierung (~60-80% Ersparnis): 30-80 GB pro Repo. Beide Targets (500 GB OVH, 1 TB Hetzner) bieten ausreichend Reserve.
Verschluesselung¶
Algorithmus: repokey-blake2 (AES-256-CTR + BLAKE2b-256)
- Schluessel im Repo gespeichert, passwortgeschuetzt
- Passphrase in
/opt/containers/novabrands-mgmt/.env(gitignored) - Schluessel-Export (
borg key export) muss bei Einrichtung erstellt und sicher verwahrt werden (z.B. Proton Pass)
Schluessel-Backup
Ohne den Borg-Key und die Passphrase sind die Backups unwiederbringlich verloren. Der Key-Export ist ein kritischer Schritt bei der Einrichtung.
Kompression¶
zstd bietet das beste Verhaeltnis aus Kompressionsrate und CPU-Verbrauch. Level 3 ist der Standard-Sweet-Spot (45-50% Kompression bei geringer CPU-Last).
Monitoring und Alerting¶
Integration in die bestehende Monitoring-Infrastruktur auf dem Hetzner-Server:
Zwei separate Healthchecks-Checks:
| Check | Erwartung | Grace Period |
|---|---|---|
novabrands-backup-nacht |
Ping alle 24h um ~02:00 | 2 Stunden |
novabrands-backup-nachmittag |
Ping alle 24h um ~14:00 | 2 Stunden |
Bei ausbleibendem Ping (Dead-Man-Switch) sendet Healthchecks eine Benachrichtigung ueber ntfy. borgmatic hat native Healthchecks-Integration und pingt automatisch bei Start, Erfolg und Fehler.
Deployment: Host-nativ¶
borgmatic wird direkt auf dem Host installiert, nicht als Docker-Container.
Begruendung:
| Kriterium | Host-nativ | Docker-Container |
|---|---|---|
| DB-Dump-Hooks | docker exec direkt verfuegbar |
Docker-Socket-Mount oder DinD noetig |
| NFS-Mount | Trivial via /etc/fstab |
Mount auf Host, Durchreichung als Volume |
| Volume-Zugriff | Direkter Zugriff auf /var/lib/docker/volumes/ |
Alle Volumes einzeln mounten |
| Debugging | Einfach (borg list, borg info) |
Erfordert docker exec |
| Abhaengigkeiten | borgbackup, borgmatic Pakete |
Image-Management |
| Updates | apt upgrade oder pip install --upgrade |
Image-Tag aktualisieren |
Das bestehende Homeserver-Setup nutzt ebenfalls borgmatic auf dem Host und hat sich bewaehrt. Die Vorteile der Container-Isolierung wiegen die Komplexitaet der Docker-Socket- und Volume-Durchreichung nicht auf.
Installation:
# borgbackup via apt (stabiler, OS-integriert)
apt install borgbackup
# borgmatic via pipx (aktuelle Version, isoliert)
pipx install borgmatic
Systemd-Timer statt Cron:
# /etc/systemd/system/borgmatic.timer
[Unit]
Description=borgmatic backup timer
[Timer]
OnCalendar=*-*-* 02:00:00
OnCalendar=*-*-* 14:00:00
RandomizedDelaySec=15min
Persistent=true
[Install]
WantedBy=timers.target
Persistent=true stellt sicher, dass ein verpasstes Backup (z.B. bei Reboot)
nachgeholt wird.
borgmatic-Konfiguration (Entwurf)¶
# /etc/borgmatic.d/novabrands.yaml
source_directories:
- /opt/containers/novabrands-mgmt
- /opt/containers/traefik/certs
- /var/lib/docker/volumes/novabrands-mgmt_op-assets
- /var/lib/docker/volumes/novabrands-mgmt_nc-html
- /var/lib/docker/volumes/novabrands-mgmt_nc-data
- /var/lib/docker/volumes/novabrands-mgmt_speakr-uploads
- /var/lib/docker/volumes/novabrands-mgmt_speakr-instance
- /etc
- /root/.ssh
- /home/ubuntu/.ssh
- /var/spool/cron
repositories:
- path: /mnt/backup-ovh/borg
label: ovh-local
- path: ssh://uXXXXXX@uXXXXXX.your-storagebox.de:23/./novabrands
label: hetzner-offsite
exclude_patterns:
- '*.pyc'
- '__pycache__'
- '*.tmp'
- '*.log'
- '/etc/shadow'
- '/etc/gshadow'
encryption_passcommand: cat /opt/containers/novabrands-mgmt/.borg-passphrase
compression: zstd,3
retention:
keep_daily: 14
keep_weekly: 8
keep_monthly: 12
hooks:
before_backup:
- /opt/containers/novabrands-mgmt/dump-databases.sh
healthchecks:
ping_url: https://healthchecks.robinwerner.net/ping/{uuid}
Volume-Pfade
Die Docker-Volume-Pfade (/var/lib/docker/volumes/...) muessen nach
Deployment verifiziert werden. Das Volume-Praefix haengt vom
Compose-Projekt-Namen ab (Standard: Verzeichnisname novabrands-mgmt).
NFS-Mount (OVH Backup Storage)¶
# /etc/fstab
ftpback-rbxX-YYY.ovh.net:/export/ftpbackup/nsXXXX.ip-X-X-X.eu \
/mnt/backup-ovh nfs rw,soft,intr,nfsvers=3 0 0
- Aktivierung im OVH Control Panel (Dedicated → Server → Backup Storage)
- IP-basierte Autorisierung fuer NFS konfigurieren
- Mount-Verzeichnis:
/mnt/backup-ovh/ - Borg-Repo in Unterverzeichnis:
/mnt/backup-ovh/borg/
Hetzner Storage Box¶
| Parameter | Wert |
|---|---|
| Produkt | BX11 |
| Kapazitaet | 1 TB |
| Kosten | ~3,81 EUR/Monat |
| Protokoll | Borg nativ via SSH (Port 23) |
| Standort | Falkenstein |
Einrichtung:
- Storage Box im Hetzner Robot bestellen
- SSH-Key auf der Storage Box hinterlegen
- Sub-Account fuer Novabrands-Backups erstellen (Zugriffstrennung)
borg initauf dem Remote-Repo ausfuehren
# SSH-Key generieren
ssh-keygen -t ed25519 -f /root/.ssh/hetzner-borg-novabrands -N ""
# Key auf Storage Box hinterlegen
cat /root/.ssh/hetzner-borg-novabrands.pub | \
ssh -p 23 uXXXXXX@uXXXXXX.your-storagebox.de install-ssh-key
# Repo initialisieren
borg init --encryption=repokey-blake2 \
ssh://uXXXXXX@uXXXXXX.your-storagebox.de:23/./novabrands
Restore-Verfahren¶
Einzelne Dateien:
# Letztes Archiv listen
borg list /mnt/backup-ovh/borg
# Datei extrahieren
borg extract /mnt/backup-ovh/borg::ARCHIV-NAME \
opt/containers/novabrands-mgmt/docker-compose.yml
Datenbank-Restore:
# Dump aus Backup extrahieren
borg extract /mnt/backup-ovh/borg::ARCHIV-NAME \
opt/containers/novabrands-mgmt/db-dumps/nextcloud.dump
# In Container kopieren und wiederherstellen
docker cp nextcloud.dump nextcloud-db:/tmp/
docker exec nextcloud-db pg_restore \
-U nextcloud -d nextcloud --clean /tmp/nextcloud.dump
Vollstaendiges Disaster Recovery:
- Server neu aufsetzen (Ubuntu, Docker)
- borgbackup + borgmatic installieren
- Borg-Key und Passphrase aus sicherem Speicher holen
- NFS-Mount einrichten ODER von Hetzner Storage Box wiederherstellen
- Konfiguration extrahieren (
/opt/containers/novabrands-mgmt/) - DB-Dumps extrahieren und wiederherstellen
- Docker-Volumes extrahieren
docker compose up -d
Geschaetzte Recovery Time (RTO):
| Szenario | Quelle | Geschaetzte Dauer |
|---|---|---|
| Einzelne Datei | OVH (NFS) | < 5 Minuten |
| Einzelne Datei | Hetzner (SSH) | < 15 Minuten |
| Datenbank-Restore | OVH (NFS) | 10-30 Minuten |
| Vollstaendiges DR | OVH (NFS) | 1-2 Stunden |
| Vollstaendiges DR | Hetzner (SSH) | 2-4 Stunden (netzwerkabhaengig) |
Betroffene Repos¶
| Repo | Aenderung |
|---|---|
| novabrands-mgmt | borgmatic-Config, Passphrase in .env, dump-databases.sh anpassen, NFS-fstab, systemd-Timer, SSH-Key fuer Hetzner |
| homelab-external | Healthchecks: 2 neue Checks fuer Novabrands-Backups anlegen, ntfy-Alerting konfigurieren |
| homelab-documentation | RFC-005, Backup-Dokumentation, LikeC4-Modell um Backup-Flows ergaenzen |
Umsetzungsschritte¶
Phase 1: Infrastruktur vorbereiten¶
- OVH Backup Storage im Control Panel aktivieren (falls nicht geschehen)
- NFS-Zugang fuer Server-IP autorisieren
- Hetzner Storage Box BX11 bestellen
- SSH-Key fuer Hetzner Storage Box generieren und hinterlegen
- Sub-Account auf der Storage Box erstellen
Phase 2: borgmatic einrichten¶
borgbackupundborgmaticauf dem Server installieren- NFS-Mount in
/etc/fstabeinrichten und testen - Borg-Repo auf OVH Backup Storage initialisieren (
borg init) - Borg-Repo auf Hetzner Storage Box initialisieren (
borg init) - Borg-Keys exportieren und sicher verwahren (Proton Pass)
- borgmatic-Konfiguration erstellen (
/etc/borgmatic.d/novabrands.yaml) dump-databases.shals borgmatic-Pre-Hook integrieren- Passphrase in
.borg-passphraseablegen (restriktive Rechte:chmod 600)
Phase 3: Testen¶
- Manuelles Backup ausfuehren (
borgmatic --verbosity 1) - Backup-Integritaet pruefen (
borgmatic check) - Restore-Test: Einzelne Datei extrahieren
- Restore-Test: Datenbank-Dump wiederherstellen (auf Test-Container)
- Backup-Groesse und -Dauer dokumentieren
Phase 4: Automatisierung und Monitoring¶
- Systemd-Timer einrichten (02:00 + 14:00)
- Healthchecks-Checks auf dem Hetzner-Server anlegen
- ntfy-Alerting fuer fehlgeschlagene Backups konfigurieren
- Healthchecks-UUID in borgmatic-Config eintragen
- Ersten automatischen Lauf abwarten und verifizieren
Phase 5: Dokumentation¶
- Backup-Dokumentation in homelab-documentation erstellen
- LikeC4-Modell um Backup-Beziehungen erweitern
- Restore-Runbook schreiben (Schritt-fuer-Schritt fuer DR)
Offene Aktionspunkte¶
- OVH Backup Storage NFS-Adresse und Mount-Optionen verifizieren
- Hetzner Storage Box bestellen und Sub-Account einrichten
- Docker-Volume-Pfade auf dem Server verifizieren (Praefix abhaengig vom Compose-Projekt)
- Borg-Key-Export sicher verwahren (Proton Pass)
- Healthchecks-Checks anlegen und UUIDs dokumentieren
- Erster Restore-Test nach Einrichtung
- Backup-Groesse nach erstem Lauf dokumentieren
- Entscheidung: Separate Passphrase pro Repo oder gemeinsame Passphrase?
- dump-databases.sh: Fehlerbehandlung verbessern (Abbruch bei fehlgeschlagenem Dump)
Risiken¶
| Risiko | Mitigation |
|---|---|
| Borg-Key oder Passphrase verloren | Key-Export bei Einrichtung erstellen, in Proton Pass speichern. Beide Repos haben eigene Keys — Verlust betrifft nur ein Repo. |
| NFS-Mount nicht verfuegbar | soft,intr Mount-Optionen verhindern Haenger. borgmatic meldet Fehler an Healthchecks. Hetzner-Backup laeuft unabhaengig weiter. |
| Hetzner Storage Box nicht erreichbar | OVH-Backup laeuft unabhaengig. Alerting via Healthchecks bei Fehler. |
| Backup-Fenster zu lang (Netzwerk-Engpass) | Bei unter 50 GB Quelldaten und Borg-Deduplizierung sind inkrementelle Backups klein (~MB bis wenige GB). Initial-Backup kann laenger dauern. |
| Nextcloud-Dateien inkonsistent ohne Maintenance Mode | Maintenance Mode wird im Pre-Hook aktiviert. Kurze Downtime (~Sekunden fuer DB-Dump) ist akzeptabel. |
| DB-Dump schlaegt fehl, Backup laeuft trotzdem | dump-databases.sh mit set -euo pipefail — Fehler bricht Pre-Hook ab, borgmatic stoppt. |
| OVH Backup Storage voll (500 GB) | Monitoring der Repo-Groesse. Bei 50 GB Quelldaten und Retention 14d/8w/12m geschaetzte Nutzung 30-80 GB — ausreichend Reserve. |
| Kein regelmaessiger Restore-Test | Quartalsweisen Restore-Test als Reminder in Healthchecks einplanen. |
Kosten¶
| Posten | Kosten |
|---|---|
| OVH Backup Storage (500 GB) | 0 EUR/Monat (im Server inklusive) |
| Hetzner Storage Box BX11 (1 TB) | ~3,81 EUR/Monat |
| Healthchecks + ntfy (self-hosted) | 0 EUR (bestehende Infrastruktur) |
| Gesamt | ~3,81 EUR/Monat |
Zukunftsaussichten¶
- Borg 2.0 Migration: Sobald Borg 2.0 stabil ist, Migration evaluieren (neue Repository-Architektur, bessere Performance)
- Nextcloud-Daten wachsen: Bei deutlichem Wachstum ggf. Storage Box upgraden (BX21, 5 TB) oder separate Nextcloud-Backup-Strategie
- Weitere Server anbinden: Das Konzept ist auf zusaetzliche Server uebertragbar (gleiche borgmatic-Konfiguration, eigene Repos)
- Append-Only-Mode: Fuer zusaetzlichen Schutz gegen Ransomware koennte das Hetzner-Repo im Append-Only-Mode betrieben werden (Pruning dann nur ueber separaten Zugang)