---
title: "Quire mit Docker Compose installieren"
description: "Installieren Sie Quire mit Docker Compose auf Ihrer eigenen Infrastruktur."
image: "https://docs.quirelms.com/og.png"
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.quirelms.com/de/llms.txt
> Use this file to discover all available pages before exploring further.

# Quire mit Docker Compose installieren

<span id="installing-quire-with-docker-compose"></span>

Auf einem Host läuft das vollständige Produkt: das LMS, seine Hintergrundaufgaben, die Realtime- und kollaborativen Bearbeitungsdienste sowie alle optionalen Dienste hinter einem Profil. Das Design ist in Abschnitt 2 von `docs/architecture/23-ops.md` beschrieben.

Andere Ziele: [Vercel](/de/ops/vercel/) und [Cloudflare Workers](/de/ops/cloudflare/) betreiben nur den Web-Tier. Upgrades werden unter [upgrade.md](/de/ops/upgrade/) beschrieben, Sicherungen und die Wiederherstellungsübung unter [backup-restore.md](/de/ops/backup-restore/).

## Voraussetzungen <!--quire:what-you-need-->

- Docker Engine 27 oder höher mit Compose-Plugin 2.30 oder höher.
- 4 CPU-Kerne und 8 GB Speicher für den Standard-Stack; 8 Kerne und 16 GB mit `--profile full` (allein ClamAV benötigt etwa 1,5 GB für Signaturen).
- Einen DNS-Namen für den Web-Tier und einen zweiten für nicht vertrauenswürdige Inhalte. Es müssen verschiedene Hosts sein: SCORM-Pakete und hochgeladenes HTML laufen auf dem Content-Origin, damit sie niemals die Cookies des LMS lesen können.
- Für lokale Tests zeigen `lvh.me` und `*.localhost` auf 127.0.0.1. Das verwendet `docker/.env.example`. Der eigene Dienst `proxy` des Stacks stellt beide über HTTPS mit einer lokalen Zertifizierungsstelle bereit; es muss daher nichts Weiteres installiert werden (siehe „TLS“).
- Die Ports 80 und 443 müssen auf dem Host frei sein (`QUIRE_PROXY_HTTP_PORT` und `QUIRE_PROXY_HTTPS_PORT` ändern sie).

## Erster Start <!--quire:first-run-->

```sh
QUIRE_APP_ORIGIN=https://learn.example.org \
QUIRE_CONTENT_ORIGIN=https://content.example-content.org \
QUIRE_SETUP_ADMIN_EMAIL=you@example.org \
  docker/scripts/init-env.sh
docker compose -f docker/compose.yaml up -d --build
docker compose -f docker/compose.yaml logs init
```

`docker/scripts/init-env.sh` erstellt `docker/.env` aus `docker/.env.example` und generiert alle Secrets (Datenbankpasswörter, Signatur- und Master-Schlüssel sowie das Schlüsselpaar zum Starten von Inhalten) sowie den Signaturschlüssel für Audit-Prüfpunkte unter `docker/secrets/audit-signing-key.pem`. Compose hängt ihn als Secret in die Worker ein. Das Skript benötigt nur `sh`, `awk` und `openssl` und überschreibt keine vorhandene `docker/.env`. Kopieren Sie beide Dateien vom Host weg: Ohne `QUIRE_MASTER_KEY` kann eine wiederhergestellte Datenbank ihre gespeicherten Zugangsdaten nicht entschlüsseln. Um die Datei stattdessen manuell zu erstellen, führen Sie `cp docker/.env.example docker/.env` aus; die Datei erläutert, wie jedes Secret erzeugt wird.

Beide Origins müssen `https` verwenden: Der Content-Dienst lehnt in der Produktion einfaches HTTP ab, und sie dürfen keine gemeinsame registrierbare Domain haben. Der Dienst `proxy` beendet TLS für beide (siehe „TLS“); `init-env.sh` lehnt eine Origin mit `http://` ab.

Der Stack startet in einer festgelegten Reihenfolge, wobei jeder Schritt auf den Abschluss des vorherigen wartet:

