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.

Internet · Port 80 / 443TraefikReverse Proxy · TLS via Let’s EncryptCockpitSvelteKit · Dockercockpit.raspb.eu · :3000MedusaCommerce-Backend · Dockermedusa.raspb.eu · :9000Storefronts × NSvelteKit · ein Container je Weingutshop-<slug>.raspb.eu · :3000Admin APIStore APIPostgreSQLpgvector:pg17 · :5432nur Docker-internRedisEvent-Bus · Sessions127.0.0.1 · :6379MinIO / S3Bucket „medusa“s3.raspb.euStripe (Connect)Durchgezogen: interner Verkehr · Gestrichelt: externer Dienst
Systemübersicht: Der gesamte Verkehr betritt die Plattform über Traefik. Cockpit und Storefronts sprechen ausschließlich mit Medusa, nie direkt mit der Datenbank.

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.

Laufende Dienste
KomponenteBetriebsartPortTechnologieStatus
TraefikDocker80 / 443traefik:latestLive
MedusaDocker9000Medusa v2.13.1Live
CockpitDocker3000 (intern)SvelteKit · adapter-nodeLive
StorefrontsDocker × N3000 (intern)SvelteKit · adapter-nodeLive
PostgreSQLDocker5432 (nur intern)pgvector/pgvector:pg17Live
RedisDocker6379 (127.0.0.1)redis:alpineLive
MinIODocker9000 / 9001minio:latestLive
RegistryDocker5000registry:2Live
AnalyticsDocker8000 (intern)Plausible CommunityLive

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.

Traefik-Routing
DomainZielZweck
medusa.raspb.euMedusa-Container :9000Commerce-API (Store + Admin)
cockpit.raspb.eucockpit:3000Admin-Oberfläche für Winzer
shop-<slug>.raspb.eustorefront-<slug>:3000Shop eines Weinguts
shop.raspb.eustorefront-landing:3000Diese Landingpage
s3.raspb.euminio:9000Medien-Auslieferung (S3-API)
minio.raspb.euminio:9001MinIO-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.

web externes Bridge-Netzwerk

Alles, was von außen erreichbar sein muss.

  • Traefik :80/:443
  • Cockpit :3000
  • Medusa :9000
  • Storefronts :3000 × N
  • MinIO :9000/:9001
  • Registry :5000
  • Analytics
root_default internes Compose-Netzwerk

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.

  1. Browser
  2. Traefik (TLS)
  3. Storefront-Container
  4. Medusa Store API
  5. PostgreSQL
  6. MinIO (Bilder)
  7. HTML an den Browser

Winzer arbeitet im Cockpit

Nach dem Login läuft die Oberfläche als Single-Page-Anwendung weiter.

  1. Browser
  2. Traefik (TLS)
  3. Cockpit-Container
  4. Medusa Admin API (JWT)
  5. PostgreSQL
  6. MinIO (Upload)

Kundin bezahlt

Der Payment Intent entsteht im Stripe-Konto des Weinguts, nicht im Plattform-Konto.

  1. Checkout im Shop
  2. Medusa Payment-Modul
  3. Stripe Connect (Weingut)
  4. Webhook zurück an Medusa
  5. 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 gleich
  • TENANT_PUBLISHABLE_KEY — Store-API-Key des Mandanten
  • TENANT_SALES_CHANNEL_ID — die Mandantengrenze
  • TENANT_REGION_ID — Währung, Steuersatz, Lieferländer
  • TENANT_SHOP_DOMAIN und TENANT_SHOP_NAME — Domain und Anzeigename
  • TENANT_STRIPE_ACCOUNT_ID — das Stripe-Connect-Konto des Weinguts

Sicherheit

Der ehrliche Stand — inklusive dem, was noch offen ist.

Sicherheitsmaßnahmen
BereichStandBeschreibung
TLS / HTTPSErledigtLet’s Encrypt über Traefik, automatische Erneuerung
PostgreSQL-PortErledigtNicht mehr auf dem Host geöffnet, nur Docker-intern
RedisErledigtPasswortgeschützt, gebunden an 127.0.0.1
Medusa AdminErledigtJWT-Authentifizierung, Scope über Sales Channel
MinIOErledigtAccess Key und Secret Key
BackupsErledigtNächtlicher pg_dump per Cron, externe Ablage
SecretsOffenUmgebungsvariablen liegen im Klartext auf dem Host
Host-FirewallOffenKeine 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.