Big Picture¶
Diese Seite zeigt den kompletten Stack, so wie er auf dieser Instanz (Dev,
hocx.tweber.ch) tatsächlich läuft: alle Container, welche Daten sie halten und wie sie
miteinander sprechen. Auf Test/Prod ist die Struktur identisch — dort laufen dieselben
Services nur aus fertigen GHCR-Images statt aus lokalem Source-Build (siehe
Deployment).
Diagramm¶
flowchart TB
Internet(("Internet"))
subgraph Edge["Traefik – Reverse Proxy / TLS"]
Traefik["traefik"]
end
Internet -->|"hocx.tweber.ch"| Traefik
Internet -->|"upload.tweber.ch"| Traefik
Internet -->|"docs.hocx.tweber.ch"| Traefik
Internet -->|"Custom-Domains je Mandant"| Traefik
subgraph Main["Hauptanwendung"]
Frontend["frontend<br/>Next.js"]
Backend["backend<br/>FastAPI"]
end
subgraph AB["Abgabebox – öffentlich, kein Login"]
ABFrontend["abgabebox-frontend<br/>Next.js"]
ABBackend["abgabebox-backend<br/>FastAPI"]
end
subgraph DocsGroup["Dokumentation"]
DocsSvc["docs<br/>nginx / MkDocs"]
end
subgraph Data["Daten"]
DB[("db<br/>PostgreSQL 16")]
Redis[("redis<br/>ephemer, kein Volume")]
ClamAV["clamav"]
Storage["Storage-Volume<br/>uploads / exports / latex_templates"]
ABStorage["Storage-Volume<br/>abgabebox-uploads"]
TraefikDyn["infra/traefik/dynamic<br/>generierte Router-Configs"]
end
Traefik -->|"Host-Routing"| Frontend
Traefik -->|"/api, /docs, /openapi.json"| Backend
Traefik --> ABFrontend
Traefik -->|"/api"| ABBackend
Traefik --> DocsSvc
Frontend -.->|"SSR-Fetch über öffentliche Domain (INTERNAL_API_URL)"| Traefik
ABFrontend -.->|"SSR-Fetch über öffentliche Domain (INTERNAL_ABGABEBOX_API_URL)"| Traefik
Backend --> DB
Backend --> Redis
Backend --> Storage
Backend -->|"read-write Mount"| ABStorage
Backend -->|"generiert"| TraefikDyn
TraefikDyn -.->|"gelesen von"| Traefik
ABBackend -->|"restricted Rolle hocx_abgabebox"| DB
ABBackend --> ABStorage
ABBackend -->|"Virenscan"| ClamAV
Warum Frontend → Backend über die öffentliche Domain läuft, nicht direkt
Die gestrichelten Pfeile (Frontend/Abgabebox-Frontend zurück zu Traefik) sind kein Zeichen einer Fehlkonfiguration: server-seitige Fetches laufen bewusst über die öffentliche Domain statt direkt per Docker-Servicenamen zum Backend-Container. Direkte Verbindungen unter echter Nebenläufigkeit haben in diesem Setup nachweislich zu gelegentlich falschen Antworten von uvicorn geführt (siehe Bekannte offene Punkte). Über Traefik ist der Pfad stabil.
Komponenten im Überblick¶
| Komponente | Typ | Zweck | Genutzt von | Hält Daten in |
|---|---|---|---|---|
traefik |
Reverse Proxy | TLS-Terminierung (Let's Encrypt), Host-basiertes Routing für alle Domains | jeder eingehende Request aus dem Internet | infra/traefik/letsencrypt (Zertifikate), liest infra/traefik/dynamic |
frontend |
Next.js App | Kunden-UI und Platform-Admin-Panel (/admin) |
Endnutzer (Vereine + Betreiber-Admins) | kein eigener State, SSR-Fetches gegen backend |
backend |
FastAPI | Haupt-API, Business-Logik, PDF-Export, WebSocket-Kollaboration, generiert Traefik-Router für Custom-Domains | frontend (SSR + Client via /api) |
db (volle Rolle hocx), redis, storage/, abgabebox-uploads (read-write) |
db |
PostgreSQL 16 | Zentrale Datenbank für Haupt- und Abgabebox-Daten, über getrennte Rollen isoliert | backend (volle Rolle), abgabebox-backend (restricted Rolle) |
Volume postgres_data, nur an 127.0.0.1 gebunden |
redis |
Redis 7 | Ephemerer State für Live-Kollaboration im Protokoll-Editor (Presence, Zell-/Feldsperren, Pub/Sub) | backend (WebSocket-Route /api/ws/protocols/{id}) |
kein Volume, kein Passwort, nur intern im Compose-Netz erreichbar |
abgabebox-frontend |
Next.js App | Öffentliche, anmeldefreie Upload-Oberfläche | externe Personen ohne hocX-Account | kein eigener State, SSR-Fetches gegen abgabebox-backend |
abgabebox-backend |
FastAPI | Upload-Annahme, Magic-Byte-Prüfung, ClamAV-Anbindung | abgabebox-frontend |
db (restricted Rolle hocx_abgabebox), storage/abgabebox-uploads |
clamav |
ClamAV | Virenscan aller Abgabebox-Uploads | abgabebox-backend |
Volume clamav_db (Signaturdatenbank) |
docs |
nginx + MkDocs (statischer Build) | diese Dokumentation | Team + Endnutzer | kein State |
Domain → Service¶
| Domain | Zeigt auf | Bemerkung |
|---|---|---|
hocx.tweber.ch |
frontend (alles) + backend (/api, /docs, /openapi.json) |
Haupt-UI inkl. /admin, dazu die separat rate-limitierten Login-Routen /api/auth/login und /api/admin/auth/login |
upload.tweber.ch |
abgabebox-frontend (alles) + abgabebox-backend (/api) |
/api/public/* (POST) hat ein eigenes, engeres Rate-Limit gegen Spam |
docs.hocx.tweber.ch |
docs |
diese Seite |
Custom-Domains einzelner Mandanten (z. B. hocx.jwsachseln.ch) |
frontend/backend |
Router werden vom backend zur Laufzeit generiert und landen als Datei in infra/traefik/dynamic, die Traefik automatisch einliest |
Zwei komplett getrennte Auth-/Datenwelten¶
Das Diagramm zeigt technisch einen gemeinsamen db-Container, aber inhaltlich existieren
drei voneinander unabhängige Zugriffs-/Datenwelten darin, die bewusst nicht
miteinander verknüpft sind:
- Kunden-Login (
app_user, Session-Cookie, SecretAUTH_SECRET) — tenant-gescopt, sieht nie mehr als die eigenen Vereine. - Platform-Admin-Login (
platform_admin, eigenes Session-Cookiehocx_admin_session, eigenes SecretADMIN_AUTH_SECRET) — einzige Stelle mit mandantenübergreifender Sicht, siehe Platform-Admin-Panel. - Abgabebox — läuft komplett ohne Login, greift nur über die restricted
DB-Rolle
hocx_abgabebox(REVOKE-ALL-Baseline + Allowlist) auf einen kleinen Tabellenausschnitt zu; selbst ein kompletter Kompromiss vonabgabebox-backendgibt keinen Zugriff auf Kunden- oder Admin-Daten.
Details zu den Sicherheitsgrenzen zwischen diesen Welten stehen auf der Sicherheits-Seite.