1. `postgres` wird gesund. Beim allerersten Start setzt sein Initialisierungsskript (`docker/postgres/init/90-passwords.sh`) die vier Rollenpasswörter.
2. `migrate` wendet jede Migration an und richtet die Job-Warteschlange sowohl in der Kontrolldatenbank als auch in jeder dedizierten Mandantendatenbank ein. Der Dienst prüft, dass sie alle übereinstimmen, und wird dann beendet (siehe docs/ops/upgrade.md). Migrationen werden bei jedem Start ausgeführt und sind idempotent; ein Upgrade besteht daher aus einem neuen Image und einem Neustart.
3. `init` (`apps/web/src/first-run.ts`) registriert die Anwendungsdatenbank unter `QUIRE_DATABASE_ID` und erstellt bei gesetztem `QUIRE_SETUP_ADMIN_EMAIL` die erste Organisation und deren Administrator. Die Anmeldeadresse und ein generiertes Passwort werden einmalig in `docker compose logs init` ausgegeben.
4. `web`, `content`, `worker`, `scheduler`, `collab` und `centrifugo` starten.
5. `proxy` startet, sobald `web` und `content` gesund sind.

Öffnen Sie `https://demo.` gefolgt von Ihrer Anwendungsdomain (das `init`-Protokoll zeigt die genaue Anmeldeadresse an) und melden Sie sich an. Bei einer lokalen Installation müssen Sie zuerst der Zertifizierungsstelle des Proxys vertrauen (siehe „TLS“). Ändern Sie das generierte Passwort unter `/account/security`.

Ein Prozess, der ohne ein erforderliches Secret gestartet wird, verweigert den Start und nennt die fehlende Einstellung im Protokoll. Es startet nichts mit unvollständiger Konfiguration.

## Dienste und Profile <!--quire:services-and-profiles-->

| Dienst | Profil | Aufgabe |
| --- | --- | --- |
| postgres | always | Datenbank (PostgreSQL 18 mit pgvector, erstellt aus `docker/postgres.Dockerfile`), mit WAL-Archivierung ab dem ersten Start |
| migrate, init | always | Einmalige Ausführung: Migrationen, dann Erststart |
| web | always | LMS auf `QUIRE_HTTP_PORT` (8080) |
| content | always | Origin für nicht vertrauenswürdige Inhalte auf `QUIRE_CONTENT_PORT` (8081) |
| worker | always | Hintergrundjobs: E-Mail, Berichte, Dateiverarbeitung und Webhooks |
| scheduler | always | Wiederkehrende Jobs: registriert die 64 Laufzeitzeitpläne und übergibt sie an den Worker; jeweils nur ein führender Prozess |
| collab | always | WebSocket für kollaboratives Bearbeiten auf `QUIRE_COLLAB_HTTP_PORT` (1234) |
| centrifugo | always | Realtime-Verteilung auf `QUIRE_REALTIME_PORT` (8000) |
| proxy | always | Caddy, TLS-Eingang auf den Ports 80 und 443 (siehe „TLS“) |
| valkey | `cache` | Cache und Ratenbegrenzungen |
| clamav | `scan` | Malware-Prüfung hochgeladener Dateien |
| gotenberg | `preview` | Vorschauen von Office-Dateien als PDF und Darstellung von Zertifikaten |
| imgproxy | `images` | Skalierte und konvertierte Bilder |
| transcoder | `video` | Worker-Image mit ausschließlich LGPL-lizenziertem ffmpeg für Video-Varianten |
| seaweedfs | `storage` | S3-kompatibler Objektspeicher auf diesem Host |
| otelcol | `observability` | OpenTelemetry-Collector |
| mailpit | `devmail` | Fängt für Tests alle ausgehenden E-Mails ab |
| backup | `backup` | Einmalige Basissicherung; siehe backup-restore.md |
| backup-scheduler, backup-offsite | `backup` | Basissicherung alle `QUIRE_BACKUP_INTERVAL_HOURS` und verschlüsselte externe Kopien mit wöchentlicher Verifizierungsübung |
| h5p | `h5p` | Von Ihnen bereitgestelltes H5P-LTI-1.3-Tool-Image in `QUIRE_H5P_IMAGE`, auf `QUIRE_H5P_PORT` (8090); siehe „H5P-Anbieter verbinden“ |

