Paperless-ngx: Automatisches Backup mit Ofelia

So richtest du ein automatisches Backup für Paperless-ngx direkt im Docker-Stack mit Ofelia ein.

Paperless-ngx: Automatisches Backup mit Ofelia hero image

Ein Dokumentenmanagementsystem ohne Backup geht so lange gut, bis es schiefgeht. Ein manuelles Backup ist aufwendig und das reine Kopieren der Ordner könnte zu einem korrupten Backup führen. Bei fertigen NAS-Systemen könnte ein Systemupdate außerdem einen manuell angelegten Cronjobh löschen.

Deshalb zeige ich dir heute eine “Container-Native” Backup-Lösung. Wir nutzen Ofelia, einen Job-Scheduler, der direkt in Docker läuft. Das bedeutet: Dein Backup gehört zum Stack dazu, ist portabel und überlebt theoretisch jedes System-Update.

Die Strategie

Wir wollen folgendes erreichen:

  1. Vollautomatische Sicherung: Jede Nacht (oder wie du willst) wird ein Backup erstellt.
  2. Datenbank-Konsistenz: Wir nutzen pg_dump, um die Datenbank sauber zu exportieren.
  3. Unabhängigkeit: Das Backup-Tool läuft als Container (backup-runner) und wird von ofelia gesteuert.
  4. Kompatibilität: Wir fixen das Problem, dass Synology oft eine veraltete Docker-API nutzt.

Schritt 1: Ordnerstruktur vorbereiten

Bevor wir mit Containern hantieren, schaffen wir Ordnung auf dem NAS. Öffne beispielsweise die File Station auf deiner Synology.

Im Ordner docker/paperless (oder wo auch immer du Paperless installiert hast), erstellen wir folgende Struktur:

  • backups (Hier landen die fertigen Backups)
  • scripts (Hier kommt unser Backup-Skript rein)
  • export (Temporärer Ordner für den Paperless Exporter)
  • … sowie die üblichen Ordner (data, media, db, consume, cache)

Schritt 2: Das Backup-Skript

Erstelle eine Datei namens backup.sh. Dieses Skript wird später im Container ausgeführt.

#!/bin/bash
set -e

# --- KONFIGURATION ---
BACKUP_BASE_DIR="/backup"
PAPERLESS_DATA_DIR="/paperless_data"
LATEST_LINK="${BACKUP_BASE_DIR}/latest"
RETENTION_DAYS=30

# Namen
PG_CONTAINER="paperless-db"
WEBSERVER_CONTAINER="paperless"
PG_USER="paperless"
PG_DB="paperless"

echo "--- Backup Start: $(date) ---"

# 1. Verzeichnis erstellen
DATE_STAMP=$(date +"%Y-%m-%d_%H-%M-%S")
BACKUP_DIR="${BACKUP_BASE_DIR}/${DATE_STAMP}"
mkdir -p "${BACKUP_DIR}"

# 2 Versionsinfos speichern
echo "Speichere Versionen..."
VERSION_FILE="${BACKUP_DIR}/restore_info.txt"
echo "Backup Timestamp: ${DATE_STAMP}" > "${VERSION_FILE}"
# Postgres Version auslesen
PG_VER=$(docker exec "${PG_CONTAINER}" psql -U "${PG_USER}" -d "${PG_DB}" -c "SHOW server_version;" -t | xargs)
echo "PostgreSQL Version: ${PG_VER}" >> "${VERSION_FILE}"

# 3. Datenbank Dump
echo "Erstelle DB Dump..."
docker exec "${PG_CONTAINER}" pg_dump -U "${PG_USER}" -d "${PG_DB}" > "${BACKUP_DIR}/paperless-db.sql"

if [ ! -s "${BACKUP_DIR}/paperless-db.sql" ]; then
    echo "FEHLER: Dump leer!"
    rm -rf "${BACKUP_DIR}"
    exit 1
fi

# 4. Exporter (Sichert Dokumente + Manifeste)
echo "Starte Exporter..."
docker exec "${WEBSERVER_CONTAINER}" document_exporter ../export

