Kapitel 1
Systemarchitektur
Die Plattform besteht aus den Shop-Anwendungen, den Self-Service-Diensten und gemeinsam genutzter Infrastruktur — alles hinter einem einzigen Reverse Proxy. Dieses Kapitel zeigt, was wo läuft, wie die Teile miteinander sprechen, wie ein neuer Shop entsteht 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.
Daneben stehen die Self-Service-Dienste: die Landingpage mit der Registrierung, die Self-Service-API, die Accounts, Abos und die Einrichtung neuer Shops verwaltet, und das Master Dashboard für den Betreiber. Beide Dienste halten ihre eigenen Daten in einer kleinen SQLite-Datenbank; die Shop-Daten selbst bleiben in Medusa.
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 |
| Self-Service-API | Docker | 3000 (intern) | SvelteKit · SQLite | Live |
| Master Dashboard | Docker | 3000 (intern) | SvelteKit · SQLite | Live |
| Landingpage | Docker | 3000 (intern) | SvelteKit · adapter-node | Live |
| Registry | Docker | 5000 | registry | Live |
| Analytics | Docker | 3000 (intern) | Umami | Live |
Domains und Routing
Stammdomain ist wein-online-verkaufen.de. Jeder Dienst bekommt eine eigene
Subdomain, jedes Weingut eine Shop-Subdomain nach dem Muster <slug>.wein-online-verkaufen.de — oder, wenn es eine verbunden hat, die
eigene Domain.
| Domain | Ziel | Zweck |
|---|---|---|
| wein-online-verkaufen.de | storefront-landing:3000 | Landingpage mit Registrierung (/register) und dieser Dokumentation |
| cockpit.wein-online-verkaufen.de | cockpit:3000 | Cockpit der Weingüter — vom kostenlosen Account bis zum laufenden Shop |
| admin.wein-online-verkaufen.de | weinshop-admin:3000 | Master Dashboard des Betreibers |
| self-service.wein-online-verkaufen.de | self-service:3000 | Self-Service-API: Accounts, Abo, Provisioning |
| medusa.wein-online-verkaufen.de | medusa:9000 | Commerce-API (Store + Admin), intern genutzt |
| <slug>.wein-online-verkaufen.de | storefront-<slug>:3000 | Shop eines Weinguts |
| s3.wein-online-verkaufen.de | minio:9000 | Medien-Auslieferung (S3-API) |
Zwei Wege führen zu einer Route. Cockpit, Medusa und Self-Service-API melden sich über Docker-Labels an. Landingpage, Master Dashboard und alle Shops haben eine Dateiroute im Verzeichnis traefik/dynamic, das Traefik laufend
beobachtet — für neue Shops schreibt die Provisioning-Pipeline diese Datei selbst. In keinem
Fall muss der Proxy neu geladen werden.
Für *.wein-online-verkaufen.de gibt es einen Wildcard-DNS-Eintrag auf den Server. Eine neue Shop-Subdomain braucht deshalb keinen DNS-Schritt. Das Zertifikat
holt Traefik beim ersten Aufruf über die TLS-Challenge — eines pro Hostname, kein
Wildcard-Zertifikat — und erneuert es danach automatisch.
Self-Service und Provisioning
Neue Weingüter kommen ohne Zutun des Betreibers auf die Plattform. Drei Dienste teilen sich die Arbeit:
- Landingpage — nimmt unter
/registerdie E-Mail-Adresse entgegen - Self-Service-API — legt den Account an, verschickt den Anmeldelink, startet den Stripe Checkout fürs Abo, empfängt den Stripe-Webhook und führt die Provisioning-Pipeline aus
- Cockpit — führt das Weingut durch Stammdaten, Tarifwahl und Zahlung und zeigt den Fortschritt der Einrichtung
Die Provisioning-Pipeline startet erst nach bestätigter Zahlung. Sie legt in Medusa Schlüssel, Benutzer, Sales Channel sowie Lager und Versand an, schreibt die Cockpit-Stammdaten, erzeugt Medien-Bucket und Stripe-Connect-Konto, startet den Storefront-Container aus dem Template-Image, schreibt die Traefik-Route und schickt am Ende die Go-live-E-Mail. Jeder Schritt wird geprüft; scheitert einer, wird zurückgerollt. Die vollständige Liste steht im Onboarding-Kapitel.
Status-Modell
Jedes Weingut trägt in der Self-Service-API einen Status. Der reguläre Weg führt von registered über onboarding zu active.
| Status | Ausgelöst durch | Bedeutung |
|---|---|---|
| registered | Registrierung mit E-Mail-Adresse | Anmeldelink verschickt, noch keine Stammdaten |
| onboarding | Stammdaten und Passwort gespeichert | Kostenloser Account; nach der Zahlung läuft hier die Einrichtung |
| active | Provisioning-Pipeline abgeschlossen | Shop live, volles Cockpit |
| suspended | Stripe meldet offene Zahlung | Vermerk für den Betreiber, sperrt nichts automatisch |
| cancelled | Abo bei Stripe gekündigt | Vermerk für den Betreiber, sperrt nichts automatisch |
Unabhängig vom Status gibt es die Sperre („freeze“), die der Betreiber im Master Dashboard setzt und wieder aufhebt. Ein gesperrter Shop leitet auf eine Hinweisseite um, nur Impressum und Datenschutz bleiben erreichbar; das Cockpit des Weinguts zeigt ebenfalls nur noch den Hinweis. Ist die Self-Service-API nicht erreichbar, bleiben Shops offen — die Sperre ist bewusst fail-open.
Master Dashboard
Unter admin.wein-online-verkaufen.de sieht der Betreiber alle Weingüter:
Interessenten und frisch Registrierte unter „Prospects“, alle Weingüter mit Stammdaten
unter „Kunden“ — mit Tarif, Status und Stand der Einrichtung. Dazu kommen das Feedback aus
den Cockpits und die Plattform-Konfiguration. Von hier aus werden Shops gesperrt und
reaktiviert.
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
Vier 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)
Weingut bucht einen Tarif
Erst die bestätigte Zahlung startet die Einrichtung — Webhook oder Rückkehr aus Stripe, je nachdem, was zuerst ankommt.
- Cockpit /shop-einrichten
- Self-Service-API
- Stripe Checkout
- Webhook an Self-Service-API
- Provisioning-Pipeline
- Shop unter <slug>.wein-online-verkaufen.de
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.