Produktionsbetrieb
Dieses Dokument beschreibt, wie Katalon auf einem Linux-Server in Produktion betrieben wird.
Empfohlener Weg für neue Instanzen: katalon-cli (uv tool install katalon-cli) installiert und aktualisiert Produktionsinstanzen über gepinnte Release-Images, ohne Repository-Checkout — siehe katalon-cli Repository. Der manuelle Weg unten (Repository klonen, Compose-Dateien selbst pflegen) bleibt für Sonderfälle und zum Verständnis der zugrundeliegenden Compose-Topologie relevant, wird aber nicht mehr als primärer Installationsweg empfohlen.
Verwaltungswerkzeug katalon-manage
Abschnitt betitelt „Verwaltungswerkzeug katalon-manage“Für Wartungsaufgaben im API-Container steht katalon-manage bereit:
docker compose exec api katalon-manage --helpBestandsdaten zurücksetzen
Abschnitt betitelt „Bestandsdaten zurücksetzen“katalon-manage db-reset löscht Bestandsdaten und legt vorher standardmäßig ein
PostgreSQL-Backup an. Ohne --all bleiben Konfiguration, Benutzerkonten, Schemata,
Vokabulare sowie Rollen- und Funktionsrechte erhalten. --all löscht auch diese
Konfiguration und ist nur für eine bewusst vollständig zurückzusetzende Instanz geeignet.
Optionen: --backup-dir PFAD wählt das Sicherungsverzeichnis, --no-backup überspringt
die Sicherung und --yes unterdrückt die interaktive Bestätigung.
CSV importieren
Abschnitt betitelt „CSV importieren“katalon-manage import-csv DATEI --type TYP --mapping MAPPING.json importiert Objekte,
Entitäten, Orte oder Occurrences anhand eines JSON-Mappings. --dry-run validiert und
zeigt die Vorschau, ohne Daten zu ändern. Optional steuern --subtype, --idno-strategy
(auto, column, skip), --upsert-strategy (skip, merge, replace) und
--auto-publish den Import; --media-selector ordnet beim Objektimport Dateinamen zu.
XML importieren
Abschnitt betitelt „XML importieren“katalon-manage import-xml DATEI --type TYP --mapping MAPPING.json --record-xpath XPATH
importiert dieselben Datensatztypen aus XML. --record-xpath wählt die einzelnen
Datensätze aus; für XML-Namensräume verwendet der Ausdruck die Clark-Notation. Die
Optionen entsprechen dem CSV-Import; --media-selector erwartet hier einen XPath.
Administrator-Passwort zurücksetzen
Abschnitt betitelt „Administrator-Passwort zurücksetzen“katalon-manage reset-admin erstellt interaktiv ein neues zufälliges Passwort für ein
Admin- oder Superuser-Konto. Bei mehreren solchen Konten wird das Zielkonto abgefragt.
Das Passwort wird nur ausgegeben und zusätzlich mit Dateirechten 0600 unter
/var/lib/katalon/reset-credentials.txt abgelegt. Wie jedes Zugangsdatum muss es
anschließend sicher behandelt und nach der Anmeldung geändert werden.
[~/Coding/Katalon Collections/katalon-docs/src/content/docs/en/administration/production.md#D648]
The katalon-manage administration tool
Abschnitt betitelt „The katalon-manage administration tool“katalon-manage is available inside the API container for maintenance tasks. Show its
full help with:
docker compose exec api katalon-manage --helpkatalon-manage db-reset deletes collection data and creates a PostgreSQL backup by
default. Without --all, it retains configuration, user accounts, schemas,
vocabularies, and role and feature permissions. --all also deletes that configuration
and is only suitable when intentionally resetting an entire instance.
Voraussetzungen
Abschnitt betitelt „Voraussetzungen“- Linux-Server (Debian/Ubuntu empfohlen), min. 4 GB RAM, 20 GB Disk
- Docker ≥ 24 und Docker Compose v2 installiert
- Öffentliche IP-Adresse, DNS-Einträge für deine Domains gesetzt
- TLS-Zertifikate (Let’s Encrypt empfohlen)
Checkliste vor dem ersten Produktionsstart
Abschnitt betitelt „Checkliste vor dem ersten Produktionsstart“- Domainname(n) entschieden und DNS-Einträge gesetzt
- URL-Layout gewählt (Subdomain oder Subpfad, → Abschnitt 4)
- TLS-Zertifikate ausgestellt
-
.envvollständig ausgefüllt — insbesondereSECRET_KEY, Datenbankpasswort,KATALON_BASE_URL,CORS_ORIGINS -
MEDIA_ROOT-Host-Verzeichnis existiert und gehört UID/GID1000(install -d -o 1000 -g 1000 -m 755 /srv/katalon/media, nichtmkdir -p) —api- undworker-Container laufen als nicht-root Userapp(UID 1000). Fehlt das, schlagen Uploads still mitPermission deniedfehl, ohne Health-Check-Alarm — siehe Abschnitt “Medien-Upload schlägt fehl” unten. -
docker/nginx.prod.confauf eigene Domain(en) angepasst (enthält bereits/robots.txt//llms.txt-Routing für das Portal sowie ein statischesDisallow: /für die Admin-Subdomain) - Rate-Limits für öffentliche Endpunkte geprüft (
RATE_LIMIT_*, Defaults meist ausreichend) — siehe Zugriffsschutz für öffentliche Endpunkte unten -
.envVITE-Build-Argumente für Admin/Portal gesetzt - Wikidata-Adapter:
WIKIDATA_USER_AGENTsetzen oderKATALON_BASE_URL+OAI_ADMIN_EMAILvollständig pflegen (Wikidata-Policy erfordert identifizierbaren User-Agent) - Backup-Strategie eingerichtet (Cron für DB-Dump, Media-Volume gesichert)
- Automatische Zertifikatserneuerung (certbot-Cron) eingerichtet
- Für transaktionale E-Mails: SMTP-Relay, Absender-Domain und SPF/DKIM/DMARC eingerichtet
- Nach erstem Start:
alembic upgrade headausgeführt - Nach erstem Start: First-Run-
superuser-Passwort geändert
1. Repository klonen
Abschnitt betitelt „1. Repository klonen“git clone https://github.com/katalon-collections/katalon.gitcd Katalon2. Umgebungsvariablen konfigurieren
Abschnitt betitelt „2. Umgebungsvariablen konfigurieren“cp .env.example .envnano .envMindestens diese Werte anpassen:
| Variable | Beschreibung |
|---|---|
POSTGRES_PASSWORD |
Starkes Datenbankpasswort |
DATABASE_URL |
Muss dasselbe Passwort enthalten |
SECRET_KEY |
JWT-Schlüssel — generieren mit openssl rand -hex 32 |
KATALON_BASE_URL |
Öffentliche Basis-URL der Instanz (z.B. https://katalon.example.org) |
FIRST_RUN_CREDENTIALS_PATH |
Pfad im API-Container für die einmalig erzeugte Credentials-Datei (bei Bedarf auf ein persistentes Volume legen) |
DEFAULT_ADMIN_EMAIL |
Fallback-E-Mail für lokale Entwicklung ohne KATALON_BASE_URL |
DEFAULT_ADMIN_PASSWORD |
Fallback-Passwort für lokale Entwicklung ohne KATALON_BASE_URL |
CORS_ORIGINS |
Komma-separierte Liste erlaubter Frontends |
OAI_ADMIN_EMAIL |
Erscheint im OAI-PMH Identify-Response |
WIKIDATA_USER_AGENT |
Optionaler User-Agent für Wikidata. Leer = automatisch aus KATALON_BASE_URL + OAI_ADMIN_EMAIL. |
SMTP_* |
Optionaler externer SMTP-Relay für transaktionale E-Mails. Passwort nur als Betreiber-Secret setzen. |
RATE_LIMIT_* |
Rate-Limits für öffentliche Endpunkte (Portal-Suche, OAI-PMH, JSON-LD/Turtle-Export, Authority-Proxy, globaler Default) — siehe Zugriffsschutz für öffentliche Endpunkte unten. |
ROBOTS_DISALLOW_PATHS / LLMS_TXT_* |
Steuerung von /robots.txt und /llms.txt — siehe unten. |
ARKs einrichten
Abschnitt betitelt „ARKs einrichten“ARKs werden in Katalon lokal geprägt, aber erst über einen eigenen, bei der ARK Alliance registrierten NAAN weltweit auflösbar. Vor der Aktivierung:
- Einen dauerhaften öffentlichen Domainnamen und
KATALON_BASE_URLfestlegen. - Einen NAAN über das NAAN-Antragsformular der ARK Alliance beantragen.
- Im NAAN-Register den lokalen Resolver
https://katalog.example.org/ark:/<NAAN>/hinterlegen. N2T leitet dann vollständige ARKs an die Instanz weiter.
Danach in .env setzen:
ARK_ENABLED=trueARK_NAAN=12345ARK_RESOLVER_URL=https://n2t.net/ARK_SUFFIX_LENGTH=10Katalon beantwortet https://katalog.example.org/ark:/12345/<Suffix> mit
einem Redirect auf die aktuelle öffentliche Portal-Detailseite. ARK_ENABLED
schaltet nur neue Vergaben ab; die Auflösung bereits vergebener ARKs bleibt
aktiv. Der Test-NAAN 99999 ist nicht für Produktionsdaten geeignet und wird
nicht aufgelöst.
SMTP für transaktionale E-Mails
Abschnitt betitelt „SMTP für transaktionale E-Mails“Katalon betreibt keinen eigenen Mailserver. Für Passwort-Reset und spätere Benachrichtigungen wird ein externer SMTP-Relay verwendet. Versand bleibt mit SMTP_ENABLED=false deaktiviert, bis Relay und Absender vollständig konfiguriert sind.
SMTP_ENABLED=trueSMTP_HOST=smtp.example.orgSMTP_PORT=587SMTP_USERNAME=noreply@example.orgSMTP_PASSWORD=BETREIBER_SECRETSMTP_FROM=Katalon <noreply@example.org>SMTP_STARTTLS=trueSMTP_SSL_TLS=falseKATALON_BASE_URL=https://katalon.example.orgPort 587 verwendet üblicherweise STARTTLS. Für implizites TLS (meist Port 465) SMTP_STARTTLS=false und SMTP_SSL_TLS=true setzen. Bei aktiviertem SMTP muss genau ein TLS-Modus aktiv sein und KATALON_BASE_URL gesetzt sein, damit sichere Reset-Links erzeugt werden können. Die Absender-Domain benötigt SPF, DKIM und DMARC; außerdem muss der Server den SMTP-Host erreichen können. SMTP_PASSWORD gehört ausschließlich in die nicht versionierte Produktionsumgebung oder die Secret-Verwaltung.
Instanzspezifische Docker-Compose-Anpassungen
Abschnitt betitelt „Instanzspezifische Docker-Compose-Anpassungen“Wenn du eigene Ports, Volume-Pfade oder zusätzliche Umgebungsvariablen brauchst, ändere dafür nicht die zentrale docker-compose.yml.
Stattdessen:
cp docker-compose.override.yml.example docker-compose.override.ymldocker-compose.override.yml wird automatisch von Docker Compose mitgeladen und bleibt bei Updates unangetastet.
3. TLS-Zertifikate einrichten
Abschnitt betitelt „3. TLS-Zertifikate einrichten“Option A: Let’s Encrypt mit certbot (empfohlen)
Abschnitt betitelt „Option A: Let’s Encrypt mit certbot (empfohlen)“# certbot installieren (Debian/Ubuntu)apt install certbot
# Zertifikat ausstellen (DNS muss auf den Server zeigen)certbot certonly --standalone -d example.org -d admin.example.org
# Zertifikate in den Docker-Pfad kopierenmkdir -p docker/certscp /etc/letsencrypt/live/example.org/fullchain.pem docker/certs/cp /etc/letsencrypt/live/example.org/privkey.pem docker/certs/chmod 644 docker/certs/*.pemAutomatische Erneuerung (crontab):
0 3 * * * certbot renew --quiet && cp /etc/letsencrypt/live/example.org/fullchain.pem /pfad/zu/katalon/docker/certs/ && cp /etc/letsencrypt/live/example.org/privkey.pem /pfad/zu/katalon/docker/certs/ && docker compose -f docker-compose.yml -f docker-compose.prod.yml exec nginx nginx -s reloadOption B: Eigenes Zertifikat
Abschnitt betitelt „Option B: Eigenes Zertifikat“Lege fullchain.pem und privkey.pem in docker/certs/.
Option C: TLS wird extern terminiert (z. B. Traefik, nginx-proxy)
Abschnitt betitelt „Option C: TLS wird extern terminiert (z. B. Traefik, nginx-proxy)“Läuft vor dem Stack bereits ein Reverse Proxy, der TLS terminiert (Traefik-Label-Setup,
externer nginx, …), braucht der nginx-Service selbst kein TLS mehr — er wird dann
nur noch intern per Docker-Netzwerk auf Port 80 angesprochen, Port 443 wird meist gar
nicht mehr veröffentlicht.
docker/nginx.conf und docker/nginx.prod.conf enthalten beide einen listen 443 ssl-Block,
der beim Start ladbare Zertifikate unter docker/certs/ voraussetzt — auch wenn Port 443
nie extern erreichbar ist (cannot load certificate ... BIO_new_file() failed sonst).
Ein Self-signed-Zertifikat nur zum Booten in Produktion zu erzeugen ist kein guter Fix
(make certs ist explizit für lokale Entwicklung gedacht, nicht für Prod-Container).
Sauberer: eine eigene, schlanke nginx-Config ohne den 443-Block einbinden (identisch
zu docker/nginx.conf, nur listen 443 ssl + die beiden ssl_certificate*-Zeilen
entfernt) und im jeweiligen Compose-Overlay statt docker/nginx.conf mounten. Diese
Reverse-Proxy-spezifische Config ist Teil der Instanz-Konfiguration, nicht des Repos —
sie gehört (wie ein eigenes docker-compose.traefik.yml-Overlay) lokal zur Instanz.
4. URL-Layout wählen und nginx anpassen
Abschnitt betitelt „4. URL-Layout wählen und nginx anpassen“Es gibt zwei unterstützte Layouts. Einmal entscheiden, dann konsequent durchziehen.
Option A — Subdomain (Standard, empfohlen)
Abschnitt betitelt „Option A — Subdomain (Standard, empfohlen)“https://meineurl.de → Public-Portalhttps://admin.meineurl.de → Admin-UIdocker/nginx.prod.conf ist für dieses Layout vorbereitet. Domains ersetzen:
sed -i 's/example\.org/meineurl.de/g; s/admin\.example\.org/admin.meineurl.de/g' docker/nginx.prod.confTLS-Zertifikate für beide Domains ausstellen:
certbot certonly --standalone -d meineurl.de -d admin.meineurl.deFrontend-Build-Argumente in .env setzen (werden über docker-compose.prod.yml an die
Build-Stages weitergereicht):
ADMIN_VITE_API_URL=https://admin.meineurl.dePORTAL_URL=https://meineurl.dePORTAL_VITE_API_URL=https://meineurl.deOption B — Subpfad (eine Domain, zwei Pfade)
Abschnitt betitelt „Option B — Subpfad (eine Domain, zwei Pfade)“https://meineurl.de → Public-Portalhttps://meineurl.de/cataloging → Admin-UIVite muss die Asset-Pfade beim Build einbetten. Dafür in frontend/admin/vite.config.ts ergänzen:
export default defineConfig({ base: '/cataloging/', // ← neu // … Rest unverändert})docker/nginx.prod.conf — den separaten server-Block für admin.example.org ersetzen durch einen location-Block im Portal-Server:
# Im Portal-Server-Block ergänzen (nach dem /v1/-Block):location /cataloging/ { proxy_pass http://admin/; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme;}Den server-Block für admin.example.org sowie den zugehörigen HTTP-Redirect-Eintrag komplett entfernen.
Nur ein Zertifikat nötig:
certbot certonly --standalone -d meineurl.deFrontend-Build-Argumente in .env:
ADMIN_VITE_API_URL=https://meineurl.dePORTAL_URL=https://meineurl.dePORTAL_VITE_API_URL=https://meineurl.deTLS-Volume in docker-compose.prod.yml (beide Optionen)
Abschnitt betitelt „TLS-Volume in docker-compose.prod.yml (beide Optionen)“nginx: volumes: - ./docker/nginx.prod.conf:/etc/nginx/conf.d/default.conf:ro - ./docker/certs:/etc/nginx/certs:ro ports: - "80:80" - "443:443"docker/nginx.prod.conf erwartet fullchain.pem und privkey.pem direkt unter
/etc/nginx/certs/ (also docker/certs/fullchain.pem und docker/certs/privkey.pem).
6. Images bauen und starten
Abschnitt betitelt „6. Images bauen und starten“docker compose -f docker-compose.yml -f docker-compose.prod.yml builddocker compose -f docker-compose.yml -f docker-compose.prod.yml up -dLogs beobachten:
docker compose logs -f api worker7. Datenbank-Migrationen ausführen
Abschnitt betitelt „7. Datenbank-Migrationen ausführen“Beim ersten Start führt der API-Container automatisch keine Migrationen aus — das muss manuell angestoßen werden:
docker compose exec api alembic upgrade headNach jedem Update ebenfalls ausführen.
8. Elasticsearch-Index initialisieren
Abschnitt betitelt „8. Elasticsearch-Index initialisieren“Nach dem ersten Start den Index und die Mappings aufbauen:
# Index anlegen (passiert automatisch beim ersten API-Start via ensure_index())# Alle bestehenden Datensätze indexieren:curl -X POST https://deine-domain.de/v1/search/reindexNach dem Vorgänge-Update (0.2.x) muss für bestehende Daten mindestens der Objektindex neu aufgebaut werden, weil collection_status in die öffentlichen Suchfilter aufgenommen wurde:
curl -X POST https://deine-domain.de/v1/search/reindex/object9. Erster Login
Abschnitt betitelt „9. Erster Login“Beim ersten API-Start ohne vorhandenen Admin/Superuser erzeugt Katalon automatisch:
- E-Mail:
admin@<domain-aus-KATALON_BASE_URL> - Passwort: kryptografisch zufällig (einmalig)
Die Zugangsdaten werden im API-Log mit dem Block ====== KATALON FIRST RUN ====== ausgegeben und zusätzlich in ./first-run-credentials.txt abgelegt (über install.sh via docker cp).
- Browser:
https://admin.deine-domain.de - Login mit den First-Run-Zugangsdaten
- Sofort das Passwort ändern (Admin → Benutzer → eigenes Konto)
Zugriffsschutz für öffentliche Endpunkte
Abschnitt betitelt „Zugriffsschutz für öffentliche Endpunkte“Portal-Suche, OAI-PMH und die JSON-LD/Turtle-Export-Endpunkte sind bewusst ohne API-Key erreichbar — jeder Datensatz mit Status public ist darüber offen abrufbar (siehe REST API: Authentifizierung). Schutz vor Massenzugriff/Scraping läuft deshalb über Rate-Limiting und Crawler-Konventionen, nicht über Zugriffsbeschränkung.
Rate-Limits
Abschnitt betitelt „Rate-Limits“Alle über .env konfigurierbar (slowapi-Syntax "N/unit", z. B. 30/minute, 500/hour):
| Variable | Default | Betrifft |
|---|---|---|
RATE_LIMIT_DEFAULT |
200/minute |
Globaler Fallback für alle Endpunkte ohne eigenes Limit |
RATE_LIMIT_PUBLIC_EXPORT |
30/minute |
JSON-LD/Turtle-Export je Datensatz (/{typ}/{id}/export) — der wahrscheinlichste Ziel-Endpunkt für Bulk-Scraping |
RATE_LIMIT_PUBLIC_SEARCH |
100/minute |
Portal-Suche (/portal/v1/search, /portal/v1/search/advanced) |
RATE_LIMIT_OAI |
100/minute |
OAI-PMH (/oai) |
RATE_LIMIT_AUTHORITY_PROXY |
60/minute |
Normdaten-Proxy (GND/Geonames, erfordert ohnehin einen eingeloggten Benutzer) |
RATE_LIMIT_SPARQL |
60/minute |
SPARQL-Endpoint (/sparql, Abfragen gegen den Triple Store) |
Login und Passwort-Reset haben eigene, fest codierte Brute-Force-Limits und sind nicht über .env steuerbar — anderes Bedrohungsmodell als Crawler-/Scraper-Traffic.
robots.txt & llms.txt
Abschnitt betitelt „robots.txt & llms.txt“Katalon stellt /robots.txt und /llms.txt auf der Portal-Domain bereit — beides sind reine Konventionen für wohlerzogene Bots/Agenten, kein technischer Zugriffsschutz:
curl https://deine-domain.de/robots.txtcurl https://deine-domain.de/llms.txtROBOTS_DISALLOW_PATHS— Pfade, die/robots.txtfür alle Bots sperrt (Default["/v1/"]; Portal-HTML-Seiten bleiben crawlbar).LLMS_TXT_ENABLED/LLMS_TXT_EXTRA_NOTES—/llms.txtweist LLM-Agenten explizit auf die JSON-LD/Turtle-Export-Endpunkte und OAI-PMH als strukturierte Datenquelle hin, statt HTML zu scrapen. Ab-/anschaltbar, mit optionalem Freitext-Zusatz.
docker/nginx.prod.conf routet beide Pfade auf der Portal-Domain zur API; die Admin-Subdomain bekommt stattdessen ein statisches Disallow: /, damit die Admin-UI nie indexiert wird. Bei einer eigenen nginx-Config diese Routen entsprechend nachziehen.
Custom-Betrieb ohne öffentliches Portal
Abschnitt betitelt „Custom-Betrieb ohne öffentliches Portal“Manche Institutionen wollen Katalon nur als interne Erfassungsoberfläche betreiben — ohne öffentliches Portal-Frontend, teils auch ohne öffentliche REST-API/OAI-PMH. Das ist kein eigenständig gepflegtes Installationsprofil, sondern ein Custom-Rezept auf Basis der Standard-Compose-/nginx-Dateien:
- Portal-Container weglassen — den
portal-Service in einem Compose-Override nicht starten (oder aus einer lokalen Kopie vondocker-compose.prod.ymlentfernen). - Öffentliche
location-Blöcke ausdocker/nginx.prod.confentfernen, je nachdem wie weit die Schließung gehen soll:/portal/v1/,/oai,/sparql,^/(objects|entities|...)(kanonische Record-URIs),/robots.txt,/llms.txt. Wer stattdessen eine eigene Konsumenten-Anwendung gegen die REST-API bauen will, lässt/portal/v1/bewusst offen und entfernt nur den Rest. - Admin-UI von den jetzt toten Links befreien — beim Admin-Build
VITE_PORTAL_ENABLED=falseals Build-Argument setzen. Blendet den Button „Im Portal ansehen“ sowie die angezeigte OAI-Endpunkt-URL in den Einstellungen aus. - IIIF (
cantaloupe) bleibt in jedem Fall Pflicht-Container — wird auch für interne Bildvorschauen bei der Erfassung gebraucht, unabhängig vom Portal.
Das ist bewusst kein per Installer abfragbares Profil und wird nicht als eigener
katalon-cli-Installationspfad angeboten — nur ein dokumentierter, manuell gepflegter
Ausgangspunkt für Selbst-Hoster mit entsprechendem Bedarf.
Optionale RDF-Projektion & SPARQL-Schnittstelle (Oxigraph)
Abschnitt betitelt „Optionale RDF-Projektion & SPARQL-Schnittstelle (Oxigraph)“Katalon enthält einen optionalen Triple Store (Oxigraph) für semantische Graphabfragen via SPARQL 1.1. Um den Standard-Betrieb ressourcenschonend zu halten, ist der Oxigraph-Dienst als Docker Compose-Profil rdf konzipiert und standardmäßig inaktiv (Zero Clutter).
1. In .env aktivieren
Abschnitt betitelt „1. In .env aktivieren“Füge folgende Variablen zu deiner .env-Datei hinzu:
# Docker Compose Profile aktivierenCOMPOSE_PROFILES=rdf
# Oxigraph Triple Store IntegrationOXIGRAPH_ENABLED=trueOXIGRAPH_URL=http://oxigraph:7878
# SPARQL 1.1 Protocol EndpointSPARQL_ENDPOINT_ENABLED=trueSPARQL_REQUIRE_AUTH=true # 'false' setzen für offenen, anonymen LesezugriffSPARQL_QUERY_TIMEOUT=30.0 # Maximaldauer einer SPARQL-Abfrage in SekundenSPARQL_MAX_QUERY_LENGTH=65536 # Maximale Query-Größe in Bytes (64 KB)RATE_LIMIT_SPARQL=60/minute # Ratenbegrenzung für SPARQL-Anfragen2. Container starten
Abschnitt betitelt „2. Container starten“Starte den Stack mit dem Profil rdf:
docker compose --profile rdf up -dDocker startet nun zusätzlich den Container oxigraph mit dem persistenten Volume oxigraph_data.
3. Named Graphs initial aufbauen (Rebuild)
Abschnitt betitelt „3. Named Graphs initial aufbauen (Rebuild)“Nach dem ersten Start ist der Triple Store noch leer. Die Daten werden nicht automatisch beim Booten, sondern über einen Celery-Task synchronisiert:
- Über die Admin-Oberfläche:
- Gehe zu Einstellungen (Zahnrad-Symbol).
- In der Sektion Linked Data & SPARQL siehst du den Status und den Triple-Zähler.
- Klicke auf „RDF-Index neu aufbauen“.
- Oder per API / curl:
Terminal-Fenster curl -X POST "https://admin.deine-domain.de/sparql/rebuild" \-H "Authorization: Bearer <dein-admin-token>"
Der Worker verarbeitet alle publizierten Datensätze im Hintergrund und erzeugt die entsprechenden Named Graphs (urn:katalon:graph:<type>:<uuid>).
Laufende Synchronisation
Abschnitt betitelt „Laufende Synchronisation“Sobald OXIGRAPH_ENABLED=true aktiv ist, synchronisiert Katalon bei jeder Veröffentlichung, Aktualisierung oder Löschung eines Datensatzes den zugehörigen Named Graph automatisch in Oxigraph. Nicht-öffentliche Entwürfe verbleiben isoliert in PostgreSQL.
Backup & Wiederherstellung
Abschnitt betitelt „Backup & Wiederherstellung“Da Oxigraph eine reine, abgeleitete Projektion aus PostgreSQL darstellt, kann der gesamte Triple Store im Katastrophenfall jederzeit verlustfrei über „RDF-Index neu aufbauen“ aus der Datenbank rekonstruiert werden. Ein separates Backup des Volumes oxigraph_data ist daher im Normalbetrieb nicht zwingend erforderlich.
Optionales S3-kompatibles Medien-Speicherbackend
Abschnitt betitelt „Optionales S3-kompatibles Medien-Speicherbackend“Standardmäßig liegen Mediendateien unter MEDIA_ROOT auf dem lokalen Dateisystem (unverändertes Verhalten, keine Aktion nötig). Alternativ lassen sich Mediendateien in einem S3-kompatiblen Objektspeicher ablegen — Ceph RADOSGW, MinIO, Hetzner Object Storage, Garage oder AWS S3. Strikt opt-in über STORAGE_BACKEND=s3; Logos und Portal-Themes bleiben unabhängig vom gewählten Backend immer lokal unter MEDIA_ROOT, nur die eigentlichen Objekt-Mediendateien wandern nach S3.
1. Bucket vorbereiten
Abschnitt betitelt „1. Bucket vorbereiten“Der Bucket muss vor der Aktivierung existieren; Katalon legt ihn nicht selbst an. Die konfigurierten Zugangsdaten benötigen Put-, Get- und Delete-Rechte auf dem Bucket.
2. In .env konfigurieren
Abschnitt betitelt „2. In .env konfigurieren“STORAGE_BACKEND=s3S3_ENDPOINT_URL=https://s3.example-provider.comS3_BUCKET=katalon-mediaS3_REGION=us-east-1S3_ACCESS_KEY=...S3_SECRET_KEY=...S3_FORCE_PATH_STYLE=trueS3_VERIFY_TLS=true#S3_CA_BUNDLE=/pfad/zu/ca-bundle.pem # nur bei privater CA nötig| Variable | Default | Bedeutung |
|---|---|---|
STORAGE_BACKEND |
local |
s3 aktiviert das Objektspeicher-Backend |
S3_ENDPOINT_URL |
leer | Leer = AWS-Default-Endpoint. Bei Ceph RADOSGW/MinIO/Hetzner/Garage die jeweilige Endpoint-URL setzen |
S3_BUCKET |
leer | Pflichtfeld bei STORAGE_BACKEND=s3 |
S3_REGION |
us-east-1 |
RADOSGW/MinIO ignorieren die Region inhaltlich, SigV4 braucht aber einen Wert |
S3_ACCESS_KEY / S3_SECRET_KEY |
leer | Pflichtfelder bei STORAGE_BACKEND=s3 |
S3_FORCE_PATH_STYLE |
true |
Path-Style-Adressierung (endpoint/bucket/key) funktioniert überall ohne DNS-Wildcard-Setup |
S3_VERIFY_TLS |
true |
false nur für Test-Umgebungen mit selbstsigniertem Zertifikat |
S3_CA_BUNDLE |
leer | Pfad zu einem CA-Bundle, wenn der Endpoint ein Zertifikat einer privaten CA verwendet |
Fehlen S3_BUCKET, S3_ACCESS_KEY oder S3_SECRET_KEY bei STORAGE_BACKEND=s3, verweigert der API-Start mit einer entsprechenden Fehlermeldung.
3. Compose-Overlay aktivieren
Abschnitt betitelt „3. Compose-Overlay aktivieren“docker-compose.s3.yml reicht die S3_*-Variablen an api, worker und cantaloupe durch und stellt Cantaloupe auf S3Source um. Als letztes Overlay nach den übrigen anhängen:
docker compose -f docker-compose.yml -f docker-compose.cantaloupe.yml \ -f docker-compose.prod.yml -f docker-compose.s3.yml up -dWichtig — Cantaloupe braucht DNS für den Bucket: Cantaloupes S3Source (Version 5.0.x) kennt keine Path-Style-Adressierung, sondern spricht ausschließlich virtual-hosted (<bucket>.<endpoint-host>). <bucket>.<endpoint-host> muss also auflösbar sein — entweder per Wildcard-DNS auf den Objektspeicher-Endpoint oder per explizitem DNS-Eintrag für genau diesen Bucket-Namen. Das Backend selbst (boto3, für Upload/Download über die API) spricht dagegen per Default Path-Style (S3_FORCE_PATH_STYLE=true) und braucht dieses DNS-Setup nicht.
Ohne Cloud-Account testen (MinIO)
Abschnitt betitelt „Ohne Cloud-Account testen (MinIO)“Für den Dev-Stack steht docker-compose.minio.yml bereit — startet einen lokalen MinIO-Container samt Konsole (http://localhost:9001, minioadmin/minioadmin) und verdrahtet api, worker und cantaloupe automatisch dagegen:
docker compose -f docker-compose.yml -f docker-compose.dev.yml \ -f docker-compose.cantaloupe.yml -f docker-compose.minio.yml up -dDer Bucket muss auch hier einmalig angelegt werden (MinIO-Konsole oder S3-API).
Grenzen
Abschnitt betitelt „Grenzen“- Keine Live-Migration: Der Wechsel zwischen
localunds3verschiebt bestehende Mediendateien nicht automatisch. Ein Backend-Wechsel ist nur für neue Instanzen bzw. vor dem ersten Produktionsstart sinnvoll; bereits gespeicherte Dateien müssten manuell auf den neuen Bucket bzw. zurück nachMEDIA_ROOTübertragen werden, inklusive Anpassung derstorage_key-Werte in der Datenbank. - Keine presigned URLs: Ausgelieferte Mediendateien (
/objects/{id}/media/{media_id}/file) streamen bei S3 immer durch die API, nicht per Redirect auf eine presigned URL. So bleibt die Sichtbarkeitsprüfung (öffentlich/privat) wirksam — private Medien können nicht über eine direkt aufrufbare S3-URL an der API vorbeigeleitet werden. Der lokale Backend-Pfad nutzt dagegen weiterhin die effizienteFileResponse-Sendfile-Auslieferung.
Updates einspielen
Abschnitt betitelt „Updates einspielen“git pulldocker compose -f docker-compose.yml -f docker-compose.prod.yml builddocker compose -f docker-compose.yml -f docker-compose.prod.yml up -ddocker compose exec api alembic upgrade head# Falls Schema-Änderungen: Reindex anstoßencurl -X POST https://deine-domain.de/v1/search/reindexFür Updates, die nur Objekt-Suchfelder ändern, reicht:
curl -X POST https://deine-domain.de/v1/search/reindex/objectServerumzug
Abschnitt betitelt „Serverumzug“Für den Wechsel auf einen neuen Host siehe Katalon auf einen neuen Server umziehen. Die Anleitung sichert Datenbank, Medien, Instanzkonfiguration und TLS-Dateien; Elasticsearch wird auf dem Zielsystem neu indexiert.
Backups
Abschnitt betitelt „Backups“Automatisch (backup-Service)
Abschnitt betitelt „Automatisch (backup-Service)“Der Compose-Stack enthält einen backup-Service (docker/backup.sh), der täglich
läuft und beides sichert:
- Datenbank:
pg_dump→db_<timestamp>.sql.gz - Mediendateien (
MEDIA_ROOT):media_<timestamp>.tar.gz
Dumps landen im Host-Verzeichnis BACKUP_ROOT (Default /srv/katalon/backups) und
werden nach BACKUP_RETENTION_DAYS (Default 14) automatisch gelöscht.
Konfiguration über .env:
| Variable | Default | Bedeutung |
|---|---|---|
BACKUP_ENABLED |
true |
false = Service läuft, macht aber keine Backups |
BACKUP_ROOT |
/srv/katalon/backups |
Host-Zielverzeichnis |
BACKUP_RETENTION_DAYS |
14 |
ältere Dumps werden gelöscht |
BACKUP_AT |
03:00 |
feste Uhrzeit HH:MM (Container-Zeitzone, s. u.) |
BACKUP_INTERVAL_SECONDS |
86400 |
nur wirksam wenn BACKUP_AT leer ist |
Zwei Zeitplan-Modi: Ist BACKUP_AT gesetzt, läuft das Backup täglich zur festen
Uhrzeit. Ist es leer, greift der Intervall-Modus (BACKUP_INTERVAL_SECONDS, Backup
sofort beim Start + dann alle N Sekunden).
Zeitzone: BACKUP_AT wird in der Container-Zeitzone interpretiert (Standard UTC).
Für lokale Zeit TZ=Europe/Berlin im environment des backup-Service setzen.
# Einmaliges Backup sofort auslösen (z. B. vor einem Deploy)docker compose run --rm backup once
# Vorhandene Backups ansehenls -lh /srv/katalon/backupsOff-site empfohlen: BACKUP_ROOT zusätzlich per rsync/S3 auf einen zweiten
Standort spiegeln — ein Backup auf demselben Host schützt nicht vor Host-Verlust.
Restore
Abschnitt betitelt „Restore“Datenbank:
gunzip -c /srv/katalon/backups/db_20260101_030000.sql.gz \ | docker compose exec -T db psql -U katalon katalonMediendateien (. = Inhalt von MEDIA_ROOT):
tar xzf /srv/katalon/backups/media_20260101_030000.tar.gz -C "$MEDIA_ROOT"Nach dem DB-Restore Elasticsearch neu aufbauen:
curl -X POST https://deine-domain.de/v1/search/reindex/object # je TypRestore-Drill (durchgespielt 2026-07-13)
Abschnitt betitelt „Restore-Drill (durchgespielt 2026-07-13)“Ein Restore ist nur so viel wert wie sein letzter Test. Verifizierter Ablauf auf frischer Umgebung:
- Frisches Verzeichnis +
.envmit anderemPOSTGRES_DB(z. B.katalon_restore). - Nur DB starten:
docker compose up -d db. - Neueste
db_*.sql.gzper obigempsql-Befehl einspielen. - Zeilenzahl gegen Quelle prüfen:
docker compose exec -T db psql -U katalon -d katalon_restore -c "SELECT count(*) FROM objects;" - Media-tar in ein Testverzeichnis entpacken, Dateizahl vergleichen.
- Voll starten,
curl .../health→ok, Stichprobe im Admin-UI.
Ergebnis: Dump lässt sich sauber einspielen (PostGIS-Extension inklusive), Media-tar entpackt vollständig. Drill mindestens halbjährlich wiederholen.
Elasticsearch
Abschnitt betitelt „Elasticsearch“Elasticsearch-Daten können jederzeit aus der Datenbank neu indexiert werden (POST /v1/search/reindex). Ein eigenes ES-Backup ist für den normalen Betrieb nicht zwingend nötig.
Monitoring
Abschnitt betitelt „Monitoring“Health-Check
Abschnitt betitelt „Health-Check“curl https://deine-domain.de/health# → {# "status": "ok",# "checks": {# "database": "ok",# "elasticsearch": "ok",# "redis": "ok",# "beat_heartbeat": "ok",# "cantaloupe": "ok"# },# "queue_backlog": 0,# "backup_age_hours": 6.2# }Der Endpoint prüft aktiv Datenbank, Elasticsearch, Redis, Cantaloupe (IIIF-Bildserver) und den Celery-Beat/Worker-Herzschlag; ist Oxigraph aktiviert (OXIGRAPH_ENABLED=true), erscheint zusätzlich ein oxigraph-Eintrag. Ist eine dieser Abhängigkeiten nicht erreichbar, liefert er HTTP 503 mit {"status": "degraded", ...} —
so kann ein Load Balancer einen ausgefallenen Backend-Zustand erkennen. queue_backlog (Länge der Standard-Task-Queue) und backup_age_hours (Alter des letzten erfolgreichen Backups) sind rein informativ und wirken sich nicht auf den HTTP-Status aus — als Grundlage für eigene Monitoring-Schwellenwerte geeignet.
docker compose logs api # API-Logsdocker compose logs worker # Celery-Worker-Logsdocker compose logs nginx # ZugriffslogOAI-PMH-Endpunkt testen
Abschnitt betitelt „OAI-PMH-Endpunkt testen“# Identifycurl "https://deine-domain.de/oai?verb=Identify"
# Alle Setscurl "https://deine-domain.de/oai?verb=ListSets"
# Alle Records (paginiert)curl "https://deine-domain.de/oai?verb=ListRecords&metadataPrefix=oai_dc"Ressourcen-Empfehlungen
Abschnitt betitelt „Ressourcen-Empfehlungen“| Service | Minimum | Empfohlen |
|---|---|---|
| db (PostgreSQL) | 256 MB | 512 MB |
| redis | 64 MB | 128 MB |
| elasticsearch | 1 GB | 2 GB |
| api | 256 MB | 512 MB |
| worker (Celery) | 256 MB | 512 MB |
| cantaloupe | 256 MB | 512 MB |
| nginx | 32 MB | 64 MB |
| Gesamt | ~2,1 GB | ~4 GB |
Häufige Probleme
Abschnitt betitelt „Häufige Probleme“nginx startet nicht: “cannot load certificate … BIO_new_file() failed”
Abschnitt betitelt „nginx startet nicht: “cannot load certificate … BIO_new_file() failed”“docker/certs/ enthält keine Zertifikatsdateien. Betrifft typischerweise Deployments
hinter einem externen Reverse Proxy (Traefik, …), die weiterhin docker/nginx.conf
einbinden, obwohl der 443-Block dort nie gebraucht wird — siehe
Option C oben. Kein
Zertifikat für Prod erzeugen, sondern eine eigene Config ohne listen 443 ssl mounten.
API startet nicht (Datenbankverbindung schlägt fehl)
Abschnitt betitelt „API startet nicht (Datenbankverbindung schlägt fehl)“docker compose logs db | tail -20# Healthcheck abwarten: depends_on mit condition: service_healthy ist gesetztElasticsearch nicht erreichbar
Abschnitt betitelt „Elasticsearch nicht erreichbar“Katalon startet auch ohne ES (API-Endpunkte funktionieren, Suche gibt leere Ergebnisse zurück). ES braucht beim ersten Start 30–60 Sekunden.
Medien-Upload schlägt fehl
Abschnitt betitelt „Medien-Upload schlägt fehl“Prüfe, ob das Volume media_data vom API-Container schreibbar ist:
docker compose exec api ls -la /var/lib/katalon/media/api und worker laufen als nicht-root User app (UID 1000). Ist das
Host-Verzeichnis hinter MEDIA_ROOT nicht für UID 1000 schreibbar, schlagen
Uploads mit PermissionError fehl. Ein Startup-Check loggt in diesem Fall
eine Warnung (MEDIA_ROOT nicht beschreibbar für UID ...) — in den
api-Logs nach dieser Meldung suchen, statt erst beim ersten Upload-Fehler
zu bemerken.
Fix (nur das Verzeichnis selbst, nicht rekursiv — bei großem Medienbestand
wäre chown -R potenziell sehr langsam):
sudo chown 1000:1000 "${MEDIA_ROOT:-/srv/katalon/media}"Neue Unterordner (_batch_imports, logos, themes) erben die Rechte des
Elternverzeichnisses bei Neuanlage durch den app-User automatisch korrekt;
bereits bestehende Unterordner mit falschem Owner müssen einzeln behandelt
werden (chown 1000:1000 <verzeichnis>, ebenfalls nicht rekursiv nötig,
solange nur neue Dateien hinzukommen).
Große IIIF-Bilder bleiben leer, obwohl Thumbnails funktionieren
Abschnitt betitelt „Große IIIF-Bilder bleiben leer, obwohl Thumbnails funktionieren“Cantaloupe kann info.json und kleine Thumbnails ausliefern, obwohl der beschreibbare Derivat-Cache nicht korrekt angelegt wurde. Große Bildanforderungen liefern dann unter Umständen HTTP 200 mit leerem Inhalt. Der Compose-Stack verwendet für den wegwerfbaren Derivat-Cache inzwischen ein tmpfs; bestehende Installationen mit dem früheren Named Volume müssen den Dienst einmal neu erstellen.
docker compose up -d --force-recreate cantaloupeDadurch wird kein Datenbank- oder Medien-Volume gelöscht.
Mehrere Instanzen auf einem Host: tmpfs-Größe des Cantaloupe-Caches
Abschnitt betitelt „Mehrere Instanzen auf einem Host: tmpfs-Größe des Cantaloupe-Caches“Der Cantaloupe-Derivat-Cache läuft als tmpfs, begrenzt über CANTALOUPE_CACHE_TMPFS_SIZE (Default 512m, siehe .env.example). Ohne dieses Limit würde Docker pro Mount bis zu 50 % des Host-RAM erlauben. Läuft mehr als eine Katalon-artige Instanz auf demselben Host, CANTALOUPE_CACHE_TMPFS_SIZE je Instanz bewusst so wählen, dass die Summe aller Instanzen zusammen mit Elasticsearch- und Postgres-Speicherbedarf den verfügbaren Host-RAM nicht übersteigt.
Bei einem alten Stack, der noch das frühere Named Volume verwendet, kann der Cache alternativ repariert werden:
docker compose logs cantaloupe --tail=100 | grep -E "AccessDeniedException|FilesystemCache"docker compose exec cantaloupe ls -ld /var/lib/cantaloupe/cachedocker compose exec cantaloupe sh -c 'chown -R cantaloupe:cantaloupe /var/lib/cantaloupe/cache && chmod -R u+rwX /var/lib/cantaloupe/cache'curl -s -o /tmp/iiif-check.jpg -w 'HTTP %{http_code}, bytes %{size_download}\n' \ "https://deine-domain.de/iiif/3/<media-id>.<endung>/full/max/0/default.jpg"Die Daten im Medien-Volume werden dabei nicht gelöscht. Der Test muss eine positive Byte-Anzahl liefern; anschließend die betroffene Portalseite hart neu laden.
Images nach Update nicht aktuell
Abschnitt betitelt „Images nach Update nicht aktuell“docker compose -f docker-compose.yml -f docker-compose.prod.yml build --no-cache