Paperless-ngx — Synology NAS Setup Anleitung
10 Container · Grafana · Prometheus · Automatisches Backup · 4 Dateinamens-Varianten
paperless-ngx 2.20 PostgreSQL 18 Valkey 9 Grafana 12 Prometheus Ofelia Gotenberg Tika Synology DSM

📋 Inhaltsverzeichnis

  1. Architektur & Container-Übersicht
  2. Vorbereitung — Ordner & Berechtigungen
  3. Prometheus Konfiguration erstellen
  4. ENV Datei erstellen
  5. Backup-Skript erstellen
  6. Stack starten & prüfen
  7. Grafana einrichten
  8. Dateinamens-Format Varianten A–D
  9. Komplette docker-compose.yml
  10. Nützliche Befehle

1 Architektur & Container-Übersicht

Das Setup besteht aus 10 Containern die in einem internen Docker-Netzwerk paperless_network kommunizieren. Nur die nötigen Ports werden nach außen exponiert.

#ContainerImagePortFunktion
1paperless-redisvalkey/valkey:9internMessage Broker / Queue
2paperless-dbpostgres:18internDatenbank (Metadaten)
3paperlesspaperless-ngx:2.20.118090Hauptanwendung + Web UI
4paperless-gotenberggotenberg:83005PDF-Konvertierung
5paperless-tikaapache/tika:3.2.3.0internOffice Dokument-Analyse
6paperless-ofeliamcuadros/ofelia:0.3internCron-Job Scheduler
7paperless-backup-runneralpine:3.15internAutomatisches Backup täglich 20:10
8paperless-redis-exporterredis_exporter:v1.81.0internMetriken für Prometheus
9paperless-grafanagrafana/grafana:12.43001Dashboard & Visualisierung
10paperless-prometheusprom/prometheus:v3.10.0internMetriken-Sammler

Startabhängigkeiten: paperless startet erst wenn db und broker healthy sind. ofelia startet erst wenn backup-runner läuft.

2 Vorbereitung — Ordner & Berechtigungen

2.1 Alle Ordner erstellen

1

File Station öffnen

2

Navigiere zu /volume1/docker/

3

Neuen Ordner paperless erstellen

4

Darin folgende Unterordner einzeln erstellen: redis, db, data, media, export, consume, grafana, prometheus, scripts, backups

💡 Tipp: SSH ist hier deutlich schneller — ein Befehl erstellt alle Ordner auf einmal.

Alle Ordner auf einmal erstellen:

mkdir -p /volume1/docker/paperless/{redis,db,data,media,export,consume,grafana,prometheus,scripts,backups}

2.2 Grafana Berechtigung setzen

Grafana läuft intern als User 472 — ohne diesen Schritt startet Grafana nicht und wirft Permission-Fehler!

1

File Station öffnen → /volume1/docker/paperless/ navigieren

2

Rechtsklick auf den Ordner grafanaEigenschaften

3

Tab Berechtigung → bei Eigentümer auf 472 setzen → Übernehmen

4

Alle Berechtigungen auf Lesen/Schreiben/Ausführen setzen → Fertig

💡 SSH ist hier zuverlässiger — die GUI setzt manchmal nicht alle Unterordner-Rechte korrekt.

chown -R 472:472 /volume1/docker/paperless/grafana
chmod -R 775 /volume1/docker/paperless/grafana

Prüfen ob korrekt gesetzt:

ls -la /volume1/docker/paperless/ | grep grafana

Erwartete Ausgabe: drwxrwxr-x 472 472 grafana

2.3 Eigene UID ermitteln

Diese UID muss in der docker-compose.yml bei user: und USERMAP_UID eingetragen werden.

1

DSMSystemsteuerungBenutzer & Gruppe

2

Tab Benutzer → Deinen Benutzer anklicken → Bearbeiten

3

Die angezeigte UID notieren — diese in die docker-compose.yml eintragen

id DEINUSERNAME

Ausgabe z.B.: uid=1026(deinname) gid=100(users) → UID ist 1026, GID ist 100

3 Prometheus Konfiguration erstellen

Datei erstellen: /volume1/docker/paperless/prometheus/prometheus.yml

1

File Station öffnen → /volume1/docker/paperless/prometheus/ navigieren

2

Erstellen → Neue Datei → Name: prometheus.yml

3

Datei mit Rechtsklick öffnen → Text Editor öffnen mitText Editor

4

Folgenden Inhalt einfügen und speichern:

💡 Text Editor muss einmalig im Paket-Zentrum installiert werden falls noch nicht vorhanden.

global:
  scrape_interval: 15s
scrape_configs:
  - job_name: 'paperless-monitoring'
    static_configs:
      - targets: ['paperless-redis-exporter:9121']
cat > /volume1/docker/paperless/prometheus/prometheus.yml << 'EOF'
global:
  scrape_interval: 15s
scrape_configs:
  - job_name: 'paperless-monitoring'
    static_configs:
      - targets: ['paperless-redis-exporter:9121']
EOF

4 .env Datei erstellen

Die .env Datei muss im gleichen Ordner wie die docker-compose.yml liegen: /volume1/docker/paperless/.env

1

File Station öffnen → oben rechts Einstellungen"Versteckte Dateien anzeigen" aktivieren

2

Navigiere zu /volume1/docker/paperless/

3

Erstellen → Neue Datei → Name: .env (Punkt am Anfang!)

4

Datei mit Rechtsklick öffnen → Öffnen mitText Editor → Inhalt einfügen und speichern:

POSTGRES_PASSWORD=SICHERES_PASSWORT_HIER
PAPERLESS_DBPASS=GLEICHES_PASSWORT_WIE_OBEN
PAPERLESS_SECRET_KEY=LANGEN_ZUFAELLIGEN_KEY_HIER
GF_SECURITY_ADMIN_PASSWORD=GRAFANA_PASSWORT_HIER

