Bereitstellung auf Data Clerk
Diese App läuft auf Data Clerk, einem kleinen, von einem Betreiber (einer Person, Kontakt in PROJECT.md) verwalteten Kubernetes-Cluster. Der Besitzer dieses Repos ist normalerweise keine Infrastruktur-Person. Er sollte niemals Server, Kubernetes oder eine Cloud-Konsole berühren müssen: Er pusht in main, und der Cluster erledigt den Rest. Deine Aufgabe ist es, das so zu halten: Arbeite innerhalb des Repos, befolge den untenstehenden Vertrag und schreibe dem Betreiber eine klare Anfrage, wenn etwas wirklich auf der Clusterseite erledigt werden muss, anstatt zu improvisieren.
Erkläre dem Benutzer Dinge in einfachen Worten ("Deine Seite wird in etwa fünf Minuten aktualisiert"), nicht in Kubernetes-Begriffen.
Starte hier
- Lies
deploy-data-clerk/PROJECT.md. Es enthält die Werte dieses Projekts: öffentliche URL, das Bereitstellungsverzeichnis, den Image-Namen, den Port, den Migrationsbefehl, Limits und wer der Betreiber ist. Alles, was unten "aus PROJECT.md" sagt, bezieht sich auf diese Datei. Wenn der Ordnername in PROJECT.md vondeploy-data-clerk/abweicht (ältere Projekte verwendendeploy/), verwende das, was PROJECT.md sagt. - Keine
PROJECT.mdoder sie enthält noch__PLACEHOLDER__-Werte? Das Projekt ist noch nicht onboarded: Folgereferences/new-project.md. - Du schreibst oder änderst ein Dockerfile, entscheidest, wie du die App strukturierst, oder der Stack ist nicht Node? Lies
references/stacks.md(jede Sprache, Full-Stack vs. getrenntes Frontend + API, FastAPI, Go). - Bevor du die erste Secrets-Aufgabe auf einem Rechner ausführst, stelle sicher, dass die Tools funktionieren:
sops --versionund eine Test-Entschlüsselung (sops decrypt <deploy folder>/secrets.env > /dev/null). Wenn etwas fehlschlägt, folgereferences/setup-tools.md.
Wie eine Änderung die Live-Seite erreicht
| Was sich geändert hat | Was passiert | Live nach |
|---|---|---|
Code (Push in main) | GitHub Actions erstellt ein Docker-Image und pusht es; der Cluster bemerkt den neuen Image-Tag und rollt es aus | ~3–7 Min (Build-Zeit + ~1 Min) |
secrets.env (Push in main) | Der Cluster liest alle 5 Minuten das Bereitstellungsverzeichnis neu, entschlüsselt es, aktualisiert die Umgebung der App und startet die App neu | ~5 Min |
Eine neue Datei in migrations/ | Wird automatisch ausgeführt, bevor die neue Version startet | mit dem Code-Deploy |
Alles außerhalb von main | Nichts. Branches und PRs werden nicht deployed | — |
Die Ausrollung erfolgt vorsichtig: Die neue Version startet neben der alten, Migrationen werden zuerst ausgeführt, und der Datenverkehr wechselt erst, wenn die neue Version ihren Healthcheck besteht. Wenn der Build, eine Migration oder der Healthcheck fehlschlägt, bedient die alte Version weiter und die Seite bleibt online. Ein fehlgeschlagener Deploy bedeutet "die Änderung ist nicht live", nicht "die Seite ist down".
Um einen Deploy zu überprüfen: gh run list --workflow "Deploy to cluster" --limit 3 (oder den Reiter "Actions" auf GitHub), dann öffne die öffentliche URL. Um ohne Codeänderung neu zu deployen: gh workflow run "Deploy to cluster", das einen neuen Tag erstellt, sodass die App mit einer frischen Umgebung neu startet.
Der Vertrag (was Deploys funktionieren lässt)
Jede Regel existiert, weil ein Verstoß sie stillschweigend fehlschlagen lässt oder Daten verloren gehen. Halte dich daran und sage dem Benutzer, wenn eine Anfrage eine Regel verletzen würde.
- Höre auf
$PORT(der Port in PROJECT.md, vom Cluster gesetzt), auf0.0.0.0, nichtlocalhost. - Das Image wird aus
<deploy folder>/Dockerfilefürlinux/amd64erstellt und läuft als Nicht-Root-Benutzer. Kein Root, keine Ports unter 1024, keine zusätzlichen Linux-Fähigkeiten. - Image-Tags sind
YYYYMMDDHHMMSS-<shortsha>. Der Cluster deployt die höchste Zahl und ignoriert stillschweigend jeden Tag, der nicht in diesem Format ist. Ändere niemals den Tag-Schritt in.github/workflows/deploy-cluster.yml. - Die Festplatte des Containers wird bei jedem Deploy und Neustart gelöscht. Behalte niemals Benutzer-Uploads, SQLite-Dateien oder etwas Wertvolles auf der lokalen Festplatte. Lege Daten in der Datenbank ab; für Dateien (Fotos, Videos) frage den Betreiber nach Objektspeicher.
- Healthcheck: Der Cluster prüft den Health-Pfad in PROJECT.md (
/healthzempfohlen). Lass ihn schnell 200 zurückgeben, ohne Datenbankaufruf oder Seitenrendering, da eine langsame Datenbank den Cluster glauben lässt, die App sei tot. DATABASE_URLkommt vom Cluster. Setze sie niemals insecrets.env(das würde die echte überschreiben) und setze sie niemals zur Image-Build-Zeit.- Laufzeit vs. Build-Zeit.
secrets.enverreicht die App nur zur Laufzeit. Das Image wird in GitHub Actions ohne sie erstellt. Alles, was zur Build-Zeit in das Frontend eingebunden wird (VITE_*,NEXT_PUBLIC_*,REACT_APP_*), kann nicht aussecrets.envkommen: Lies solche Werte zur Laufzeit auf dem Server und gib sie an die Seite weiter, oder (falls nicht geheim) committe sie im Code. - Secrets, die die App für sich selbst generiert, wenn sie fehlen (Auth/Session-Secret, Verschlüsselungs-Salze, Push-Schlüssel): Setze sie in
secrets.env. Andernfalls erstellt jeder Neustart neue, Benutzer werden ausgeloggt, Push-Abonnements brechen und so weiter. - Migrationen müssen für die alte Version sicher sein. Für etwa eine Minute laufen beide Versionen gegen das neue Schema. Füge Spalten und Tabellen frei hinzu. Benenne um oder lösche in zwei Deploys: Verwende es zuerst nicht mehr, dann entferne es. Details in
references/database.md.
Umgebungsvariablen und Secrets
Die gesamte Laufzeitkonfiguration lebt in <deploy folder>/secrets.env, verschlüsselt mit sops committet. Nur Werte sind verschlüsselt; Schlüsselnamen bleiben lesbar. Ändere sie mit sops selbst. Entschlüssele sie niemals in eine Datei, die du committen könntest, und gib niemals Secret-Werte in den Chat oder in Logs aus.
sops set deploy-data-clerk/secrets.env '["STRIPE_SECRET_KEY"]' '"sk_live_..."'
sops unset deploy-data-clerk/secrets.env '["OLD_KEY"]'
sops decrypt deploy-data-clerk/secrets.env | cut -d= -f1
Dann committe und pushe in main; es ist in ~5 Min live. Wenn der Benutzer den Wert bereitstellen muss (einen API-Schlüssel von einem Dashboard), lass ihn die sops set-Zeile selbst ausführen oder füge den Wert nur in sein eigenes Terminal ein. Die vollständige Anleitung (Schlüssel, wo sie leben, Rotieren, Fallstricke) befindet sich in references/secrets.md.
Datenbank
Postgres 18, verwaltet vom Betreiber. Die App bekommt ihre eigene Datenbank und ihren eigenen Benutzer über DATABASE_URL. Schemaänderungen sind Migrationsdateien, die sich beim Deploy selbst anwenden (references/database.md).
Backups: Ein vollständiges Backup jede Nacht um 03:00 UTC plus ein kontinuierliches Log jeder Änderung, das außerhalb des Servers in Google Cloud Storage gespeichert wird. Der Betreiber kann die Datenbank zu jedem Zeitpunkt innerhalb der letzten 7 Tage wiederherstellen. Wenn also Daten gelöscht werden oder eine Migration schiefgeht, keine Panik und versuche nicht, es von Hand zu beheben: Notiere den Zeitpunkt und bitte den Betreiber um eine Wiederherstellung (Vorlage in references/troubleshooting.md). Eine Wiederherstellung dreht die gesamte Datenbank zurück, sodass Änderungen, die nach diesem Zeitpunkt vorgenommen wurden, verloren gehen.
Netzwerk-Fakten, die dein Code kennen könnte
- Verkehrsweg: Besucher → Cloudflare → Traefik (Cluster-Ingress) → App. HTTPS-Zertifikate sind automatisch. Die App selbst spricht einfaches HTTP auf
$PORT;X-Forwarded-Proto: httpssagt, dass das Original HTTPS war. Vertraue den Proxy-Headern und leite in der App nicht auf HTTPS um (das würde eine Schleife erzeugen). - Echte Besucher-IP: Lies den
CF-Connecting-IP-Header.X-Forwarded-Forenthält eine Cloudflare-Adresse, daher würde eine Ratenbegrenzung darauf alle Besucher in einen Topf werfen. - Traefik fügt diese Header hinzu: HSTS (2 Jahre, Preload),
X-Content-Type-Options: nosniff,X-Frame-Options: SAMEORIGIN(die Seite kann nicht in einem Iframe auf anderen Domains eingebettet werden). Zusätzliche Header (CSP, COOP/COEP fürSharedArrayBuffer, Erlauben von Iframes) werden vom Betreiber auf Anfrage gesetzt. - Der Ingress komprimiert Antworten nicht. Cloudflare komprimiert gängige Texttypen; für die volle Kontrolle komprimiere in der App oder liefere vor-komprimierte Assets aus.
- Lange Verbindungen (SSE, Websockets) sind in Ordnung, aber Cloudflare beendet eine Verbindung, die ~100 s idle ist. Sende alle ~30 s einen Heartbeat und stelle die Verbindung auf Client-Seite wieder her.
- Ausgehend: Die App kann jeden öffentlichen Internet-Endpunkt auf jedem Port aufrufen. Das umfasst LLM-APIs, Stripe, E-Mail, S3 und verwaltete Datenbanken oder Redis (Neon, Supabase, Atlas, Upstash) auf ihren öffentlichen Endpunkten. Sie kann keine privaten Netzwerke (VPC-Peering, PrivateLink, private IPs) oder andere Projekte auf dem Cluster erreichen. Eingehender Datenverkehr kommt nur über die öffentliche URL.
- Drittanbieter-Dienste: Lege ihre Schlüssel und URLs in
secrets.env. Wenn ein Dienst eine IP-Allowlist benötigt, hat der Cluster eine statische ausgehende IP: Frage den Betreiber danach (sie ist nicht veröffentlicht) und sage dem Benutzer, dass sie mit anderen Projekten auf dem Cluster geteilt wird. Der Server befindet sich in US-central, daher bevorzuge US-central/us-east-Regionen für externe Datenbanken (eine EU-Datenbank fügt ~100 ms pro Abfrage hinzu). Eine externe Datenbank gehört dem Benutzer selbst: Data Clerks Backups decken sie nicht ab. Für langsame LLM-Aufrufe streamst du die Antwort oder führst sie als Hintergrundjobs aus; eine Browseranfrage, die über ~100 s wartet, wird abgeschnitten. - Ressourcen sind begrenzt (Speicher/CPU in PROJECT.md). Das Überschreiten des Speicherlimits tötet und startet die App neu. Wenn das passiert, frage den Betreiber nach mehr Spielraum, anstatt es im Code zu bekämpfen.
- Eine Kopie der App läuft (ein Replica). Verlasse dich nicht auf den In-Memory-Zustand, der einen Deploy überlebt, und behalte Sitzungen in der DB oder in signierten Cookies.
Was nur der Betreiber tun kann
Der Benutzer hat keinen Cluster-Zugriff, und das ist beabsichtigt. Schreibe dem Betreiber eine kurze Anfrage (siehe references/troubleshooting.md für die Vorlage) für:
neue Domain oder Subdomain · mehr Speicher/CPU · Datei-/Objektspeicher · eine Datenbankwiederherstellung · App-Logs oder Absturzursachen · geänderte Sicherheitsheader · Ändern des Migrations- oder Startbefehls, Ports oder Health-Pfads · geplante Jobs (Cron) · zusätzliche Dienste (Redis, Warteschlangen) · eine zweite App im selben Projekt · Onboarding eines neuen Repos.
Mehr Details
references/stacks.md: Der universelle Vertrag für jede Sprache; Full-Stack vs. SPA + API vs. statisch; Node, Python FastAPI, Go und andere Stacks.references/new-project.md: Onboarding eines Repos (hinzuzufügende Dateien, was den Betreiber zu fragen, Dockerfile pro Stack).references/secrets.md: sops im Detail, die Schlüsseldatei, Rotieren, Build-Zeit-Variablen.references/database.md: Migrationen, Verbindungszeichenfolge, Backups, Datenkorrekturen.references/setup-tools.md: Sicheres Installieren von sops/git/gh auf macOS, Linux und Windows. Führescripts/install-tools.shoderscripts/install-tools.ps1aus.references/troubleshooting.md: "Meine Änderung ist nicht live", "Seite ist down", häufige Fehler, die Vorlage für Betreiberanfragen.assets/: Dockerfile-Vorlagen (Node, Python, Go) und Vorlagen für den Deploy-Workflow,kustomization.yaml,.sops.yaml,.gitattributesundPROJECT.md.