`--profile full` startet alle optionalen Dienste außer `backup` und `h5p`. Starten Sie ein einzelnes Profil mit `docker compose -f docker/compose.yaml --profile scan up -d`. Auch ohne einen optionalen Dienst funktioniert Quire weiterhin und zeigt an, was fehlt: Ohne Scanner werden Uploads ungeprüft gespeichert und der Administrator wird informiert; ohne Gotenberg können Dateien heruntergeladen, aber nicht in der Vorschau angezeigt werden; ohne Transcoder wird ein Video als Originaldatei abgespielt.

Alle Images von Drittanbietern und die damit verbundenen Lizenzpflichten sind in `docker/third-party-containers.yaml` aufgeführt.

### H5P-Anbieter verbinden <!--quire:connecting-an-h5p-provider-->

Quire bindet keine H5P-Laufzeitumgebung oder Sidecar-Komponente ein und liefert sie auch nicht mit (ADR 0019). Wenn Sie H5P verwenden, benötigen Sie ein eigenes gehostetes Abonnement oder betreiben eine selbst gehostete H5P-Instanz getrennt von Quire. Registrieren Sie den Anbieter als externes LTI-1.3-Tool und fügen Sie dessen Inhalte als Tool-Aktivitäten in Kurse ein. Quire tauscht Noten und Aktivitäts- beziehungsweise Bewertungsfortschritt über LTI Assignment and Grade Services (AGS) aus. Sendet der Anbieter auch xAPI-Anweisungen, konfigurieren Sie diese getrennt für den xAPI-Anweisungsspeicher von Quire; der Austausch von Noten und Fortschritt über AGS sendet keine xAPI-Anweisungen. Bei Moodle-Importen werden H5P-Aktivitäten als solche gemeldet, die eine LTI-Tool-Verbindung benötigen. Der Anbieter bleibt für seine H5P-Laufzeitumgebung, Inhaltserstellung, Inhaltsbibliothek und den Versuchsverlauf verantwortlich.

Um auf diesem Host eine eigene selbst gehostete Instanz zu betreiben, setzen Sie `QUIRE_H5P_IMAGE` auf deren Image und starten Sie das Profil `h5p`. Compose veröffentlicht sie auf `QUIRE_H5P_PORT` (8090) und speichert ihre Daten im Volume `h5p-data`; das Image und die damit verbundenen Pflichten bleiben in Ihrer Verantwortung.

## Einstellungen <!--quire:settings-->

Jeder Prozess liest `docker/.env`. Die Vorlage `docker/.env.example` führt jede Einstellung mit ihrem Standardwert auf. Die Bereiche:

### Adressen <!--quire:addresses-->

| Einstellung | Bedeutung |
| --- | --- |
| `QUIRE_APP_ORIGIN` | Öffentliche Adresse des LMS, zum Beispiel `https://learn.example.com` |
| `QUIRE_CONTENT_ORIGIN` | Origin für Inhalte auf einem anderen Host |
| `QUIRE_PLATFORM_DOMAINS` | Kommagetrennte Domains, unter denen Organisationen liegen |
| `QUIRE_MARKETING_ORIGIN` | Optional. The marketing site, default `https://quirelms.com`. The only origin the waitlist form (`POST /api/waitlist`, `POST /waitlist`) accepts and redirects to. Comma separated; a `www.` variant is allowed only if listed |
| `QUIRE_DEPLOY_TARGET` | Hier `compose`. Weitere Anleitungen behandeln `vercel` und `cloudflare` |
| `QUIRE_TRUSTED_PROXY_CIDRS` | Proxys, deren `X-Forwarded-For` vertraut wird |

### Secrets <!--quire:secrets-->

| Einstellung | Bedeutung |
| --- | --- |
| `QUIRE_SECRET_KEY` | Signiert Sitzungen und Tokens. 64 hexadezimale Zeichen |
| `QUIRE_MASTER_KEY` | Umhüllt gespeicherte Zugangsdaten, etwa SSO- und Webhook-Secrets. 32 Byte, Base64. Web-Tier und Worker benötigen denselben Wert. Rotation: [key-rotation.md](/de/ops/key-rotation/) |
| `QUIRE_MASTER_KEY_VERSION` | Versionsbezeichnung des Master-Schlüssels, ohne Angabe `v1`. Bei einer Rotation erhöhen |
| `QUIRE_MASTER_KEY_RETIRED` | Frühere Master-Schlüssel, die zum Lesen der von ihnen umhüllten Werte noch nötig sind, etwa `v1=<base64>`. Entfernen, wenn eine Rotation ohne ungelöste Werte abgeschlossen ist |
| `QUIRE_COLLAB_SIGNING_KEY` | Von Web-Tier und Collab gemeinsam zum Signieren von Bearbeitungs-Tokens verwendet |
| `QUIRE_BACKUP_SIGNING_KEY` | Signiert Kurssicherungen (optional) |