💡 Secret Key per SSH generieren: openssl rand -base64 50 — dann in die .env einfügen

Secret Key generieren:

openssl rand -base64 50

.env Datei erstellen:

nano /volume1/docker/paperless/.env

.env korrekt gelesen prüfen:

docker compose config | grep PASSWORD

⚠️ PAPERLESS_SECRET_KEY niemals nach erstem Start ändern!

⚠️ POSTGRES_PASSWORD und PAPERLESS_DBPASS müssen identisch sein!

⚠️ .env Datei niemals teilen oder in Git einchecken!

5 Backup-Skript erstellen

Datei erstellen: /volume1/docker/paperless/scripts/backup.sh

1

File Station → /volume1/docker/paperless/scripts/ navigieren

2

Erstellen → Neue Datei → Name: backup.sh

3

Rechtsklick auf backup.shÖffnen mitText Editor → Inhalt unten einfügen → Speichern

4

Ausführbar machen: Rechtsklick → Eigenschaften → Tab Berechtigung → bei Eigentümer "Ausführen" aktivieren → Übernehmen

⚠️ Die GUI setzt das "Ausführbar"-Bit manchmal nicht korrekt. SSH-Befehl zur Sicherheit empfohlen: chmod +x /volume1/docker/paperless/scripts/backup.sh

Skript erstellen und ausführbar machen:

chmod +x /volume1/docker/paperless/scripts/backup.sh

Backup manuell testen:

docker exec paperless-backup-runner /bin/bash /scripts/backup.sh

Backup Log prüfen:

cat /volume1/docker/paperless/backups/backup.log

Inhalt der backup.sh:

#!/bin/bash
set -e

# --- LOGGING (alle Ausgaben in Logdatei + Konsole) ---
exec > >(tee -a "/backup/backup.log") 2>&1

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

# Container 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: DB Dump ist leer — Backup abgebrochen!"
    rm -rf "${BACKUP_DIR}"
    exit 1
fi

# 4. Paperless Exporter (sichert Dokumente + Manifeste)
# WICHTIG: python3 manage.py ist zwingend erforderlich!
echo "Starte Paperless Exporter..."
docker exec "${WEBSERVER_CONTAINER}" python3 manage.py document_exporter ../export

# 5. Rsync (kopiert exportierte Daten ins Backup, Hardlinks sparen Speicherplatz)
echo "Synchronisiere Dateien..."
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, || true verhindert Abbruch wenn nichts zu löschen)
echo "Lösche Backups älter als ${RETENTION_DAYS} Tage..."
find "${BACKUP_BASE_DIR}" -maxdepth 1 -type d -not -name "latest" -mtime +${RETENTION_DAYS} -exec rm -rf {} \; || true

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

Backup-Struktur:

/volume1/docker/paperless/backups/
├── 2024-03-15_20-10-00/
│   ├── paperless-db.sql     ← Datenbank-Dump
│   ├── restore_info.txt     ← Versionsinfos für Restore
│   └── documents/           ← Alle Dokumente + Manifeste
├── 2024-03-16_20-10-00/
├── latest -> 2024-03-16_20-10-00  ← Symlink neuestes Backup
└── backup.log               ← Alle Backup-Logs

⚠️ Backups älter als 30 Tage werden automatisch gelöscht.

⚠️ Backup liegt auf gleicher Disk wie Daten — externes Backup zusätzlich empfohlen!

6 Stack starten & prüfen

Stack starten:

1

DSMContainer ManagerProjekt

2

Erstellen klicken → Projektname: paperless

3

Pfad: /volume1/docker/paperless auswählen

4

Die docker-compose.yml wird automatisch erkannt → Weiter

5

Erstellen → Container Manager startet alle 10 Container automatisch

6

Status prüfen: Im Projekt alle Container sollten grün "Wird ausgeführt" zeigen

cd /volume1/docker/paperless
docker compose up -d

Status prüfen:

docker compose ps

Alle Container sollten "running" zeigen. db und broker zeigen zusätzlich "(healthy)".

Admin-User anlegen:

1

Container ManagerProjekt paperlessContainer paperless

2

Container anklicken → Tab Terminal & Protokoll

3

Terminal erstellen → Shell öffnet sich

4

Befehl eingeben:

python3 manage.py createsuperuser
docker exec -it paperless python3 manage.py createsuperuser
ServiceURL
Paperless UIhttp://NAS-IP:8090
Grafana UIhttp://NAS-IP:3001
Gotenberghttp://NAS-IP:3005

7 Grafana einrichten

A) Prometheus als Datenquelle hinzufügen:

  1. Grafana öffnen: http://NAS-IP:3001 — Login: admin / Grafana-Passwort aus .env
  2. Links im Menü: Connections → Data sources
  3. Oben rechts: "Add new data source"
  4. "Prometheus" auswählen
  5. URL eintragen: http://paperless-prometheus:9090
  6. Ganz unten: "Save & test" klicken

Grüne Meldung "Successfully queried" = Verbindung korrekt!

B) Redis Dashboard importieren:

  1. Links im Menü: Dashboards → New → Import
  2. Bei "Import via grafana.com" eingeben: 763
  3. "Load" klicken
  4. Bei "Prometheus" die soeben angelegte Datenquelle auswählen
  5. "Import" klicken — Dashboard öffnet sich automatisch

C) Als Home Dashboard setzen:

  1. Das importierte Dashboard öffnen
  2. Oben rechts auf das Stern-Symbol ★ klicken
  3. Oben rechts: Profil-Icon → Profile
  4. Preferences → Home Dashboard → Redis-Dashboard auswählen → "Save"

8 Dateinamens-Format Varianten A–D

In der docker-compose.yml die gewünschte Option einkommentieren, andere auskommentieren. Danach:

docker exec -it paperless python3 manage.py document_renamer
OptionStrukturEmpfehlung
ABesitzer/Korrespondent/Jahr/Datum - Titel✅ Empfohlen — simpel & robust
BBesitzer/Korrespondent/Dokumententyp/Datum - TitelWenn Typ wichtiger als Jahr
CBesitzer/Korrespondent/Dokumententyp/Jahr/Datum - TitelDetaillierteste einfache Struktur
DBesitzer/Thema-Tag/Korrespondent/Jahr/Datum - Typ - TitelTag als Ordner — wartungsintensiv

A Besitzer / Korrespondent / Jahr / Datum - Titel

PAPERLESS_FILENAME_FORMAT: "{% if owner_username %}{{ owner_username }}{% else %}gemeinsam{% endif %}/{% if correspondent %}{{ correspondent }}{% else %}Allgemein{% endif %}/{{ created_year }}/{{ created }} - {{ title }}"

B Besitzer / Korrespondent / Dokumententyp / Datum - Titel

PAPERLESS_FILENAME_FORMAT: "{% if owner_username %}{{ owner_username }}{% else %}gemeinsam{% endif %}/{% if correspondent %}{{ correspondent }}{% else %}Allgemein{% endif %}/{% if document_type %}{{ document_type }}{% else %}Allgemein{% endif %}/{{ created }} - {{ title }}"

C Besitzer / Korrespondent / Dokumententyp / Jahr / Datum - Titel

PAPERLESS_FILENAME_FORMAT: "{% if owner_username %}{{ owner_username }}{% else %}gemeinsam{% endif %}/{% if correspondent %}{{ correspondent }}{% else %}Allgemein{% endif %}/{% if document_type %}{{ document_type }}{% else %}Allgemein{% endif %}/{{ created_year }}/{{ created }} - {{ title }}"

D Besitzer / Thema-Tag / Korrespondent / Jahr / Datum - Typ - Titel

Verfügbare Themen-Tags (Reihenfolge = Priorität):

DokumenteGemeindeSteuerRechtFinanzen VersicherungGesundheitArbeitITGeräte FuhrparkWohnenNebenkostenHandwerkGarten ReiseSportSpendenKitaSchule BildungBücherStudiumPflegeRente AbosHaushaltAllgemein (Fallback)
PAPERLESS_FILENAME_FORMAT: "{% if owner_username %}{{ owner_username }}{% else %}gemeinsam{% endif %}/{% if 'Dokumente' in tag_name_list %}Dokumente{% elif 'Gemeinde' in tag_name_list %}Gemeinde{% elif 'Steuer' in tag_name_list %}Steuer{% elif 'Recht' in tag_name_list %}Recht{% elif 'Finanzen' in tag_name_list %}Finanzen{% elif 'Versicherung' in tag_name_list %}Versicherung{% elif 'Gesundheit' in tag_name_list %}Gesundheit{% elif 'Arbeit' in tag_name_list %}Arbeit{% elif 'IT' in tag_name_list %}IT{% elif 'Geräte' in tag_name_list %}Geräte{% elif 'Fuhrpark' in tag_name_list %}Fuhrpark{% elif 'Wohnen' in tag_name_list %}Wohnen{% elif 'Nebenkosten' in tag_name_list %}Nebenkosten{% elif 'Handwerk' in tag_name_list %}Handwerk{% elif 'Garten' in tag_name_list %}Garten{% elif 'Reise' in tag_name_list %}Reise{% elif 'Sport' in tag_name_list %}Sport{% elif 'Spenden' in tag_name_list %}Spenden{% elif 'Kita' in tag_name_list %}Kita{% elif 'Schule' in tag_name_list %}Schule{% elif 'Bildung' in tag_name_list %}Bildung{% elif 'Bücher' in tag_name_list %}Bücher{% elif 'Studium' in tag_name_list %}Studium{% elif 'Pflege' in tag_name_list %}Pflege{% elif 'Rente' in tag_name_list %}Rente{% elif 'Abos' in tag_name_list %}Abos{% elif 'Haushalt' in tag_name_list %}Haushalt{% else %}Allgemein{% endif %}/{% if correspondent %}{{ correspondent }}{% else %}Allgemein{% endif %}/{{ created_year }}/{{ created }} - {% if document_type %}{{ document_type }} - {% endif %}{{ title }}"

Wichtig: tag_name_list verwenden — NICHT tags! (tags ist ein QuerySet, kein String)

9 Komplette docker-compose.yml

Speichern als: /volume1/docker/paperless/docker-compose.yml

Anpassen vor dem Start: user: "UID:GID", USERMAP_UID, PAPERLESS_ALLOWED_HOSTS und ggf. Pfade und Zeitzone.

