Kapitel 1
Systemarchitektur
Die Plattform besteht aus drei Anwendungsschichten und drei Infrastrukturdiensten hinter einem einzigen Reverse Proxy. Dieses Kapitel zeigt, was wo läuft, wie die Teile miteinander sprechen und wo die Grenzen zwischen den Mandanten verlaufen.
Systemübersicht
Aller Verkehr betritt die Plattform über Traefik. Der Reverse Proxy terminiert TLS, erzwingt HTTPS und verteilt die Anfragen anhand des Hostnamens auf die Container dahinter. Kein Anwendungsdienst ist direkt aus dem Internet erreichbar.
Die drei Anwendungen darunter haben klar getrennte Aufgaben: Das Cockpit ist die Oberfläche für Winzer, die Storefronts sind die Shops für die Kundschaft, und Medusa ist die einzige Instanz, die tatsächlich mit der Datenbank spricht. Weder Cockpit noch Storefront haben eine Datenbankverbindung — sie kennen nur HTTP-Endpunkte.
Komponenten im Überblick
Alle Dienste laufen als Docker-Container auf einem einzelnen Server. Die Ports in der Tabelle sind Container-Ports; nach außen sind nur 80 und 443 offen.
| Komponente | Betriebsart | Port | Technologie | Status |
|---|---|---|---|---|
| Traefik | Docker | 80 / 443 | traefik:latest | Live |
| Medusa | Docker | 9000 | Medusa v2.13.1 | Live |
| Cockpit | Docker | 3000 (intern) | SvelteKit · adapter-node | Live |
| Storefronts | Docker × N | 3000 (intern) | SvelteKit · adapter-node | Live |
| PostgreSQL | Docker | 5432 (nur intern) | pgvector/pgvector:pg17 | Live |
| Redis | Docker | 6379 (127.0.0.1) | redis:alpine | Live |
| MinIO | Docker | 9000 / 9001 | minio:latest | Live |
| Registry | Docker | 5000 | registry:2 | Live |
| Analytics | Docker | 8000 (intern) | Plausible Community | Live |
Domains und Routing
Root-Domain ist raspb.eu. Jeder Dienst bekommt eine eigene Subdomain,
jedes Weingut eine Shop-Subdomain nach dem Muster shop-<slug>.raspb.eu — oder, sobald das Weingut umgestellt hat, die eigene Domain.
| Domain | Ziel | Zweck |
|---|---|---|
| medusa.raspb.eu | Medusa-Container :9000 | Commerce-API (Store + Admin) |
| cockpit.raspb.eu | cockpit:3000 | Admin-Oberfläche für Winzer |
| shop-<slug>.raspb.eu | storefront-<slug>:3000 | Shop eines Weinguts |
| shop.raspb.eu | storefront-landing:3000 | Diese Landingpage |
| s3.raspb.eu | minio:9000 | Medien-Auslieferung (S3-API) |
| minio.raspb.eu | minio:9001 | MinIO-Konsole (intern) |
Storefronts und Cockpit melden sich über Docker-Labels bei Traefik an. Ein neuer Container trägt seine Route also selbst bei — es muss keine zentrale Konfigurationsdatei angefasst und kein Proxy neu geladen werden. Das Zertifikat holt Traefik beim ersten Aufruf über die TLS-Challenge und erneuert es danach automatisch.
Docker-Netzwerke
Zwei Netzwerke trennen das, was von außen erreichbar sein muss, von dem, was es nicht sein darf. PostgreSQL und Redis liegen ausschließlich im internen Netz und haben keinen veröffentlichten Port.
Alles, was von außen erreichbar sein muss.
- Traefik :80/:443
- Cockpit :3000
- Medusa :9000
- Storefronts :3000 × N
- MinIO :9000/:9001
- Registry :5000
- Analytics
Kein Port nach außen. Erreichbar nur aus dem Netz heraus.
- PostgreSQL :5432
- Redis :6379
Traefik ist der einzige Container, der beide Welten berührt: Er hängt im Netz web und ist als einziger von außen ansprechbar.
Datenfluss
Drei Wege beschreiben praktisch alles, was in der Plattform passiert.
Kundin besucht einen Shop
Serverseitig gerendert — die erste Antwort enthält bereits das fertige HTML.
- Browser
- Traefik (TLS)
- Storefront-Container
- Medusa Store API
- PostgreSQL
- MinIO (Bilder)
- HTML an den Browser
Winzer arbeitet im Cockpit
Nach dem Login läuft die Oberfläche als Single-Page-Anwendung weiter.
- Browser
- Traefik (TLS)
- Cockpit-Container
- Medusa Admin API (JWT)
- PostgreSQL
- MinIO (Upload)
Kundin bezahlt
Der Payment Intent entsteht im Stripe-Konto des Weinguts, nicht im Plattform-Konto.
- Checkout im Shop
- Medusa Payment-Modul
- Stripe Connect (Weingut)
- Webhook zurück an Medusa
- Bestellung + E-Mail
Mandantentrennung
Alle Weingüter teilen sich eine Datenbank. Getrennt werden sie über den Sales Channel von Medusa — ein Konzept, das ohnehin für Vertriebskanäle vorgesehen ist und hier als Mandantengrenze dient.
Auf der Kundenseite
Jede Storefront kennt genau eine sales_channel_id und einen Publishable API Key, der auf diesen Channel eingeschränkt ist. Die Store API
liefert damit ausschließlich Produkte dieses Kanals aus. Selbst wenn jemand die
Produkt-ID eines fremden Weinguts errät, kommt mit dem falschen Key nichts zurück.
Auf der Winzerseite
Der Cockpit-Benutzer trägt seine sales_channel_id in den Metadaten. Jeder
Lese- und Schreibzugriff über die Admin-Routen wird auf diesen Wert eingeschränkt —
Weine, Bestellungen, Beiträge, Lagen, Rebsorten und das Winzer-Profil.
Was pro Mandant konfiguriert wird
TENANT_BASE_URL— die Medusa-Instanz, für alle gleichTENANT_PUBLISHABLE_KEY— Store-API-Key des MandantenTENANT_SALES_CHANNEL_ID— die MandantengrenzeTENANT_REGION_ID— Währung, Steuersatz, LieferländerTENANT_SHOP_DOMAINundTENANT_SHOP_NAME— Domain und AnzeigenameTENANT_STRIPE_ACCOUNT_ID— das Stripe-Connect-Konto des Weinguts
Sicherheit
Der ehrliche Stand — inklusive dem, was noch offen ist.
| Bereich | Stand | Beschreibung |
|---|---|---|
| TLS / HTTPS | Erledigt | Let’s Encrypt über Traefik, automatische Erneuerung |
| PostgreSQL-Port | Erledigt | Nicht mehr auf dem Host geöffnet, nur Docker-intern |
| Redis | Erledigt | Passwortgeschützt, gebunden an 127.0.0.1 |
| Medusa Admin | Erledigt | JWT-Authentifizierung, Scope über Sales Channel |
| MinIO | Erledigt | Access Key und Secret Key |
| Backups | Erledigt | Nächtlicher pg_dump per Cron, externe Ablage |
| Secrets | Offen | Umgebungsvariablen liegen im Klartext auf dem Host |
| Host-Firewall | Offen | Keine ufw-/iptables-Regeln aktiv |
Skalierung
Heute läuft alles auf einem Server. Das ist eine bewusste Entscheidung: Bei einer einstelligen Zahl von Weingütern kostet verteilte Infrastruktur mehr Aufmerksamkeit, als sie einbringt.
Vertikal — der aktuelle Weg
Mehr RAM und CPU für den Host, dazu PostgreSQL-Tuning. Trägt komfortabel bis in den Bereich von etwa 20 bis 30 Mandanten.
Horizontal — der vorbereitete Weg
- PostgreSQL auf einen eigenen Server oder einen Managed Service
- Redis auf eine eigene Instanz
- Medusa mehrfach hinter einem Load Balancer
- Storefronts beliebig verteilen — sie sind zustandslos und nur über Umgebungsvariablen konfiguriert
- MinIO gegen einen S3-Anbieter tauschen; die Schnittstelle bleibt dieselbe
Portabilität
Ein Serverumzug besteht aus fünf Schritten: Datenbank per pg_dump sichern
und einspielen, Images aus der Registry ziehen, .env übernehmen,
Compose-Stack starten, DNS umstellen. Es gibt keine Bindung an einen Hoster.