Bewahren Sie eine Kopie von `QUIRE_MASTER_KEY` getrennt von diesem Host auf. Ohne diesen Schlüssel kann eine wiederhergestellte Datenbank die enthaltenen Zugangsdaten nicht entschlüsseln.

### Datenbank <!--quire:database-->

| Einstellung | Bedeutung |
| --- | --- |
| `POSTGRES_PASSWORD` | Superuser-Passwort für Container und Sicherungen |
| `QUIRE_DB_APP_PASSWORD`, `QUIRE_DB_MIGRATOR_PASSWORD`, `QUIRE_DB_REPORT_PASSWORD`, `QUIRE_DB_AUDIT_PASSWORD` | Rollenpasswörter, die beim ersten Start gesetzt werden |
| `DATABASE_URL` | Anwendungsrolle. Für jede ihrer Abfragen gilt Row-Level-Security |
| `DATABASE_MIGRATOR_URL`, `QUIRE_MIGRATION_URL` | Migrationsrolle für `migrate` und `init` |
| `QUIRE_SUPERUSER_URL` | Wird nur beim Erststart verwendet |
| `QUIRE_REPORT_DATABASE_URL` | Schreibgeschützte Report-Rolle für Berichte und den Report-Builder |
| `QUIRE_AUDIT_DATABASE_URL` | Audit-Rolle für die Audit-Konsole und den SIEM-Export |
| `QUIRE_DATABASE_ID` | Beliebige UUID, während der gesamten Lebensdauer der Installation unverändert |

Rollenpasswörter werden nur gesetzt, wenn das Datenbank-Volume zum ersten Mal erstellt wird. Ändern Sie eines später mit `ALTER ROLE` und aktualisieren Sie anschließend die zugehörige URL.

`QUIRE_REPORT_DATABASE_URL` wird für die physische Datenbank verwendet, die durch `DATABASE_URL` konfiguriert ist. Legen Sie für jede weitere registrierte physische Datenbank ihre eigene Verbindungs-URL `quire_report` in den Umgebungen von Web-Tier und Worker fest. Tragen Sie anschließend im Feld **Reporting environment variable** der Datenbank den Variablennamen als `env:NAME` ein. Der Verweis muss auf dieselbe Datenbank wie ihre Anwendungsverbindung zeigen, idealerweise auf deren Lesereplikat. Jede Report-Oberfläche folgt dem Mandanten zur Report-Verbindung seiner eigenen Datenbank: der Report-Builder und gespeicherte Berichte, geplante Zustellungen, Report-Exporte, Analytics, das Audit-Protokoll, die REST-Auditressourcen und die Auditsuche des Assistenten. Keine davon verwendet jemals die Report-URL einer anderen Datenbank. Hat eine Datenbank keine Report-Verbindung, laufen gewöhnliche Berichte über ihre eigene Anwendungsverbindung; Analytics und alle Audit-Lesezugriffe werden dagegen abgelehnt und melden den Grund, da die Anwendungsrolle das Auditprotokoll nicht lesen kann.

### Treiber <!--quire:drivers-->

