Friedrich Report · Plattform-Migration
Ein bezahlter Finanz-Newsletter zieht von Ghost auf eine eigene Rails-Plattform, ohne ein einziges Mitglied zu verlieren.
Rails übernimmt Login, Mitglieder, Stripe-Abos, Kurse und Audio-Artikel. Ghost bleibt, wofür es gut ist: Schreiben und Versenden. Zehntausende Mitglieder migriert, null Abweichungen.
- Kunde
- Friedrich Report
- Zeitraum
- seit Juni 2025
- Technik
- Rails 8, SQLite, Hotwire, Ghost, Stripe, Cloudflare Stream, OpenRouter, Hetzner
Ausgangslage
Der Friedrich Report ist ein bezahlter Newsletter für Anleger, mit Artikeln, Videokursen, Webinaren und einer Übersichtsseite mit Aktien und Einstiegszonen. Technisch lief das auf Ghost mit einem selbstgebauten Theme, einem Flask-Service für Zahlungen und Kurse und ein paar Servern bei AWS.
Ghost war für das Schreiben gut. Für alles andere wurde es zur Blackbox: Abo-Logik, Kurse, eigene Seiten, Auswertungen. Das Team ist klein, also musste die Lösung mit wenig Leuten betreibbar bleiben.
Mit dem Team habe ich einen wöchentlichen Termin. Seit Juni 2025 bin ich technisch für die Plattform verantwortlich. Angefangen habe ich auf Ghost, mit viel Stabilisierung und Sicherheitsarbeit. Danach habe ich den kompletten Umbau und die Migration auf Rails gemacht. Inzwischen arbeite ich immer enger mit dem Entwickler-Team des Friedrich Report zusammen.
Was ich hier baue
Raus aus AWS, rein in Docker
Der alte Produktionsserver lief noch auf Ghost 5, ein Update ging nur mit einem Umbau. Im Mai 2026 habe ich Ghost auf Version 6 in Docker auf einen Hetzner-Server gezogen: Wartungsseite an, Inhalte und Datenbank rüber, Zeilen gezählt, DNS umgestellt. Der alte Server blieb als Rückweg stehen. Backups laufen seitdem täglich verschlüsselt nach außen.
Rails neben Ghost, nicht statt Ghost
Seit März 2026 baue ich eine Rails-8-Anwendung, die alles übernimmt, was kein Schreiben ist: Login per Magic Link, Mitglieder, Stripe-Abos, Checkout, Gutscheine, Videokurse mit Fortschritt, eine Volltextsuche und einen Admin-Bereich für das Team. Ghost bleibt Editor und Newsletter-Versand. Rails holt die Inhalte über die API und rendert die öffentliche Seite selbst.
Die Entscheidung war bewusst langweilig: eine Rails-App, SQLite, Hotwire, wenig Abhängigkeiten, Deployment über Docker auf einen Server. Kein React, kein Microservice-Zoo.
Der Umzug in einer Nacht
Am 5. Juni 2026 haben wir umgeschaltet. Vorher habe ich ein Skript gebaut, das Mitglieder, Stripe-Kunden und Abos aus Ghost synchronisiert, und das mehrfach gegen echte Daten geprobt. Am Abend: Hinweis-Mail an die Leser, alter Server eingefroren, finaler Sync, DNS um.
Danach habe ich nicht auf ein grünes Dashboard vertraut, sondern gezählt. Ghost und Rails haben exakt gleich viele Mitglieder, keine E-Mail existiert nur auf einer Seite. Diese Drift-Prüfung läuft seitdem als fester Check weiter.
Videokurse auf Cloudflare Stream
Vimeo musste weg. Ich habe Cloudflare und Bunny verglichen und im Juli 2026 die Kurswiedergabe auf Cloudflare Stream umgestellt. Lektionen synchronisieren sich über strukturierte Metadaten, während der Migration sahen Kursteilnehmer einen kurzen Hinweis statt eines kaputten Players.
Jeder Artikel als Audio
Die Redaktion wollte Artikel zum Anhören. Meine Haltung im Meeting: „On the fly TTS macht eh keinen Sinn“, wir erzeugen einmal pro Artikel eine Datei. So ist es gebaut: Ein Ghost-Webhook stößt die Erzeugung an, Rails speichert einen Hash aus Titel und Text und erzeugt nur neu, wenn sich der Text wirklich ändert. Lange Artikel werden in Stücken erzeugt und können nach einem Abbruch weitermachen. Abgespielt wird nur, wer den Artikel auch lesen darf. Über 200 bestehende Artikel liefen als gedrosselter Batch durch.
Zahlungen und Pflichten
Mit dem Team habe ich die Umstellung auf Stripe Managed Payments für internationale Abos vorbereitet, das Onboarding angepasst und veraltete Abo-Zustände mit Stripe abgeglichen. Im Juli 2026 kam der EU-Widerrufsbutton nach § 356a BGB dazu, als schlankes Formular mit Name und Mitglieds-Mail.
Ein Designsystem für alle Seiten
Das Team hat eine eigene Komponentenbibliothek gebaut, „Friedrichwind“, auf Basis von Tailwind. Ich habe zuerst alle 32 verbliebenen Ghost-Seiten inventarisiert und entschieden, was weg kann. Dann habe ich die Rails-Seiten auf die Bibliothek umgestellt, inklusive Navigation, Rechtstexten, FAQ und Sitemap. Danach liefen auf main 1.298 Tests grün. Nur die Artikel kommen noch aus Ghost.
Ein eigenes CMS, und wann man es pausiert
Für Stellenanzeigen habe ich ein kleines CMS im Admin gebaut, mit Vorlage, Archiv und Online-Schalter. Für Artikel habe ich einen Prototyp mit Lexxy gebaut: feste Abschnitte wie Charttechnik oder Investmentcheck, Revisionen statt Überschreiben, links Editor, rechts Vorschau. Als klar wurde, dass das finale Design noch offen ist, haben wir das Artikel-CMS bewusst auf Eis gelegt. Lieber warten als zweimal bauen.
Betrieb mit einfachen Mitteln
Kapazität messe ich mit Rails-Logs, einem kleinen Lastprobe-Skript und klaren Health-Stufen: ein billiger Check für Uptime, ein tieferer stündlich, der auch prüft, ob die Datenbank-Backups frisch sind. Fehler in Produktion landen automatisch als GitHub-Issue.
Wie ich arbeite
Die meiste Arbeit machen Agenten in Cloud-Sandboxes, ich gebe die Richtung vor und prüfe. Jede Rails-Änderung geht vor dem Merge durch ein vollständiges Review nach 37signals-Konventionen, nicht nur eine Suche nach Blockern. Produktionsdaten schaue ich nur lesend an, Änderungen laufen über Pull Requests auf dev und dann auf main. Abhängigkeits-Updates erledigt ein eigener Agenten-Skill im Wochentakt: erst auf dev mergen, testen, dann ein einziger Pull Request nach main. Der Entwickler im Team baut parallel an Features, deshalb halte ich die Branches sauber und die Doku im Repo aktuell.
Was dabei rauskam
Die Plattform gehört jetzt dem Friedrich Report. Ein Server, eine Rails-App, Ghost als Werkzeug für die Redaktion. Zehntausende Mitglieder sind ohne Abweichung umgezogen, Kurse laufen auf Cloudflare, Artikel gibt es als Audio, und das Team kann neue Seiten aus der eigenen Bibliothek bauen.
Im letzten Termin hieß es: technisch läuft alles stabil. Der Fokus liegt jetzt auf Watchlist, Dashboard und App, nicht mehr auf Feuerlöschen.
Ein ähnliches Problem? Schreib mir.