# 5. Rsync (Kopiert die exportierten Daten ins Backup)
echo "Synchronisiere Dateien..."
# Wir nutzen Hardlinks (--link-dest), um Speicherplatz zu sparen
LINK_DEST_OPTION=""
if [ -d "${LATEST_LINK}" ]; then
  LINK_DEST_OPTION="--link-dest=${LATEST_LINK}/documents"
fi

rsync -a --delete ${LINK_DEST_OPTION} "${PAPERLESS_DATA_DIR}/export/" "${BACKUP_DIR}/documents/"

# 6. Symlink auf das neueste Backup setzen
ln -snf "${BACKUP_DIR}" "${LATEST_LINK}"

# 7. Aufräumen (Alte Backups löschen)
echo "Lösche alte Backups..."
find "${BACKUP_BASE_DIR}" -maxdepth 1 -type d -not -name "latest" -mtime +${RETENTION_DAYS} -exec rm -rf {} \;

echo "--- Backup Fertig ---"

Lade diese Datei in den Ordner /docker/paperless/scripts hoch.

Schritt 3: Docker Compose (Der kritische Teil)

Jetzt passen wir die docker-compose.yml (bzw. das Projekt im Container Manager) an.

Hier kommt der wichtige Trick für Synology-Nutzer: Wenn wir für den backup-runner einfach das Image alpine:latest nehmen, installiert er die allerneueste Docker CLI. Diese versucht dann, mit der Docker-Engine deiner Synology zu sprechen. Da Synology-Updates oft hinterherhinken, bekommst du ggf. solch einen Fehler im Log:

Error response from daemon: client version 1.52 is too new. Maximum supported API version is 1.43

Die Lösung: Wir nutzen eine ältere Alpine-Version (z.B. 3.15) für den Backup-Runner.

Hier ist die vollständige Konfiguration (Auszug für Backup & Ofelia):

services:
  # ... deine paperless, db, broker services ...
  webserver:
    container_name: paperless # Der Name ist wichtig für das Backup-Skript
    [...]

  db:
    container_name: paperless-db # Der Name ist wichtig für das Backup-Skript
    environment:
      POSTGRES_DB: paperless # Der Name ist wichtig für das Backup-Skript
      POSTGRES_USER: paperless # Der Name ist wichtig für das Backup-Skript
    [...]

  [...]

  ofelia:
    image: mcuadros/ofelia:0.3
    container_name: paperless-ofelia
    restart: unless-stopped
    command: daemon --docker
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    depends_on:
      - backup-runner
	# Muss sich im gleichen Netzwerk befinden

  backup-runner:
    image: alpine:3.15
    container_name: paperless-backup-runner
    command: >
      sh -c "apk add --no-cache bash rsync docker-cli && tail -f /dev/null"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./scripts/backup.sh:/scripts/backup.sh:ro
      - ./export:/paperless_data/export:ro
      - ./backups:/backup # Hier den Pfad anpassen, wo die Backups landen sollen
    labels:
      ofelia.enabled: "true"
      ofelia.job-exec.paperless-backup.schedule: "0 0 3 * * *" 
      # Zeitplan: 0 0 3 * * * = Jeden Tag um 3 Uhr nachts
      # Zum Testen kannst du "0 * * * * *" nehmen (jede Minute)
      ofelia.job-exec.paperless-backup.command: "/bin/bash /scripts/backup.sh"
    # Muss sich im gleichen Netzwerk befinden

Erklärung der Labels

  • ofelia.enabled: Sagt Ofelia, dass dieser Container Jobs hat.
  • ofelia.job-exec...schedule: Der Cron-Ausdruck. 0 0 3 * * * bedeutet täglich um 03:00 Uhr. (Achtung: Ofelia nutzt Go-Cron, das hat 6 Stellen inkl. Sekunden! Die erste 0 sind die Sekunden).
  • ofelia.job-exec...command: Der Befehl, der im backup-runner ausgeführt wird.

Schritt 4: Installation und Test

  1. Erstelle das Projekt im Container Manager (oder via Portainer/SSH) mit der neuen Config.
  2. Warte, bis alle Container gestartet sind.
  3. Öffne das Protokoll (Log) des Containers paperless-ofelia.
  4. Wenn du zum Testen den Zeitplan auf jede Minute gestellt hast, solltest du gleich sehen: Started - /bin/bash /scripts/backup.sh und kurz darauf: Finished in "...", failed: false