| Einstellung | In dieser Version | Hinweise |
| --- | --- | --- |
| `QUIRE_STORAGE_DRIVER` | `local` (Standard), `s3` oder `azure` | `local` speichert Dateien im Volume `files`. `s3` unterstützt AWS S3, R2, GCS-Interoperabilität und andere S3-kompatible Speicher mit fortsetzbaren Multipart-Uploads |
| `QUIRE_REALTIME_DRIVER` | `inprocess` (Standard), `sse`, `centrifugo` oder `durable_objects` | `inprocess` eignet sich für einen Web-Container; bei mehreren verwenden Sie `centrifugo` oder `sse` |
| `QUIRE_CACHE_DRIVER` | `memory` (Standard), `postgres` oder `valkey` | `memory` gilt pro Prozess. Verwenden Sie `valkey` oder `postgres`, damit Ratenbegrenzungen über Container hinweg gelten |
| `QUIRE_VIDEO_DRIVER` | `ffmpeg` (Standard) oder `progressive_mp4` | Oder ein gehosteter Anbieter, mit dessen Schlüsseln: Cloudflare Stream, Mux oder Bunny |
| `QUIRE_IMAGE_DRIVER` | `noop` (Standard), `imgproxy` oder `cloudflare` | `noop` liefert jedes Bild in Originalgröße aus. `imgproxy` benötigt das Profil `images` und die unten aufgeführten Einstellungen; `cloudflare` verwendet Cloudflare Images |
| `QUIRE_MEETING_PROVIDER` | `bbb`, `zoom`, `teams`, `meet`, `jitsi` oder `in_process` | Plattformstandard für Live-Sitzungen. Ohne Angabe melden Live-Sitzungen, dass sie nicht konfiguriert sind, bis eine Organisation unter Integrations, Live session provider ihr eigenes Konto verbindet. Das Konto der Organisation hat immer Vorrang vor diesem Wert. Die jeweiligen Einstellungen des Anbieters (`BBB_URL` und `BBB_SECRET`, die Variablen `ZOOM_*`, `TEAMS_*`, `GOOGLE_MEET_*` und `JITSI_*`) werden nur für den hier benannten Anbieter gelesen |
| `QUIRE_MEETING_REGIONS` | Kommagetrennte Liste aus `eu`, `uk`, `us` | Regionen, in denen der Plattformstandard-Anbieter Besprechungen verarbeitet. Ohne Angabe wird, wie bisher, nicht geprüft, ob dies einer auf eine Region festgelegten Organisation entspricht. Das eigene Konto einer Organisation zeigt seine Regionen auf der entsprechenden Seite an |

Ein Treiberwert, der in dieser Version nicht enthalten ist, wird beim Start des Web-Tiers mit Angabe der Einstellung abgelehnt, statt stillschweigend durch den Standardwert ersetzt zu werden.

### Bilder <!--quire:images-->

Seiten fordern Bilder in vier festen Größen über `/api/files/{id}/image/{size}` an. Der Endpunkt prüft dieselben Zugriffsrechte wie für die Datei selbst und leitet dann zum Bilddienst weiter. Jede Organisation darf pro Stunde `QUIRE_IMAGE_SPECS_PER_HOUR` (Standardwert 2000) neue Kombinationen aus Bild und Größe anfordern. Größen, die bereits in dieser Stunde erstellt wurden, werden nicht mitgezählt. Verwenden Sie bei mehr als einem Web-Container `valkey` oder `postgres` für `QUIRE_CACHE_DRIVER`, damit das Limit containerübergreifend gilt.

| Einstellung | Treiber | Hinweise |
| --- | --- | --- |
| `IMGPROXY_URL` | `imgproxy` | Adresse, unter der Browser imgproxy erreichen, zum Beispiel `https://images.example.org`. Das Profil `images` veröffentlicht den Dienst auf `QUIRE_IMAGES_PORT` (8082) |
| `IMGPROXY_KEY`, `IMGPROXY_SALT` | `imgproxy` | Hexadezimale Zeichenfolgen, dieselben Werte, mit denen imgproxy gestartet wird. Erzeugen Sie jeden Wert mit `openssl rand -hex 32`. Quire signiert jede Bildadresse damit, sodass imgproxy nichts rendert, was Quire nicht angefordert hat |
| `QUIRE_IMAGE_SOURCE_ORIGIN` | `imgproxy` mit lokalem Speicher | Quelle, von der imgproxy Originale abruft. Compose setzt `http://web:3000`. Bei `s3`- oder `azure`-Speicher ruft imgproxy sie aus dem Bucket ab; diese Einstellung wird dann nicht verwendet |
| `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_IMAGES_TOKEN`, `CLOUDFLARE_IMAGES_ACCOUNT_HASH` | `cloudflare` | API-Token mit Bearbeitungsrechten für Images und der Account-Hash aus Images, Developer resources. Aktivieren Sie flexible Varianten für das Konto |
| `CLOUDFLARE_IMAGES_SIGNING_KEY` | `cloudflare` | Optional. Wenn gesetzt, sind Bilder privat und jede Adresse wird signiert und läuft ab. Ohne diesen Schlüssel sind Bilder öffentlich unter Adressen, die von `QUIRE_SECRET_KEY` abgeleitet sind und die niemand erraten kann |