# ══════════════════════════════════════════════════════════════════════════════
# PAPERLESS-NGX — Docker Compose Setup
# Synology DS723+ | paperless-ngx 2.20.11
# ══════════════════════════════════════════════════════════════════════════════
#
# ┌─────────────────────────────────────────────────────────────────────────┐
# │                    ERSTEINRICHTUNG — SCHRITT FÜR SCHRITT                │
# └─────────────────────────────────────────────────────────────────────────┘
#
# SCHRITT 1 — ORDNER ERSTELLEN (SSH auf Synology)
# ─────────────────────────────────────────────────
# Folgende Ordner müssen VOR dem ersten Start existieren!
# Alle auf einmal erstellen (SSH):
#
#   mkdir -p /volume1/docker/paperless/{redis,db,data,media,export,consume,grafana,prometheus,scripts,backups}
#
# ─────────────────────────────────────────────────────────────────────────
#
# SCHRITT 2 — GRAFANA BERECHTIGUNG SETZEN (SSH auf Synology)
# ────────────────────────────────────────────────────────────
# Grafana läuft intern als User 472 — der braucht Schreibrechte auf den Ordner!
# Ohne diesen Schritt startet Grafana nicht und wirft Permission-Fehler.
#
#   chown -R 472:472 /volume1/docker/paperless/grafana
#   chmod -R 775 /volume1/docker/paperless/grafana
#
# Prüfen ob korrekt gesetzt:
#   ls -la /volume1/docker/paperless/ | grep grafana
#   → Ausgabe muss zeigen: drwxrwxr-x  472  472  grafana
#
# ─────────────────────────────────────────────────────────────────────────
#
# SCHRITT 3 — PROMETHEUS KONFIGURATION ERSTELLEN
# ────────────────────────────────────────────────
# Datei erstellen: /volume1/docker/paperless/prometheus/prometheus.yml
# (DSM → File Station → prometheus Ordner → Neue Datei: prometheus.yml)
#
# Inhalt der prometheus.yml:
#
#   global:
#     scrape_interval: 15s
#   scrape_configs:
#     - job_name: 'paperless-monitoring'
#       static_configs:
#         - targets: ['paperless-redis-exporter:9121']
#
# ─────────────────────────────────────────────────────────────────────────
#
# SCHRITT 4 — .env DATEI ERSTELLEN
# ─────────────────────────────────
# Die .env Datei enthält alle sensiblen Passwörter und Keys.
# Sie muss im GLEICHEN Ordner wie diese docker-compose.yml liegen!
#
# So erstellen (DSM → File Station):
#   1. File Station öffnen
#   2. Einstellungen → "Versteckte Dateien anzeigen" aktivieren
#   3. Navigiere zu: /volume1/docker/paperless/
#   4. Neue Datei erstellen mit dem Namen: .env  (Punkt am Anfang!)
#   5. Folgenden Inhalt einfügen und Passwörter anpassen:
#
#      POSTGRES_PASSWORD=SICHERES_PASSWORT_HIER
#      PAPERLESS_DBPASS=GLEICHES_PASSWORT_WIE_OBEN
#      PAPERLESS_SECRET_KEY=LANGEN_ZUFAELLIGEN_KEY_HIER
#      GF_SECURITY_ADMIN_PASSWORD=GRAFANA_PASSWORT_HIER
#
#   6. Datei speichern
#
# Secret Key generieren (SSH):  openssl rand -base64 50
#
# ⚠️  PAPERLESS_SECRET_KEY niemals nach erstem Start ändern!
# ⚠️  .env Datei niemals teilen oder in Git einchecken!
# ⚠️  POSTGRES_PASSWORD und PAPERLESS_DBPASS müssen identisch sein!
#
# ─────────────────────────────────────────────────────────────────────────
#
# SCHRITT 5 — DIESE WERTE ANPASSEN (bei neuer Instanz)
# ──────────────────────────────────────────────────────
# Suche nach dem Kommentar "← Anpassen bei neuer Instanz" und passe an:
#
#   USERMAP_UID / USERMAP_GID
#     → Synology Benutzer-ID ermitteln:
#       DSM → Systemsteuerung → Benutzer → Benutzer anklicken → Info
#       Oder per SSH: id DEINUSERNAME
#
#   PAPERLESS_ALLOWED_HOSTS
#     → Eigene LAN-IP eintragen (DSM → Systemsteuerung → Netzwerk)
#     → Tailscale-IP falls vorhanden
#
#   Volume-Pfade (/volume1/docker/paperless/...)
#     → Eigenen Speicherpfad anpassen falls abweichend
#
#   TZ / PAPERLESS_TIME_ZONE / _JAVA_OPTIONS
#     → Eigene Zeitzone setzen falls nicht Europe/Berlin
#     → Zeitzonen-Liste: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
#
#   PAPERLESS_OCR_LANGUAGE
#     → Gewünschte OCR-Sprachen setzen
#     → Sprachcodes: deu=Deutsch, eng=Englisch, fra=Französisch etc.
#
# ─────────────────────────────────────────────────────────────────────────
#
# SCHRITT 6 — STACK STARTEN
# ──────────────────────────
#   DSM → Container Manager → Projekt → docker-compose.yml importieren
#   Oder per SSH:
#     cd /volume1/docker/paperless
#     docker compose up -d
#
# ─────────────────────────────────────────────────────────────────────────
#
# SCHRITT 7 — NACH DEM ERSTEN START
# ───────────────────────────────────
#   Paperless UI:  http://DEINE-IP:8090
#   Grafana UI:    http://DEINE-IP:3001  (Login: admin / Grafana-Passwort aus .env)
#
#   Admin-User anlegen (SSH):
#     docker exec -it paperless python3 manage.py createsuperuser
#
# ─────────────────────────────────────────────────────────────────────────
#
# NÜTZLICHE BEFEHLE
# ──────────────────
#   Stack neu starten:
#     docker compose down && docker compose up -d
#
#   Dateien nach Format-Änderung umbenennen:
#     docker exec -it paperless python3 manage.py document_renamer
#
#   Logs anzeigen:
#     docker compose logs -f paperless
#
#   .env korrekt gelesen? (Passwörter prüfen):
#     docker compose config | grep PASSWORD
#
# ══════════════════════════════════════════════════════════════════════════════


networks:
  paperless_network:
    name: paperless_network  # Interner Netzwerkname für Container-Kommunikation
    external: false          # Wird von Docker selbst verwaltet, nicht extern


