RESTIC Backup Script – Funktionsübersicht
1. Ziel
Mein Backup Script ist ein stabiler Split-Backup-Prozess für Docker-Umgebungen, der:
- Stateful Container (z. B. Nextcloud, Nginx Proxy Manager, AdGuard) sichert
- Stateless Container ignoriert oder nur optional einbezieht
- Datenbanken (Nextcloud, Paperless) zuverlässig dumpen
- Restic für snapshots nutzt
- Time Machine automatisiert triggert
- Vollständiges Logging für Nachvollziehbarkeit bietet
2. Kernfunktionen
a) Logging
- Alle Aktionen werden mit Zeitstempel in einer Logdatei (
/Volumes/Daten/Backup/logs/backup_YYYY-MM-DD.log) protokolliert - Konsistente Statusmeldungen (
log "Message") - Standardausgabe + Fehler werden direkt in Logfile umgeleitet
b) Docker-Prüfung und Container-Detection
- Prüft, ob Docker läuft (
docker info) - Erkennt Stateful- und Stateless-Container anhand definierter Image-Listen
- Automatisches Stoppen/Starten der Stateful Container für konsistente Backups
c) Datenbank-Backups
- Nextcloud: dynamische Erkennung des DB-Containers, mysqldump/mariadb-dump mit hartkodiertem Nutzer (
nextcloud) - Paperless: hartkodierte DB-Zugangsdaten (
paperless/paperless) - Dumps landen temporär im Backup-Verzeichnis (
$TMP_DIR)
d) Volume-Backup
- Exportiert Nextcloud-Volume als TAR-Archiv via temporärem Docker-Container (
alpine) - Paperless-Medien und andere Mount-Punkte werden zusätzlich gesichert
e) Restic Backup
- Führt drei getrennte Backup-Tags durch:
core→ temporäre DB Dumps + Stateful Container-Mountsmedia→ Nextcloud TAR + Paperless Media
- Nutzt Restic-Cache für User/Root-Kompatibilität
f) Container Management
- Stoppt Stateful Container vor kritischen Backups
- Startet Container wieder nach Backup
g) Cleanup
- Löscht temporäre Backup-Dateien (
$TMP_DIR) - Bereinigt Restic-Cache und setzt Berechtigungen
- Führt Restic
forget/prunefür Retention durch
h) Time Machine Integration
- Automatisches Starten des Time Machine Backups (
tmutil startbackup)
3. Stärken des Scripts
- Robustheit
- Alles hartkodiert und minimal abstrahiert → kein Risiko von Heuristik-Fehlern
- Stop/Start der Container garantiert konsistente Dumps
- Transparenz & Nachvollziehbarkeit
- Vollständiges Logging mit Zeitstempel
- Alle Schritte klar erkennbar und nachvollziehbar
- Split-Backup Architektur
- Trennung von
core(DBs + Stateful) undmedia(Nextcloud, Paperless) - Verbessert Backup-Performance und Wiederherstellbarkeit
- Trennung von
- Docker- & Restic-kompatibel
- Nutzt vorhandene Docker-Volumes und Container
- Restic-Snapshots mit Tagging, Retention und Prune
- Einfaches temporäres Management
- Temporäre Dump- und Export-Verzeichnisse sauber isoliert
- Keine Abhängigkeit von externen Tools außer Docker, Restic, tmutil
- Stabile Time Machine Integration
- Optionaler, automatisierter Start von TM-Backups
4. Fazit
Es ist nicht das eleganteste Script, aber es ist produktionsreif, stabil und zuverlässig.
Perfekt für produktive Docker-Umgebungen, bei denen Backup-Konsistenz wichtiger ist als Refactoring oder “Optimierung um jeden Preis”.
Install:
brew install restic
Repo initialisieren:
restic -r /Volumes/Daten/Backup/restic-repo init
mein Backup script welches NUR als root ausgeführt wird und im Verzeichnis /Library/Scripts/ als backup_restic_v19.sh liegt.
#!/usr/bin/env bash
set -euo pipefail
#############################################
# CONFIG
#############################################
RESTIC_REPO="/Volumes/Daten/Backup/restic-repo"
export RESTIC_PASSWORD="**********"
# eigener Cache (wichtig für root vs user)
export RESTIC_CACHE_DIR="/Volumes/Daten/Backup/restic-cache"
mkdir -p "$RESTIC_CACHE_DIR"
NEXTCLOUD_VOLUME="nextcloud_33_nextcloud_data"
PAPERLESS_CONTAINER="paper_2_20_13-docs-1"
PAPERLESS_DB_USER="paperless"
PAPERLESS_DB_PASSWORD="paperless"
PAPERLESS_DB_NAME="paperless"
MYSQL_PASSWORD="***************"
TMP_DIR="/Volumes/Daten/Backup/tmp/db-backup"
# wichtig für Docker write access
mkdir -p "$TMP_DIR"
chmod 777 "$TMP_DIR"
LOG_PREFIX="[RESTIC-V19]"
#############################################
# LOGGING
#############################################
LOGFILE="/Volumes/Daten/Backup/logs/backup_$(date +%F).log"
mkdir -p "$(dirname "$LOGFILE")"
exec > >(tee -a "$LOGFILE") 2>&1
log() {
echo "$(date '+[%Y-%m-%d %H:%M:%S]') ${LOG_PREFIX} $1"
}
#############################################
# CHECK DOCKER
#############################################
log "Checking Docker availability"
docker info >/dev/null 2>&1 || { echo "Docker not running"; exit 1; }
#############################################
# DETECT CONTAINERS
#############################################
STATEFUL_CONTAINERS=""
STATELESS_CONTAINERS=""
EXTRA_PATHS=""
STATEFUL_IMAGES=(
"jc21/nginx-proxy-manager"
"adguard/adguardhome"
)
STATELESS_IMAGES=(
"oznu/cloudflare-ddns"
"containous/whoami"
)
log "Detecting containers"
while read -r name image; do
for s in "${STATEFUL_IMAGES[@]}"; do
[[ "$image" == *"$s"* ]] && STATEFUL_CONTAINERS+="$name"$'\n'
done
for s in "${STATELESS_IMAGES[@]}"; do
[[ "$image" == *"$s"* ]] && STATELESS_CONTAINERS+="$name"$'\n'
done
done < <(docker ps --filter "label=backup.include=true" --format '{{.Names}} {{.Image}}')
STATEFUL_CONTAINERS=$(printf "%s" "$STATEFUL_CONTAINERS" | sort -u | sed '/^$/d')
#############################################
# PAPERLESS PATHS
#############################################
log "Detecting Paperless mounts"
PAPERLESS_PATHS=$(docker inspect "$PAPERLESS_CONTAINER" \
| grep '"Source"' \
| awk -F'"' '{print $4}' \
| sort -u || true)
#############################################
# NEXTCLOUD DB DETECTION
#############################################
log "Detecting Nextcloud DB container"
NC_DB_CONTAINER=$(docker ps --format '{{.Names}}' | while read -r c; do
if docker inspect "$c" 2>/dev/null | grep -qi "mariadb\|mysql"; then
if docker exec "$c" ps aux 2>/dev/null | grep -q "mysql\|mariadbd"; then
echo "$c"; break
fi
fi
done || true)
if [[ -z "$NC_DB_CONTAINER" ]]; then
log "ERROR: Nextcloud DB container not found"
exit 1
fi
#############################################
# NEXTCLOUD DB DUMP
#############################################
log "Dumping Nextcloud DB"
docker exec "$NC_DB_CONTAINER" sh -c "
if command -v mysqldump >/dev/null 2>&1; then
mysqldump -u nextcloud -p'${MYSQL_PASSWORD}' nextcloud
else
mariadb-dump -u nextcloud -p'${MYSQL_PASSWORD}' nextcloud
fi
" > "$TMP_DIR/nextcloud.sql"
#############################################
# PAPERLESS DB DETECTION
#############################################
log "Detecting Paperless DB container"
PAPERLESS_DB=$(docker ps --format '{{.Names}}' | grep -iE 'paper.*db|db.*paper|mariadb.*paper' | head -n1 || true)
if [[ -z "$PAPERLESS_DB" ]]; then
log "ERROR: Paperless DB container not found"
exit 1
fi
#############################################
# PAPERLESS DB DUMP (FIXED!)
#############################################
log "Dumping Paperless DB"
docker exec "$PAPERLESS_DB" sh -c "
if command -v mariadb-dump >/dev/null 2>&1; then
DUMP=mariadb-dump
else
DUMP=mysqldump
fi
\$DUMP \
--host=127.0.0.1 \
--user='$PAPERLESS_DB_USER' \
--password='$PAPERLESS_DB_PASSWORD' \
--single-transaction \
--quick \
--lock-tables=false \
$PAPERLESS_DB_NAME
" > "$TMP_DIR/paperless.sql"
#############################################
# NEXTCLOUD ARCHIVE
#############################################
log "Exporting Nextcloud volume"
docker run --rm \
-v "${NEXTCLOUD_VOLUME}:/from" \
-v "$TMP_DIR:/to" \
alpine sh -c "tar cf /to/nextcloud_data.tar -C /from ."
#############################################
# STOP STATEFUL CONTAINERS
#############################################
log "Stopping stateful containers"
echo "$STATEFUL_CONTAINERS" | while read -r c; do
[[ -n "$c" ]] && docker stop "$c" >/dev/null 2>&1 || true
done
#############################################
# EXTRA PATHS
#############################################
log "Collecting mounts"
EXTRA_PATHS=$(echo "$STATEFUL_CONTAINERS" | while read -r c; do
docker inspect "$c" 2>/dev/null | grep '"Source"' | awk -F'"' '{print $4}'
done | sort -u)
#############################################
# CORE BACKUP
#############################################
log "Running CORE backup"
restic -r "$RESTIC_REPO" backup \
"$TMP_DIR" \
$PAPERLESS_PATHS \
$EXTRA_PATHS \
--tag core
#############################################
# MEDIA BACKUP
#############################################
log "Running MEDIA backup"
restic -r "$RESTIC_REPO" backup \
"$TMP_DIR/nextcloud_data.tar" \
/Volumes/Daten/docker/data/paperless-ngx/media \
--tag media
#############################################
# START CONTAINERS
#############################################
log "Starting containers"
echo "$STATEFUL_CONTAINERS" | while read -r c; do
[[ -n "$c" ]] && docker start "$c" >/dev/null 2>&1 || true
done
#############################################
# CLEAN TMP
#############################################
rm -rf "$TMP_DIR"
#############################################
# RESTIC CLEANUP
#############################################
log "Running forget/prune"
restic -r "$RESTIC_REPO" forget \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6 \
--prune
#############################################
# CACHE PERMISSION FIX
#############################################
mkdir -p /Volumes/Daten/Backup/restic-cache
chown -R credel:staff /Volumes/Daten/Backup/restic-cache || true
chmod 755 /Volumes/Daten/Backup/restic-cache || true
#############################################
# TIME MACHINE
#############################################
log "Triggering Time Machine"
tmutil startbackup --auto >/dev/null 2>&1 || true
log "===== V19 SPLIT BACKUP DONE ====="
Test-Recovery (Validierung meiner Backups)
1. Snapshot auswählen
ID merken (z. B. de8e6ede)
2. Restore in Test-Verzeichnis
–target /Volumes/Daten/RESTORE_TEST/
3. Daten prüfen (Quick Check)
Ziel:
- echte Dateien vorhanden ✔
- Verzeichnisstruktur korrekt ✔
4. DB Dumps prüfen
Optional tiefer:
head -n 20 paperless.sql
Ziel:
- SQL Dumps vorhanden ✔
- keine leeren Dateien ✔
5. (Optional) Stichproben öffnen
- PDFs öffnen
- Bilder ansehen
Ziel:
- Daten wirklich lesbar ✔
Was du damit validierst
- Restic Repository ist konsistent
- Snapshots sind vollständig
- Daten sind physisch wiederherstellbar
- DB Dumps sind nutzbar
⚠️ Wichtige Regel
Nie direkt ins Livesystem restoren
→ immer in:
Kurzform (Merksatz)
Snapshot → Restore → Dateien prüfen → DB prüfen → fertig
#############################
Notfall-Runbook (Ultra-kompakt)
Scenario: SSD/Host komplett defekt – Recovery aus Time Machine + Restic
0. Voraussetzungen (neues System)
- macOS oder Linux neu installiert
- Docker installiert
- Zugriff auf Time Machine Disk
1. Time Machine einbinden
GUI (empfohlen)
- Time Machine Disk mounten
- Finder → Time Machine öffnen
Ziel:
/Volumes/Daten/
Restore nach:
/Volumes/Daten/
2. Restore von Restic Repository (KRITISCH)
Aus Time Machine wiederherstellen:
Ziel:
3. Restic prüfen
Wenn OK → Repository intakt
4. Ziel-Ordner für Restore vorbereiten
5. Snapshot Restore
–target /Volumes/Daten/RESTORE_RECOVERY/
6. Docker Daten zurückspielen
Nextcloud / Paperless / NPM / Adguard Daten:
aus:
nach:
7. Docker Stack starten
oder einzeln:
8. Paperless (kritischer Check)
Prüfen:
- DB läuft
- Media vorhanden
- Web UI erreichbar
9. Nextcloud (optional rebuild)
Falls nötig:
- Container neu deployen
/datawieder mounten- Login prüfen
10. Validierung (Minimal)
⚠️ KRITISCHE ABHÄNGIGKEITEN
✔ Restic Repo = ESSENTIAL
✔ Paperless DB + media = CRITICAL
✔ Docker Compose Files = MUST HAVE
✔ Time Machine = LAST RESORT BACKUP LAYER
MENTAL MODEL
↓
Restic Repo Restore
↓
Snapshot Restore
↓
Docker Stack Rebuild
↓
Paperless Validated
END STATE
Wenn alles funktioniert:
- Paperless läuft wie vorher
- Container stack ist wiederhergestellt
- Daten konsistent
- System vollständig rekonstruierbar
#############################
Datenintegrität über Zeit:
Das ist der einzige Punkt, den man im Auge behalten sollte. Minimal-Empfehlung ohne Script anzufassen. Ab und zu manuell:
restic -r /Volumes/Daten/Backup/restic-repo check --read-data-subset=5%
example output:
credel@CREDELS-3540 Scripts % restic -r /Volumes/Daten/Backup/restic-repo check –read-data-subset=5%
using temporary cache in /var/folders/tq/vkhnvll57s742h0qhrk6lnvc0000gn/T/restic-check-cache-857334196
create exclusive lock for repository
enter password for repository:
repository 92697ba5 opened (version 2, compression level auto)
created new cache in /var/folders/tq/vkhnvll57s742h0qhrk6lnvc0000gn/T/restic-check-cache-857334196
load indexes
[0:00] 100.00% 3 / 3 index files loaded
check all packs
check snapshots, trees and blobs
[0:00] 100.00% 12 / 12 snapshots
read 5.0% of data packs
[0:03] 100.00% 50 / 50 packs
no errors were found
Backups Runs automatisieren:

#############################
Restic Browser
Download (offiziell)
Restic Browser Releases öffnen
Dort findest du:
- fertige macOS-Binaries (.dmg oder .app)
- Versionen für Windows und Linux
- immer die aktuellste Release-Version
Laut Projektbeschreibung werden die vorgebauten Programme genau dort bereitgestellt
Was du konkret machen musst (macOS)
- Release-Seite öffnen (Link oben)
- Neueste Version wählen (z. B.
v0.3.x) - Datei laden:
- meist
.dmgoder.tar.gz
- meist
- App nach
/Applicationsziehen - Starten
⚠️ Wichtige Voraussetzung
Der Browser ist nur ein Frontend – du brauchst weiterhin das CLI:
resticmuss installiert sein (z. B. via Homebrew)- und im PATH liegen
Das Tool ruft intern das restic-Binary auf
Beispiel:
Was du danach bekommst
Der Restic Browser kann:
- Snapshots anzeigen
- durch Ordner browsen
- einzelne Dateien wiederherstellen
- Daten als ZIP exportieren
Aber:
- ❌ keine Backups konfigurieren
- ❌ kein Scheduling
also genau mein Use Case: Restore + Browsing

Am Rande:
Was hier dokumentiert ist hat einige Tage an Arbeit gekostet. Ohne ChatGPT hätte ich das sowieso nie hinbekommen. Dennoch war es nicht ganz trivial, da mich ChatGPT oft irre geführt hat und immer wieder neu angepasste Backup scrips kaputt optimiert hat, was z.T. echt nervig war.