Cloudflare Images bewahrt eine eigene Kopie jedes Originals auf, das es ausliefert. Beim Löschen einer Datei entfernt der Worker diese Kopie vor dem Original.

### Warteschlange <!--quire:queue-->

Hintergrundjobs verwenden pg-boss in derselben Postgres-Datenbank. Es muss daher kein Warteschlangendienst betrieben oder konfiguriert werden. Jobs werden in derselben Transaktion wie die auslösende Änderung eingereiht. Ein Absturz kann deshalb keinen Job verlieren oder doppelt senden. Hier lautet der Standardwert für `QUIRE_QUEUE_DRIVER` `pgboss`; `vercel` und `cloudflare` verschieben nur einfache Benachrichtigungs- und Webhook-Zustellungen in die jeweilige Warteschlange der Plattform. Die Anleitungen für Vercel und Cloudflare beschreiben dies und erläutern, wie deren Web-Tiers Jobs einreihen.

### E-Mail <!--quire:email-->

Legen Sie eine der folgenden Einstellungen fest:

- `QUIRE_EMAIL_PROVIDER_CONFIG`: Ein JSON-Objekt mit dem Namen eines HTTP-Anbieters und seinen Zugangsdaten, zum Beispiel `{"provider":"postmark","token":"..."}`. Unterstützt werden Postmark, Amazon SES, Mailgun, SendGrid und Resend.
- `QUIRE_SMTP_URL`: `smtp://user:password@host:587`. Nur für dieses Ziel; serverlose Ziele blockieren SMTP.

`QUIRE_MAIL_FROM` gibt den Absender an. Starten Sie für einen Test von Quire das Profil `devmail`, setzen Sie `QUIRE_SMTP_URL=smtp://mailpit:1025` und lesen Sie die E-Mails unter `http://localhost:8025`.

### Optionale Dienste <!--quire:optional-services-->

| Einstellung | Mit Profil |
| --- | --- |
| `CLAMAV_URL=tcp://clamav:3310` | `scan` |
| `GOTENBERG_URL=http://gotenberg:3000` | `preview` |
| `IMGPROXY_KEY`, `IMGPROXY_SALT` | `images` |
| `VALKEY_URL=redis://valkey:6379` | `cache` |
| `QUIRE_OPENSEARCH_URL` oder `QUIRE_MEILISEARCH_URL` | Externe Suche; andernfalls Volltextsuche in Postgres |
| `QUIRE_BREACH_CHECK_PROVIDER=off`, `QUIRE_BREACH_CHECK_URL` | Prüfung auf kompromittierte Passwörter. Standardmäßig wird `api.pwnedpasswords.com` abgefragt (nur ein Hash-Präfix mit fünf Zeichen wird gesendet). Mit `off` wird die Prüfung deaktiviert; die URL verweist auf eine von Ihnen betriebene Range-API |

### Beobachtbarkeit <!--quire:observability-->

`OTEL_EXPORTER_OTLP_ENDPOINT` bezeichnet den Collector, an den jeder Prozess Traces und Metriken sendet. Mit dem Profil `observability` lautet die Adresse `http://otelcol:4318`; in `docker/otel-collector.yaml` fügen Sie den Exporter für Ihr Backend hinzu. Wenn die Einstellung gesetzt ist, exportieren Web-Tier, Worker, Scheduler, Content- und Collab-Prozesse Spans über OTLP/HTTP (Webanfragen, Transaktionen in Mandantendatenbanken, Worker-Jobs und ausgehende Aufrufe) sowie jede Minute Metriken an denselben Endpunkt (`OTEL_METRICS_EXPORTER=none` deaktiviert sie). `OTEL_TRACES_SAMPLER_ARG` legt den Anteil der beibehaltenen Traces fest. Protokolle werden mit `LOG_LEVEL` an die Standardausgabe geschrieben; Compose rotiert sie. Traces enthalten keine personenbezogenen Daten.

### Regionaler Egress (EU-Datenresidenz) <!--quire:regional-egress-eu-data-residency-->