Schau in deinen backups-Ordner. Du solltest dort nun Unterordner mit Datumsstempel sehen, die eine paperless-db.sql und einen documents-Ordner enthalten.

Schritt 5: Restore - Der Ernstfall (Disaster Recovery)

Ein Backup, das man nicht wiederherstellen kann, ist nutzlos. Wir simulieren den Totalausfall: Alle Docker-Daten sind weg, nur der backups-Ordner ist noch da.

So stellst du Paperless wieder her:

1. Alles stoppen & bereinigen

Stoppe das Docker-Projekt. Lösche (im Worst Case Szenario) die Inhalte von db, media, data.

2. Backup Ordner bereitstellen

Kopiere ein zusammenhängendes Pärchen aus Ordner documents und paperless-db.sql in einen neuen Ordner namens backup, der sich im gleichen Ordner wie die leeren Ordner media, db usw. befinden.

3. Backup Ordner mounten

Binde diesen Ordner in den Webserver und die Datenbank

services:
  webserver:
    container_name: paperless
  volumes:
    - /volume1/paperless/data:/usr/src/paperless/data
    - /volume1/paperless/media:/usr/src/paperless/media
    - /volume1/paperless/export:/usr/src/paperless/export
    - /volume1/paperless/backup:/backup
  [...]

db:
  container_name: paperless-db # Der Name ist wichtig für das Backup-Skript
  volumes:
    - /volume1/paperless/db:/var/lib/postgresql
    - /volume1/paperless/backup:/backup
  [...]

[...]

4. Nur die Datenbank starten

Starte nur den Container paperless-db. Die anderen lässt du aus. Das erzeugt eine frische, leere Datenbank.

5. Datenbank Dump einspielen

Öffne ein Terminal für den Datenbank-Container (via Portainer “Console” oder Container Manager “Terminal”) mit /bin/bash.

Angenommen, du hast den Dump unter /backup/paperless-db.sql im DB-Container verfügbar gemacht:

# In den Container gehen (falls via SSH)
# docker exec -it paperless-db /bin/bash

# Restore Befehl
psql -U paperless -d paperless < /backup/paperless-db.sql

Wenn keine Fehler kommen: Glückwunsch, die Datenbank ist wieder da!

4. Dokumente importieren

Jetzt starten wir den Rest des Stacks (Webserver, Broker etc.). Öffne ein Terminal für den Webserver (paperless) Container.

Führe den Importer aus:

document_importer /backup/documents/

Paperless liest nun alle Dateien ein, prüft die Datenbank und stellt die Verknüpfungen wieder her.

Fazit

Mit Ofelia und dem dedizierten backup-runner hast du eine robuste Lösung, die unabhängig vom Host-System funktioniert. Denk an die 3-2-1 Regel: Dieses Skript sichert lokal auf das NAS. Nutze Backupdienste wie Cloud Sync oder Duplicati, um diesen backups-Ordner zusätzlich auf eine externe Festplatte oder in die Cloud zu schieben!

FAQs

Warum Alpine 3.15 und nicht latest?

Synology nutzt oft eine ältere Docker-Version. Alpine:latest installiert eine Docker-CLI, die zu neu für die Synology-Engine ist, was zu API-Fehlern führt.

Kann ich das Backup auch manuell anstoßen?

Ja, du kannst dich jederzeit in den 'paperless-backup-runner' Container einloggen und einfach '/scripts/backup.sh' ausführen.

Was passiert mit alten Backups?

Im Skript ist die Variable RETENTION_DAYS=30 gesetzt. Backups, die älter als 30 Tage sind, werden beim nächsten Durchlauf automatisch gelöscht.

Warum Ofelia statt Synology Aufgabenplaner?

Ofelia läuft als Container im gleichen Netzwerk wie Paperless. Das macht das Setup portabel. Wenn du auf einen neuen Server umziehst, nimmst du deine Backup-Logik einfach mit, ohne Cronjobs auf dem Host neu einrichten zu müssen.

Diesen Beitrag teilen:

Diese Website verwendet Cookies. Diese sind notwendig, um die Funktionalität der Website zu gewährleisten. Weitere Informationen finden Sie in der Datenschutzerklärung