services:

  # ============================================================
  # 1. BROKER — Valkey 9 (Redis-kompatibler Message Broker)
  #    Verwaltet die Aufgabenwarteschlange zwischen Webserver
  #    und den Hintergrundprozessen (OCR, Indexierung etc.)
  # ============================================================
  broker:
    container_name: paperless-redis
    image: valkey/valkey:9                   # Valkey = moderner Redis-Fork, schneller & ressourcenschonender
    restart: unless-stopped
    networks:
      - paperless_network                    # Nur intern erreichbar, kein Port nach außen nötig
    volumes:
      - /volume1/docker/paperless/redis:/data                        # ← Pfad anpassen bei neuer Instanz
      - /usr/share/zoneinfo/Europe/Berlin:/etc/localtime:ro          # ← Zeitzone anpassen bei neuer Instanz
    environment:
      TZ: Europe/Berlin                                              # ← Zeitzone anpassen bei neuer Instanz
    user: "1026:100"                         # ← UID:GID anpassen bei neuer Instanz (id DEINUSERNAME)
    healthcheck:
      test: ["CMD", "valkey-cli", "ping"]    # Prüft ob Broker erreichbar ist
      interval: 10s
      timeout: 5s
      retries: 5


  # ============================================================
  # 2. DATENBANK — PostgreSQL 18
  #    Speichert alle Metadaten: Dokumente, Tags, Korrespondenten,
  #    User, Workflows, Custom Fields etc.
  # ============================================================
  db:
    container_name: paperless-db
    image: postgres:18
    restart: unless-stopped
    networks:
      - paperless_network                    # Nur intern erreichbar — kein Port nach außen!
    volumes:
      - /volume1/docker/paperless/db:/var/lib/postgresql             # ← Pfad anpassen bei neuer Instanz
      - /usr/share/zoneinfo/Europe/Berlin:/etc/localtime:ro          # ← Zeitzone anpassen bei neuer Instanz
    environment:
      POSTGRES_DB: paperless
      POSTGRES_USER: paperless
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}  # Aus .env Datei
      TZ: Europe/Berlin                                              # ← Zeitzone anpassen bei neuer Instanz
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U paperless"]  # Prüft ob Datenbank bereit ist
      interval: 10s
      timeout: 5s
      retries: 5


  # ============================================================
  # 3. WEBSERVER — Paperless-ngx (Hauptanwendung)
  #    Die eigentliche Paperless-Anwendung mit Web-UI, API,
  #    OCR-Verarbeitung und Dokumentenverwaltung.
  #    Erreichbar unter: http://HOST-IP:8090
  # ============================================================
  webserver:
    container_name: paperless
    image: ghcr.io/paperless-ngx/paperless-ngx:2.20.11   # ← Version bei Update anpassen
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy           # Startet erst wenn Datenbank healthy ist
      broker:
        condition: service_healthy           # Startet erst wenn Broker healthy ist
    networks:
      - paperless_network
    ports:
      - 8090:8000                            # ← Host-Port links anpassen falls belegt
    volumes:
      - /volume1/docker/paperless/data:/usr/src/paperless/data       # ← Pfade anpassen bei neuer Instanz
      - /volume1/docker/paperless/media:/usr/src/paperless/media
      - /volume1/docker/paperless/export:/usr/src/paperless/export
      - /volume1/docker/paperless/consume:/usr/src/paperless/consume # Eingangsordner für automatische Verarbeitung
      - /usr/share/zoneinfo/Europe/Berlin:/etc/localtime:ro          # ← Zeitzone anpassen bei neuer Instanz
    environment:
      PAPERLESS_REDIS: redis://broker:6379           # Verbindung zum Broker (interner DNS-Name)
      PAPERLESS_DBHOST: db                           # Verbindung zur Datenbank (interner DNS-Name)
      PAPERLESS_GOTENBERG_ENDPOINT: http://gotenberg:3000
      PAPERLESS_DBPASS: ${PAPERLESS_DBPASS}          # Aus .env Datei
      USERMAP_UID: 1026                              # ← Anpassen bei neuer Instanz (id DEINUSERNAME)
      USERMAP_GID: 100                               # ← Anpassen bei neuer Instanz
      PAPERLESS_SECRET_KEY: ${PAPERLESS_SECRET_KEY}  # Aus .env Datei — niemals nach erstem Start ändern!
      TZ: Europe/Berlin                                              # ← Zeitzone anpassen bei neuer Instanz
      PAPERLESS_TIME_ZONE: Europe/Berlin                             # ← Zeitzone anpassen bei neuer Instanz
      PAPERLESS_OCR_LANGUAGE: deu+eng                # ← Sprachen anpassen bei neuer Instanz
      PAPERLESS_CONSUMER_RECURSIVE: "true"           # Unterordner im consume-Verzeichnis werden überwacht
      PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS: "true"     # Unterordner-Name wird automatisch als Tag gesetzt

      # ══════════════════════════════════════════════════════════════
      # DATEINAMENS-FORMAT VARIANTEN — Übersicht & Vergleich
      # Sortiert von einfach → komplex
      # Aktivieren: Gewünschte Zeile einkommentieren, andere auskommentieren
      # Danach Stack neu starten + Renamer ausführen:
      # docker exec -it paperless python3 manage.py document_renamer
      # ══════════════════════════════════════════════════════════════
      #
      # ──────────────────────────────────────────────────────────────
      # OPTION A — Besitzer/Korrespondent/Jahr/Datum - Titel (einfachste)
      # Empfohlen für die meisten Nutzer — simpel & ohne Paperless navigierbar
      # ──────────────────────────────────────────────────────────────
      # Besitzer/
      # └── Korrespondent/
      #     └── Jahr/
      #         └── Datum - Titel.pdf
      #
      #PAPERLESS_FILENAME_FORMAT: "{% if owner_username %}{{ owner_username }}{% else %}gemeinsam{% endif %}/{% if correspondent %}{{ correspondent }}{% else %}Allgemein{% endif %}/{{ created_year }}/{{ created }} - {{ title }}"
      #
      # ──────────────────────────────────────────────────────────────
      # OPTION B — Besitzer/Korrespondent/Dokumententyp/Datum - Titel
      # ──────────────────────────────────────────────────────────────
      # Besitzer/
      # └── Korrespondent/
      #     └── Dokumententyp/
      #         └── Datum - Titel.pdf
      #
      #PAPERLESS_FILENAME_FORMAT: "{% if owner_username %}{{ owner_username }}{% else %}gemeinsam{% endif %}/{% if correspondent %}{{ correspondent }}{% else %}Allgemein{% endif %}/{% if document_type %}{{ document_type }}{% else %}Allgemein{% endif %}/{{ created }} - {{ title }}"
      #
      # ──────────────────────────────────────────────────────────────
      # OPTION C — Besitzer/Korrespondent/Dokumententyp/Jahr/Datum - Titel
      # ──────────────────────────────────────────────────────────────
      # Besitzer/
      # └── Korrespondent/
      #     └── Dokumententyp/
      #         └── Jahr/
      #             └── Datum - Titel.pdf
      #
      #PAPERLESS_FILENAME_FORMAT: "{% if owner_username %}{{ owner_username }}{% else %}gemeinsam{% endif %}/{% if correspondent %}{{ correspondent }}{% else %}Allgemein{% endif %}/{% if document_type %}{{ document_type }}{% else %}Allgemein{% endif %}/{{ created_year }}/{{ created }} - {{ title }}"
      #
      # ──────────────────────────────────────────────────────────────
      # OPTION D — Besitzer/Thema/Korrespondent/Jahr/Datum - Typ - Titel (AKTIV)
      # WICHTIG: tag_name_list verwenden — NICHT tags (tags = QuerySet, kein String!)
      # Neuen Tag hinzufügen: In Liste unten eintragen + elif Zeile im Format ergänzen
      # ──────────────────────────────────────────────────────────────
      # Besitzer/
      # └── Thema/                    ← Tag als Themenordner
      #     └── Korrespondent/
      #         └── Jahr/
      #             └── Datum - Dokumententyp - Titel.pdf
      #
      # Verfügbare Themen-Tags (Reihenfolge = Priorität):
      # 01. Dokumente    — Urkunden, Ausweise, Pässe
      # 02. Gemeinde     — Behörden, Ämter, Kommune
      # 03. Steuer       — Steuererklärung, Bescheide, Finanzamt
      # 04. Recht        — Verträge, Anwalt, Gerichtspost
      # 05. Finanzen     — Bank, Kontoauszüge, Investments
      # 06. Versicherung — Policen, Rechnungen, Schäden
      # 07. Gesundheit   — Arzt, Krankenhaus, Rezepte
      # 08. Arbeit       — Arbeitsvertrag, Gehaltsabrechnungen
      # 09. IT           — Software, Lizenzen, digitale Dienste
      # 10. Geräte       — Hardware, Garantien, Kaufbelege
      # 11. Fuhrpark     — Auto, Motorrad, Kfz-Dokumente
      # 12. Wohnen       — Miete, Mietvertrag, Hausverwaltung
      # 13. Nebenkosten  — Strom, Gas, Wasser, Heizung
      # 14. Handwerk     — Reparaturen, Handwerkerrechnungen
      # 15. Garten       — Gartengeräte, Pflanzen, Dienstleister
      # 16. Reise        — Urlaub, Buchungen, Tickets
      # 17. Sport        — Mitgliedschaften, Ausrüstung
      # 18. Spenden      — Spendenquittungen (steuerlich relevant!)
      # 19. Kita         — Kindergarten, Betreuung, Gebühren
      # 20. Schule       — Zeugnisse, Schulbriefe, Anmeldungen
      # 21. Bildung      — Kurse, Zertifikate, Weiterbildung
      # 22. Bücher       — Rechnungen, Bestellungen, Lizenzen
      # 23. Studium      — Uni, Hochschule, BAföG
      # 24. Pflege       — Pflegegrad, Pflegekasse, Pflegedienst
      # 25. Rente        — Rentenbescheide, Rentenversicherung
      # 26. Abos         — Zeitschriften, Streaming, Mitgliedschaften
      # 27. Haushalt     — Einkauf, Einrichtung, Haushaltsgeräte
      # --  Allgemein    — Fallback wenn kein Themen-Tag gesetzt
      #
      # Beispiele:
      # Besitzer/Dokumente/Korrespondent/Jahr/Datum - Urkunde - Titel.pdf
      # Besitzer/Versicherung/Korrespondent/Jahr/Datum - Rechnung - Titel.pdf
      # Besitzer/Steuer/Korrespondent/Jahr/Datum - Bescheid - Titel.pdf
      # gemeinsam/Allgemein/Allgemein/Jahr/Datum - Titel.pdf
      #
      PAPERLESS_FILENAME_FORMAT: "{% if owner_username %}{{ owner_username }}{% else %}gemeinsam{% endif %}/{% if 'Dokumente' in tag_name_list %}Dokumente{% elif 'Gemeinde' in tag_name_list %}Gemeinde{% elif 'Steuer' in tag_name_list %}Steuer{% elif 'Recht' in tag_name_list %}Recht{% elif 'Finanzen' in tag_name_list %}Finanzen{% elif 'Versicherung' in tag_name_list %}Versicherung{% elif 'Gesundheit' in tag_name_list %}Gesundheit{% elif 'Arbeit' in tag_name_list %}Arbeit{% elif 'IT' in tag_name_list %}IT{% elif 'Geräte' in tag_name_list %}Geräte{% elif 'Fuhrpark' in tag_name_list %}Fuhrpark{% elif 'Wohnen' in tag_name_list %}Wohnen{% elif 'Nebenkosten' in tag_name_list %}Nebenkosten{% elif 'Handwerk' in tag_name_list %}Handwerk{% elif 'Garten' in tag_name_list %}Garten{% elif 'Reise' in tag_name_list %}Reise{% elif 'Sport' in tag_name_list %}Sport{% elif 'Spenden' in tag_name_list %}Spenden{% elif 'Kita' in tag_name_list %}Kita{% elif 'Schule' in tag_name_list %}Schule{% elif 'Bildung' in tag_name_list %}Bildung{% elif 'Bücher' in tag_name_list %}Bücher{% elif 'Studium' in tag_name_list %}Studium{% elif 'Pflege' in tag_name_list %}Pflege{% elif 'Rente' in tag_name_list %}Rente{% elif 'Abos' in tag_name_list %}Abos{% elif 'Haushalt' in tag_name_list %}Haushalt{% else %}Allgemein{% endif %}/{% if correspondent %}{{ correspondent }}{% else %}Allgemein{% endif %}/{{ created_year }}/{{ created }} - {% if document_type %}{{ document_type }} - {% endif %}{{ title }}"

      PAPERLESS_URL: ""                              # ← Bei Reverse Proxy: eigene URL eintragen
      PAPERLESS_ALLOWED_HOSTS: "localhost,192.168.76.20,100.70.109.58"  # ← Eigene IPs eintragen bei neuer Instanz
      PAPERLESS_TIKA_ENABLED: 1                      # Tika für Office-Dokumente (Word, Excel etc.)
      PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000/
      PAPERLESS_TIKA_ENDPOINT: http://tika:9998


  # ============================================================
  # 4. GOTENBERG — PDF-Konvertierung
  #    Konvertiert Word, Excel, HTML etc. in durchsuchbare PDFs
  # ============================================================
  gotenberg:
    container_name: paperless-gotenberg
    image: gotenberg/gotenberg:8
    restart: unless-stopped
    ports:
      - "3005:3000"                          # ← Host-Port links anpassen falls belegt
    networks:
      - paperless_network
    volumes:
      - /usr/share/zoneinfo/Europe/Berlin:/etc/localtime:ro          # ← Zeitzone anpassen bei neuer Instanz
    environment:
      CHROMIUM_DISABLE_ROUTES: 1
      TZ: Europe/Berlin                                              # ← Zeitzone anpassen bei neuer Instanz
    command:
      - "gotenberg"
      - "--chromium-disable-javascript=true"
      - "--chromium-allow-list=file:///tmp/.*"


  # ============================================================
  # 5. TIKA — Office Dokumenten-Analyse
  #    Extrahiert Text und Metadaten aus Word, Excel, PowerPoint
  # ============================================================
  tika:
    container_name: paperless-tika
    image: apache/tika:3.2.3.0
    restart: unless-stopped
    networks:
      - paperless_network
    volumes:
      - /usr/share/zoneinfo/Europe/Berlin:/etc/localtime:ro          # ← Zeitzone anpassen bei neuer Instanz
    environment:
      - TZ=Europe/Berlin                                             # ← Zeitzone anpassen bei neuer Instanz
      - OMP_NUM_THREADS=1                    # Begrenzt CPU-Threads — wichtig auf NAS
      - _JAVA_OPTIONS=-Duser.timezone=Europe/Berlin                  # ← Zeitzone anpassen bei neuer Instanz


  # ============================================================
  # 6. OFELIA — Cron-Job Scheduler
  #    Überwacht Docker-Labels und führt geplante Aufgaben aus
  # ============================================================
  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
      - /usr/share/zoneinfo/Europe/Berlin:/etc/localtime:ro          # ← Zeitzone anpassen bei neuer Instanz
    environment:
      TZ: Europe/Berlin                                              # ← Zeitzone anpassen bei neuer Instanz
    depends_on:
      - backup-runner
    networks:
      - paperless_network


  # ============================================================
  # 7. BACKUP-RUNNER — Automatisches Backup
  #    Führt täglich um 20:10 Uhr das Backup-Skript aus
  #
  #    EINRICHTUNG — SCHRITT FÜR SCHRITT:
  #
  #    A) BACKUP-SKRIPT ERSTELLEN:
  #       1. DSM → File Station → /volume1/docker/paperless/scripts/
  #       2. Neue Datei erstellen: backup.sh
  #       3. Inhalt: siehe beigelegte backup.sh Datei
  #       4. Ausführbar machen (SSH):
  #          chmod +x /volume1/docker/paperless/scripts/backup.sh
  #
  #    B) BACKUP-ZIELORDNER ERSTELLEN (SSH):
  #       mkdir -p /volume1/docker/paperless/backups
  #
  #    C) BACKUP MANUELL TESTEN (SSH):
  #       docker exec paperless-backup-runner /bin/bash /scripts/backup.sh
  #       → Prüfen ob Ordner in /volume1/docker/paperless/backups/ erstellt wurde
  #       → Prüfen ob paperless-db.sql vorhanden und nicht leer ist
  #       → Prüfen ob documents/ Ordner gefüllt ist
  #
  #    D) LOGS PRÜFEN:
  #       cat /volume1/docker/paperless/backups/backup.log
  #
  #    BACKUP-STRUKTUR:
  #       /volume1/docker/paperless/backups/
  #       ├── 2024-03-15_20-10-00/
  #       │   ├── paperless-db.sql     ← Datenbank-Dump
  #       │   ├── restore_info.txt     ← Versionsinfos für Restore
  #       │   └── documents/           ← Alle Dokumente + Manifeste
  #       ├── 2024-03-16_20-10-00/
  #       │   └── ...
  #       ├── latest -> 2024-03-16_20-10-00  ← Symlink auf neuestes Backup
  #       └── backup.log               ← Alle Backup-Logs
  #
  #    ⚠️  Backups älter als 30 Tage werden automatisch gelöscht!
  #    ⚠️  Backup liegt auf gleicher Disk wie Daten — externes Backup empfohlen!
  # ============================================================
  backup-runner:
    image: alpine:3.15
    container_name: paperless-backup-runner
    command: >
      sh -c "apk add --no-cache bash rsync docker-cli tzdata && tail -f /dev/null"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /volume1/docker/paperless/scripts/backup.sh:/scripts/backup.sh:ro  # ← Pfad anpassen bei neuer Instanz
      - /volume1/docker/paperless/export:/paperless_data/export:ro
      - /volume1/docker/paperless/backups:/backup                           # ← Backup-Ziel anpassen bei neuer Instanz
      - /usr/share/zoneinfo/Europe/Berlin:/etc/localtime:ro                 # ← Zeitzone anpassen bei neuer Instanz
    environment:
      - TZ=Europe/Berlin                                                     # ← Zeitzone anpassen bei neuer Instanz
    labels:
      ofelia.enabled: "true"
      ofelia.job-exec.paperless-backup.schedule: "0 10 20 * * *"            # ← Uhrzeit anpassen bei Bedarf
      ofelia.job-exec.paperless-backup.command: "/bin/bash /scripts/backup.sh"
    networks:
      - paperless_network


  # ============================================================
  # 8. REDIS-EXPORTER — Metriken für Prometheus
  #    Liest Valkey/Redis-Metriken aus für Grafana-Dashboard
  # ============================================================
  redis-exporter:
    container_name: paperless-redis-exporter
    image: oliver006/redis_exporter:v1.81.0
    restart: unless-stopped
    networks:
      - paperless_network
    volumes:
      - /usr/share/zoneinfo/Europe/Berlin:/etc/localtime:ro          # ← Zeitzone anpassen bei neuer Instanz
    environment:
      - REDIS_ADDR=redis://broker:6379
      - TZ=Europe/Berlin                                             # ← Zeitzone anpassen bei neuer Instanz


  # ============================================================
  # 9. GRAFANA — Dashboard & Visualisierung
  #    Zeigt Prometheus-Metriken grafisch an
  #    Erreichbar unter: http://HOST-IP:3001
  #
  #    EINRICHTUNG — SCHRITT FÜR SCHRITT:
  #
  #    A) PROMETHEUS ALS DATENQUELLE HINZUFÜGEN:
  #       1. Grafana öffnen: http://DEINE-IP:3001
  #       2. Links im Menü: Connections → Data sources
  #       3. Oben rechts: "Add new data source"
  #       4. "Prometheus" auswählen
  #       5. URL eintragen: http://paperless-prometheus:9090
  #       6. Ganz unten: "Save & test" klicken
  #          → Grüne Meldung "Successfully queried" = alles korrekt!
  #
  #    B) REDIS DASHBOARD IMPORTIEREN:
  #       1. Links im Menü: Dashboards → New → Import
  #       2. Bei "Import via grafana.com" eingeben: 763
  #       3. "Load" klicken
  #       4. Bei "Prometheus" die soeben angelegte Datenquelle auswählen
  #       5. "Import" klicken
  #          → Dashboard öffnet sich automatisch
  #
  #    C) ALS HOME DASHBOARD SETZEN:
  #       1. Das importierte Dashboard öffnen
  #       2. Oben rechts auf das Stern-Symbol klicken (als Favorit markieren)
  #       3. Oben rechts: Eigenes Profil-Icon → Profile
  #       4. Unter "Preferences" → "Home Dashboard"
  #       5. Das Redis-Dashboard auswählen → "Save" klicken
  #          → Ab jetzt öffnet Grafana direkt dieses Dashboard
  # ============================================================
  grafana:
    container_name: paperless-grafana
    image: grafana/grafana:12.4
    restart: unless-stopped
    networks:
      - paperless_network
    ports:
      - "3001:3000"                          # ← Host-Port links anpassen falls belegt
    volumes:
      - /volume1/docker/paperless/grafana:/var/lib/grafana           # ← Pfad anpassen bei neuer Instanz
      - /usr/share/zoneinfo/Europe/Berlin:/etc/localtime:ro          # ← Zeitzone anpassen bei neuer Instanz
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=${GF_SECURITY_ADMIN_PASSWORD}     # Aus .env Datei
      - TZ=Europe/Berlin                                             # ← Zeitzone anpassen bei neuer Instanz


  # ============================================================
  # 10. PROMETHEUS — Metriken-Sammler
  #     Sammelt Daten vom Redis-Exporter, Grafana liest von hier
  #     Konfiguration: /volume1/docker/paperless/prometheus/prometheus.yml
  #     (siehe SCHRITT 3 oben!)
  # ============================================================
  prometheus:
    container_name: paperless-prometheus
    image: prom/prometheus:v3.10.0
    restart: unless-stopped
    networks:
      - paperless_network
    volumes:
      - /volume1/docker/paperless/prometheus:/etc/prometheus                   # ← Pfad anpassen bei neuer Instanz
      - /usr/share/zoneinfo/Europe/Berlin:/etc/localtime:ro                    # ← Zeitzone anpassen bei neuer Instanz
      - /usr/share/zoneinfo/Europe/Berlin:/usr/share/zoneinfo/Europe/Berlin:ro # Für Go-Anwendungen nötig
    environment:
      - TZ=Europe/Berlin                                                        # ← Zeitzone anpassen bei neuer Instanz
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.retention.time=15d'  # Metriken 15 Tage aufbewahren

10 Nützliche Befehle

Stack neu starten:

cd /volume1/docker/paperless && docker compose down && docker compose up -d

Status aller Container:

docker compose ps

Logs anzeigen:

docker compose logs -f paperless

Dateien nach Format-Änderung umbenennen:

docker exec -it paperless python3 manage.py document_renamer

Admin-User anlegen:

docker exec -it paperless python3 manage.py createsuperuser

Backup manuell starten:

docker exec paperless-backup-runner /bin/bash /scripts/backup.sh

Backup Log prüfen:

cat /volume1/docker/paperless/backups/backup.log

Ofelia Logs (Backup-Scheduler):

docker logs paperless-ofelia

Grafana Berechtigung nachträglich setzen:

chown -R 472:472 /volume1/docker/paperless/grafana && chmod -R 775 /volume1/docker/paperless/grafana