`QUIRE_REGION=eu` gibt an, dass der Stack Organisationen in der Europäischen Union bedient. Der Worker beschränkt dann alle ausgehenden Anfragen für eine auf die EU festgelegte Organisation auf eine Zulassungsliste (21-compliance.md, Abschnitt 8.1). Die Liste enthält die Hosts, die konfigurierte Dienste für die Region angeben (Speicherendpunkt, E-Mail-Anbieter, gehosteter Videoanbieter, eigene Speicherziele der Organisation, KI-Anbieter und E-Mail-Konto), die Hosts aller Dienste mit aktiver Ausnahmegenehmigung sowie die Hosts aus `QUIRE_EGRESS_ALLOW_HOSTS`. Anfragen an andere öffentliche Hosts werden vor dem Versand abgelehnt. Die Ablehnung wird als `privacy/egress_refused` im Audit-Protokoll der Organisation festgehalten und unter Compliance, Data residency aufgeführt.

| Einstellung | Werte | Auswirkung |
| --- | --- | --- |
| `QUIRE_EGRESS_ALLOW_HOSTS` | Kommagetrennte Hostnamen oder `*.example.org` für alle Subdomains | Zusätzliche Hosts, die eine EU-Organisation erreichen darf. Webhook-, xAPI- und SIEM-Endpunkte, Blog-Feeds und Hosts von Amazon SES gehören hierher, da sie von der Organisation selbst gewählt werden und von keinem Dienst angegeben sind. Loopback, private Adressen und Namen mit einem einzelnen Label wie `web` oder `clamav` gehören zu Ihrem eigenen Netzwerk und werden nie geprüft |

Organisationen im Vereinigten Königreich und in den USA sind nicht an eine Hostliste gebunden; für sie gelten weiterhin die Prüfungen der Dienstregion. Legen Sie die Liste beim Worker fest. Die Verwaltungsseite liest sie im Web-Tier aus, um die Zulassungsliste anzuzeigen. Tragen Sie sie deshalb in `docker/.env` ein, die von jedem Dienst gelesen wird.

Die Anwendungsprüfung liefert einen klaren Fehler und einen Audit-Eintrag, ist aber nicht die Garantie: Code kann fehlerhaft sein. Die Garantie ist das Netzwerk, und Compose erzwingt sie nicht für Sie. Betreiben Sie für einen regionalen Stack `worker` und `web` in einem Netzwerk mit `internal: true`, dessen einziger Weg nach außen über einen Egress-Proxy (zum Beispiel einen Squid- oder tinyproxy-Container) führt. Dieser sollte dieselben Hosts wie `QUIRE_EGRESS_ALLOW_HOSTS` sowie die Hosts der konfigurierten Dienste zulassen. Setzen Sie für diese Dienste `HTTPS_PROXY`. Die Residenzseite listet die exakten Hosts auf, die die Anwendung zulässt, sodass sich die beiden Listen vergleichen lassen.

## Systemzustand <!--quire:health-->

| Endpunkt | Bedeutung |
| --- | --- |
| `/healthz` | Liveness: Der Prozess antwortet. Compose verwendet dies für Zustandsprüfungen |
| `/readyz` | Readiness: Abhängigkeiten sind erreichbar; jeder optionale Dienst wird als konfiguriert oder nicht konfiguriert gemeldet. Richten Sie Ihren Load-Balancer darauf aus |

`docker compose -f docker/compose.yaml ps` zeigt den Zustand jedes Dienstes.

## TLS <!--quire:tls-->

Der Dienst `proxy` (Caddy, Apache-2.0, `docker/caddy/Caddyfile`) gehört zum Standard-Stack. Er antwortet auf den Ports 80 und 443 und leitet Anfragen wie folgt weiter:

| Host oder Pfad | Ziel |
| --- | --- |
| `QUIRE_PROXY_CONTENT_HOST` | `content` |
| `QUIRE_PROXY_APP_HOST`, alle Mandanten-Subdomains und benutzerdefinierten Domains | `web` |
| `/_collab/` auf diesen Hosts | `collab` (WebSocket, `QUIRE_COLLAB_URL`) |
| `/_realtime/connection/` auf diesen Hosts | Client-WebSocket von `centrifugo`; dessen Server-API wird nie offengelegt |
| `/_images/` auf diesen Hosts | `imgproxy` mit dem Profil `images` (`IMGPROXY_URL`) |

`init-env.sh` leitet `QUIRE_PROXY_APP_HOST`, `QUIRE_PROXY_CONTENT_HOST`, `QUIRE_PROXY_HTTPS_PORT`, `QUIRE_COLLAB_URL` und `IMGPROXY_URL` aus den beiden Origins ab, damit sie nicht auseinanderlaufen können. Ändern Sie sie gemeinsam, falls Sie eine Origin manuell ändern.

Zertifikate richten sich nach `QUIRE_PROXY_TLS`:

- `internal` (Standard): Die eigene Zertifizierungsstelle von Caddy für `localhost`, `*.localhost` und `lvh.me`. Vertrauen Sie einmalig ihrer Stammzertifizierungsstelle und rufen Sie dann Folgendes auf:

  ```sh
  docker compose -f docker/compose.yaml cp \
    proxy:/data/caddy/pki/authorities/local/root.crt ./quire-local-ca.crt
  ```

  Fügen Sie `quire-local-ca.crt` dem Vertrauensspeicher des Systems oder Browsers hinzu. Bei `curl` übergeben Sie sie mit `--cacert`.
- Eine E-Mail-Adresse: automatische ACME-Zertifikate (Let's Encrypt, danach ZeroSSL) für echte Hostnamen. DNS für beide Origins und jeden Mandantenhost muss auf diesen Host zeigen. Die Ports 80 und 443 müssen aus dem Internet erreichbar sein.

Zertifikate für Mandantenhosts werden bei deren erstem Aufruf nur dann ausgestellt, wenn web bestätigt, dass der Name zu dieser Installation gehört (`/tls-allowed`, über das Compose-Netzwerk angefragt). Ein Wildcard-Zertifikat und ein DNS-Anbieter-Plugin sind nicht erforderlich; eine fremde Person kann daher nicht allein dadurch, dass sie einen Namen auf den Host verweist, die Ausstellung von Zertifikaten veranlassen. Zertifikate und lokale Zertifizierungsstelle liegen im Volume `caddy-data`. Wenn Sie `internal` verwenden, sichern Sie es zusammen mit den übrigen Daten.

Der Web-Tier vertraut `X-Forwarded-For` nur vom Proxy: Dieser hat die feste Adresse `QUIRE_PROXY_ADDRESS` (Standard `172.29.64.10`) im festen Subnetz `QUIRE_COMPOSE_SUBNET`, und `QUIRE_TRUSTED_PROXY_CIDRS` nennt diese Adresse. Falls sich das Subnetz mit einem Netzwerk auf dem Host überschneidet, ändern Sie beides und führen Sie `docker compose down` vor `up` aus.

## Eigenen Reverse-Proxy verwenden <!--quire:behind-your-own-reverse-proxy-->

Wenn Sie stattdessen einen bereits vorhandenen Load-Balancer oder Proxy verwenden möchten, lassen Sie den Dienst `proxy` weg (`docker compose up -d --scale proxy=0`) und beenden Sie TLS vor `web` (8080), `content` (8081), `collab` (1234, WebSocket) und `centrifugo` (8000, WebSocket). Legen Sie die öffentlichen Adressen in `QUIRE_APP_ORIGIN`, `QUIRE_CONTENT_ORIGIN` und `QUIRE_COLLAB_URL` (`wss://`) sowie den Adressbereich Ihres Proxys in `QUIRE_TRUSTED_PROXY_CIDRS` fest.

## Fehlerbehebung <!--quire:troubleshooting-->

- `init` wird mit „QUIRE_DATABASE_ID is not a UUID“ beendet: Legen Sie den Wert mit `uuidgen` fest.
- `web` startet mit „did not start on compose“ wiederholt neu: Im Protokoll steht jede Einstellung, die der Dienst nicht verarbeiten kann, sowie die passende Alternative.
- Ein Rollenpasswort, das nach dem ersten Start in `.env` geändert wird, hat keine Wirkung: Das Initialisierungsskript wird nur einmal ausgeführt. Verwenden Sie `ALTER ROLE`.
- Uploads schlagen mit einem Scanfehler fehl, obwohl `CLAMAV_URL` gesetzt ist: ClamAV lädt beim ersten Start seine Signaturen herunter; das dauert einige Minuten.

Source: https://docs.quirelms.com/de/ops/install/index.mdx
