Watch
1
0
Fork
You've already forked GBE_Prototyp2
0
No description
  • Python 72.5%
  • HTML 16.2%
  • JavaScript 4.8%
  • Shell 2.8%
  • Jinja 1.6%
  • Other 2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
markus f25ec0fea9
Some checks failed
CI / ci (push) Has been cancelled
Release 0.8
All eleven items of the 0.8 plan are implemented (docs/DESIGN_SPEC.md §4):
password self-service, API key and reasoning control for llama.cpp, vLLM
and colibri, backup/restore commands, region comparison, proportions and
charts with confidence intervals, text-block library, one synthesis path
with text sources (legacy path removed), longer model ids, audit retention.

Spec: release history row 0.8, §4 "Released in 0.8". Helm chart 0.4.0 with
appVersion 0.8. Handbook: update notes for 0.8 (migrations 0022-0026, legacy
path removal, new commands), release tags in mitarbeiten.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 05:14:57 +02:00
.forgejo/workflows 403 page, isolated sandbox network in the dev stack, dialog queue error, Forgejo runner label 2026-10-19 15:24:48 +02:00
Alt init commit 2026-05-12 10:41:16 +02:00
Backend Release 0.8 2026-10-21 05:14:57 +02:00
ci 0.8 (04-backup): Backup and restore commands 2026-10-21 03:08:23 +02:00
deploy Release 0.8 2026-10-21 05:14:57 +02:00
docs Release 0.8 2026-10-21 05:14:57 +02:00
Frontend init commit 2026-05-12 10:41:16 +02:00
installer 0.8 (04-backup): Backup and restore commands 2026-10-21 03:08:23 +02:00
vorlagen passende vorlagen 2026-05-18 11:46:32 +02:00
.gitignore 0.8 (04-backup): Backup and restore commands 2026-10-21 03:08:23 +02:00
action_log.txt Release 0.8 2026-10-21 05:14:57 +02:00
alternate_synthesis_path.txt alternativer synthesepfad 2026-05-16 13:17:24 +02:00
compose.yaml New installer (app-installer skill): docker compose, Kubernetes, Helm 2026-10-19 18:43:46 +02:00
graph.json mass import 2026-05-19 11:23:57 +02:00
install.sh Handbook and README: manual installation 2026-10-19 20:31:07 +02:00
justfile 0.8 (06-retention): Retention of audit logs 2026-10-21 03:27:51 +02:00
linkfinder.py selected sources oben 2026-05-26 16:28:06 +02:00
linkfinder_scrapy.py selected sources oben 2026-05-26 16:28:06 +02:00
Links_Ingestion_testset.txt testlinks 2026-05-21 06:30:51 +02:00
Links_thueringen_20052026.txt excel ingestion 2026-05-21 07:56:45 +02:00
README.md 0.8 (04-backup): Backup and restore commands 2026-10-21 03:08:23 +02:00
system_description_Release_0_5.txt bugfixes 2026-06-05 01:51:54 +02:00
user-test-script-selfguided.txt kleine formatierungsverbesseungen 2026-05-27 03:09:27 +02:00
user_test_script.txt testscript 2026-05-26 19:26:23 +02:00

GBE — Gesundheitsberichterstattung

GBE erstellt Gesundheitsberichte für Gesundheitsämter: Es analysiert Datenquellen (Webseiten und Dateien im Netz), wertet sie aus und lässt lokale KI-Sprachmodelle (Ollama) die Texte schreiben. Das Ergebnis ist ein PDF und ein bearbeitbares Word- oder ODT-Dokument im Layout einer eigenen Vorlage.

Das vollständige Handbuch (Anleitungen für Fachanwender, Administration für den IT-Betrieb) ist nach der Installation in der Anwendung unter /docs/ erreichbar, für angemeldete Konten. Die Quellen liegen in Backend/handbook/. Die Installationsanleitung steht hier, damit sie vor der Installation lesbar ist; sie entspricht der Handbuchseite Installation.

Installation

GBE wird mit ./install.sh im Wurzelverzeichnis des Repositorys eingerichtet. Das Skript startet den eigentlichen Installer (python -m installer) in einem Wegwerf-Container, stellt einige Fragen, schreibt die Konfiguration (.env, Reverse-Proxy-Teil bzw. Kubernetes-Manifeste oder Helm-Werte) und startet GBE auf Wunsch. Auf dem Server selbst wird nichts installiert. Den Aufbau des Stacks beschreibt Architektur und Betrieb.

Wer jeden Schritt selbst ausführen oder die erzeugten Dateien frei anpassen will, findet alle Befehle und Dateien ohne Installer unter Manuelle Installation.

Voraussetzungen

Komponente Anforderung
Host Linux mit POSIX-Shell und Git
Docker Docker Engine mit Compose-Plugin v2 (docker compose); der Benutzer darf Docker bedienen. Auch für Kubernetes nötig: Docker führt den Installer aus und baut das Image.
just empfohlen; das justfile im Wurzelverzeichnis enthält alle Betriebsbefehle (just --list). Fehlt es, meldet install.sh „Hinweis: just fehlt (https://just.systems); die Befehle danach sind just-Rezepte.“ und läuft trotzdem; die Befehle, die dieses Kapitel ausschreibt, funktionieren auch ohne just.
Kubernetes kubectl mit Zugriff auf den Cluster; für das Helm-Chart zusätzlich helm 3; eine Registry, die der Cluster erreicht, oder ein Cluster, in den Sie das Image selbst laden (z. B. kind)
Netz beim Installieren und Bauen Zugriff auf ghcr.io (Image des Installers), PyPI (der Installer lädt Jinja2 und PyYAML), Docker Hub, npm-Registry, Debian-Spiegel und den Download-Server von Playwright
Netz im Betrieb ausgehendes HTTP/HTTPS aus GBE zu den Datenquellen und zum Inferenzserver
Inferenzserver Ollama, llama.cpp (llama-server), vLLM oder colibri, aus den Containern bzw. Pods erreichbar, ohne API-Schlüssel, mindestens ein Modell geladen, siehe Sprachmodelle und Inferenzserver
Hostname für den Betrieb ein DNS-Name, der auf den Reverse Proxy zeigt; zum Ausprobieren genügt http://localhost:8080

1. Installer starten

git clone ssh://git@fj16.bastler.space:223/markusw/GBE_Prototyp2.git gbe
cd gbe
./install.sh            # oder: just install

Der Installer fragt nacheinander; Enter übernimmt den Wert in eckigen Klammern. Bei einem erneuten Lauf stammen diese Werte aus der vorhandenen .env, Sie bestätigen also nur, was sich nicht ändern soll. Jede Frage hat einen Schlüssel gleichen Namens für eine Antwortdatei.

install.sh startet den Installer im Container ghcr.io/astral-sh/uv:python3.12-bookworm-slim mit --network host. So sieht der Installer, welche Ports auf dem Server belegt sind. Läuft GBE schon, prüft er die eigenen Ports nicht.

2. Die Fragen

Für alle Betriebsarten:

Frage Standard Schlüssel Hinweis
„Öffentliche Adresse (URL, unter der die Nutzerinnen und Nutzer GBE öffnen)“ http://localhost:8080 public_url beginnt mit http:// oder https://; daraus entstehen ALLOWED_HOSTS und CSRF_TRUSTED_ORIGINS (mit Port, wenn er nicht der Standardport des Schemas ist)
„Wie soll GBE laufen?“ compose orchestrator compose (docker compose auf diesem Server), kubernetes (Manifeste und Kustomization) oder helm (Helm-Chart deploy/helm/gbe)
„Welcher Server stellt die Sprachmodelle bereit?“ ollama llm_server_type ollama, llamacpp, vllm oder colibri
„Adresse des Inferenzservers (…)“ http://host.docker.internal:<Port> mit 11434 (Ollama), 8080 (llama.cpp) bzw. 8000 (vLLM, colibri); bei Kubernetes kein Standard ollama_base_url aus Sicht der Container (host.docker.internal ist der Docker-Host) bzw. aus dem Cluster erreichbar. Bei einem erneuten Lauf schlägt der Installer die bisherige Adresse vor. War das die Standardadresse des bisherigen Servertyps, schlägt er nach einem Wechsel des Typs die Standardadresse des neuen vor.
„API-Schlüssel des LLM-Servers (leer: keiner)“ leer llm_api_key nur bei llamacpp, vllm und colibri: der Schlüssel, mit dem der Server gestartet wurde (--api-key). Die Eingabe bleibt unsichtbar. Bei einem erneuten Lauf zeigt die Frage [gespeichert; Enter behält ihn, - entfernt ihn]. Leerzeichen und die Zeichen ", ', #, $, \ und ` lehnt der Installer ab. Siehe API-Schlüssel.
„Größte Datei zum Hochladen in MB (Vorlagen, Datenquellen)“ 50 upload_max_mb Grenze für Anfragen in der erzeugten Proxy-Konfiguration; GBE selbst begrenzt Uploads nicht

Mit docker compose (compose) folgen:

Frage Standard Schlüssel gestellt bei
„Reverse Proxy (gefunden: …):“ A proxy_mode immer; Buchstabe A–L, siehe Reverse Proxy. „gefunden“ nennt laufende Proxy-Container und installierte Proxy-Programme, nur als Hinweis.
„Ist der Name aus dem Internet erreichbar (Let's Encrypt)? Nein = Caddys eigene CA“ ja tls_public A mit https:// und einem Namen außer localhost
„E-Mail für Let's Encrypt (leer: ohne)“ leer acme_email ebenso, wenn die Antwort davor „ja“ war
„Adresse:Port für HTTP“, „Adresse:Port für HTTPS“ 0.0.0.0:80, 0.0.0.0:443 http_bind, https_bind A mit https:// und einem Namen außer localhost
„Adresse:Port des mitgelieferten Proxys“ bei localhost bzw. 127.0.0.1 in der Adresse 127.0.0.1:<Port der Adresse> (ohne Port 8080), bei jedem anderen Namen 0.0.0.0:80 http_bind A mit http:// oder localhost; der Port muss zur öffentlichen Adresse passen. https:// mit localhost oder 127.0.0.1 lehnt der Installer in Modus A ab und startet dann nichts.
„Zertifikat (Pfad, wie der Proxy ihn sieht)“ und „Privater Schlüssel (Pfad, wie der Proxy ihn sieht)“ /etc/ssl/certs/gbe.pem, /etc/ssl/private/gbe.key tls_cert, tls_key D, E, I, L mit https://
„PEM-Datei mit Zertifikat und Schlüssel (Pfad, wie HAProxy sie sieht)“ /etc/haproxy/certs/gbe.pem tls_pem F mit https://
„Name oder ID des Cloudflare-Tunnels (cloudflared tunnel create …)“ leer (in der Datei dann gbe) cloudflare_tunnel K
„Zugangsdatei, wie cloudflared sie sieht“ /etc/cloudflared/credentials.json cloudflare_credentials K
„Traefik-Entrypoint“ websecure traefik_entrypoint C
„Traefik-Zertifikatsresolver (leer: keiner)“ leer traefik_certresolver C
„Wo läuft der Proxy?“ docker proxy_location D–G, I–L: docker (Container auf diesem Server, gemeinsames Docker-Netz) oder host (direkt auf dem Server oder auf einem anderen Rechner)
„Docker-Netz, das GBE mit dem Proxy teilt“ proxy external_network B, C und docker
„Adresse, auf der GBE lauscht (127.0.0.1 = nur dieser Server)“ und „Port“ 127.0.0.1, 8000 bind_host, web_port H und host; mit 0.0.0.0 trägt der Installer 127.0.0.1:<Port> als Ziel in die Proxy-Konfiguration ein
„Name des Compose-Projekts (bestimmt die Volume-Namen)“ gbe compose_project immer; Volumes heißen <Projekt>_gbe_db und <Projekt>_gbe_media
„Jetzt bauen und starten (docker compose up)?“ ja start immer

Mit Kubernetes (kubernetes oder helm) folgen:

Frage Standard Schlüssel gestellt bei
„Namespace“ gbe k8s_namespace immer
„Name des Helm-Release“ gbe helm_release helm
„Image-Registry-Präfix (leer: Image liegt schon auf den Knoten, z. B. kind/k3s)“ leer k8s_registry immer; das Image heißt dann <Präfix>/gbe:<Tag>, sonst gbe:<Tag>
„Image-Tag“ latest k8s_image_tag immer
„StorageClass (leer: Standard des Clusters)“ leer k8s_storage_class immer
„Zugriffsart des Medien-Volumes (ReadWriteMany bei mehreren Knoten, ReadWriteOnce auf einem Knoten)“ ReadWriteOnce k8s_data_access kubernetes
„Größe des Medien-Volumes (Vorlagen, Berichte)“, „Größe des Datenbank-Volumes“ 20Gi, 10Gi k8s_media_size, k8s_db_size immer
„Eingang in den Cluster:“ nginx k8s_ingress immer; siehe Kubernetes
„IngressClass (leer: Standard des Clusters)“ leer k8s_ingress_class nginx, traefik, haproxy
„Gateway, an das die Route gehängt wird (Namespace/Name)“ gateway-system/gateway k8s_gateway gateway
„cert-manager-ClusterIssuer (leer: Sie stellen das TLS-Secret selbst)“ und „Name des TLS-Secrets“ leer, gbe-tls k8s_cert_issuer, k8s_tls_secret https:// mit nginx, traefik, haproxy
„Ist der Name aus dem Internet erreichbar (Let's Encrypt)? …“ ja tls_public caddy mit https://
„E-Mail für Let's Encrypt (leer: ohne)“ leer acme_email ebenso, wenn die Antwort davor „ja“ war
„Image jetzt bauen und ausrollen (…)?“ nein start immer

Ohne Frage, nur über die Antwortdatei: db_name und db_user (Standard gbe). Die Geheimnisse erzeugt der Installer beim ersten Lauf selbst und behält sie danach:

Wert in .env Schlüssel Inhalt
SECRET_KEY secret_key 64 zufällige Zeichen
DB_PASSWORD db_password 32 zufällige Zeichen
SANDBOX_TOKEN sandbox_token gemeinsames Geheimnis von Worker und Sandbox, 43 zufällige Zeichen
DEFAULT_ADMIN_PASSWORD, DEFAULT_USER_PASSWORD admin_password, user_password Passwörter, mit denen die Startkonten Admin und User angelegt werden, je 16 zufällige Zeichen

3. Reverse Proxy bei docker compose

Die Buchstaben stehen in .env (GBE_PROXY_MODE) und ändern sich zwischen Versionen nicht. Jede erzeugte Datei nennt im Kopf, wofür sie gedacht ist, und in Kommentaren, was Sie noch tun müssen. Alle Varianten erlauben Anfragen bis zur Größe aus der Upload-Frage und warten bis zu 300 s auf eine Antwort, weil Dialoggeführt im Web-Request auf das Sprachmodell wartet.

Modus Proxy der Installer schreibt Sie tun
A mitgelieferter Caddy (Dienst gbe_proxy, Profil bundled-proxy) deploy/proxy/Caddyfile; Ports von gbe_proxy in compose.override.yaml nichts. Bei https:// mit öffentlichem Namen holt Caddy das Zertifikat bei Let's Encrypt; DNS und die Ports 80 und 443 müssen dafür aus dem Internet erreichbar sein. Mit „Caddys eigene CA“ (tls internal) warnen Browser, bis die Clients Caddys Wurzelzertifikat vertrauen. https:// geht nur mit einem Servernamen, nicht mit localhost.
B vorhandener Caddy deploy/proxy/caddy.snippet; gbe_web tritt dem Docker-Netz bei Snippet in Ihr Caddyfile aufnehmen (import /pfad/zu/gbe/deploy/proxy/caddy.snippet), Caddy-Container am selben Netz, Caddy neu laden
C Traefik v3 Docker-Labels an gbe_web in compose.override.yaml; alternativ deploy/proxy/traefik-gbe.yaml für den Datei-Provider Traefik nutzt den Docker-Provider und hängt am Netz; am Entrypoint in der statischen Konfiguration respondingTimeouts (readTimeout, writeTimeout) auf mindestens 300 s setzen
D nginx deploy/proxy/nginx.conf (server-Block) Datei in http { … } einbinden, z. B. als /etc/nginx/conf.d/gbe.conf; nginx -t && nginx -s reload
E Apache httpd 2.4 deploy/proxy/apache.conf (VirtualHost) Module laut Dateikopf aktivieren (Debian: a2enmod …), Datei einbinden, Apache neu laden
F HAProxy deploy/proxy/haproxy.cfg (frontend/backend) hinter global/defaults in Ihre haproxy.cfg übernehmen, haproxy -c -f haproxy.cfg, neu laden
G Nginx Proxy Manager deploy/proxy/nginx-proxy-manager.md Proxy Host in der Weboberfläche nach der Anleitung anlegen, Snippet unter Advanced einfügen
H keiner oder ein anderer deploy/proxy/REQUIREMENTS.md; gbe_web veröffentlicht <Adresse>:<Port> Ihren Proxy nach den Anforderungen in der Datei einrichten
I Envoy deploy/proxy/envoy.yaml (statische Bootstrap-Konfiguration) envoy -c envoy.yaml oder Listener und Cluster in Ihre Konfiguration übernehmen. Envoy begrenzt Anfragen nicht; für die Upload-Grenze den Filter envoy.filters.http.buffer mit max_request_bytes ergänzen (Hinweis im Dateikopf).
J Kong Gateway (DB-less) deploy/proxy/kong.yml mit KONG_DECLARATIVE_CONFIG=kong.yml laden oder deck gateway sync kong.yml; bei https:// das Zertifikat in Kong selbst hinterlegen
K Cloudflare Tunnel deploy/proxy/cloudflared.yml (Ingress-Regeln) Tunnel anlegen (cloudflared tunnel create gbe), Namen routen (cloudflared tunnel route dns gbe <Name>), cloudflared tunnel run. Cloudflare beendet Anfragen meist nach 100 s und begrenzt Uploads auf 100 MB; eine lange Antwort unter Dialoggeführt scheitert dann.
L lighttpd deploy/proxy/lighttpd.conf aus lighttpd.conf einbinden (include "gbe.conf"); lighttpd nach jedem Neuerzeugen der GBE-Container neu laden, weil sich deren Adresse ändern kann

Bei D–G und I–L entscheidet „Wo läuft der Proxy?“: Mit docker tritt gbe_web dem Docker-Netz des Proxys bei und ist dort als gbe_web:8000 erreichbar, ohne einen Port zu veröffentlichen. Mit host veröffentlicht gbe_web die Adresse aus „Adresse, auf der GBE lauscht“ (Standard 127.0.0.1:8000). Binden Sie GBE an eine andere Adresse als 127.0.0.1, ist es dort ohne TLS erreichbar; beschränken Sie den Port per Firewall auf den Proxy. Bei 0.0.0.0 trägt der Installer 127.0.0.1:<Port> als Ziel in die Proxy-Konfiguration ein. Läuft der Proxy auf einem anderen Rechner, ersetzen Sie das Ziel dort durch die Adresse dieses Servers.

Für https tragen nginx, Apache, Envoy und lighttpd die Pfade aus den Fragen „Zertifikat …“ und „Privater Schlüssel …“ ein, HAProxy die PEM-Datei mit beidem. Die Pfade gelten so, wie der Proxy sie sieht (bei einem Proxy-Container also im Container). Der Installer speichert sie in der .env (GBE_TLS_CERT, GBE_TLS_KEY, GBE_TLS_PEM); Zertifikate beschafft und erneuert er nicht.

Ein gemeinsames Docker-Netz muss existieren, bevor GBE startet. Fehlt es, bricht install.sh mit „Das Docker-Netz … des Proxys fehlt: docker network create …“ ab. Legen Sie es an und starten Sie mit just up.

Die erzeugten Konfigurationen prüft just proxy-check mit dem Image des jeweiligen Proxys (caddy validate, nginx -t, apachectl configtest, haproxy -c usw.), ohne dass GBE laufen muss. Geben Sie /media/ nie direkt aus dem Volume frei: GBE prüft bei jeder Datei die Berechtigung.

4. Was der Installer schreibt und startet

Datei Betriebsart Inhalt
.env alle alle Einstellungen und Geheimnisse; Rechte 600. compose.yaml und das justfile lesen sie.
compose.override.yaml compose der Teil für den Reverse Proxy: Ports von gbe_proxy (A), Docker-Netz und Labels von gbe_web (B, C, D–G und I–L mit docker) oder der veröffentlichte Port von gbe_web
deploy/proxy/… compose Konfiguration des gewählten Proxys, siehe oben
deploy/k8s/… kubernetes Manifeste, kustomization.yaml, jobs/migrate.yaml und gbe.env (Kopie der .env, Quelle des Secrets gbe-env, Rechte 600)
deploy/helm-values.yaml helm Werte für das Chart deploy/helm/gbe, mit Geheimnissen; Rechte 600

Alle diese Dateien sind in Git ausgeschlossen. Unveränderte Dateien schreibt der Installer nicht neu; eine geänderte Datei sichert er vorher als <Datei>.bak-JJJJMMTT-HHMMSS. Jede geschriebene Datei meldet er mit geschrieben: …, zum Schluss folgen die „Nächste Schritte:“.

Haben Sie „Jetzt bauen und starten“ bejaht, startet install.sh GBE nach dem Ende des Installer-Containers auf dem Host:

Betriebsart Schritte
compose docker compose up -d --build --wait, dann docker compose exec -T gbe_web python manage.py apply_install_settings; Ende mit „GBE läuft: · Prüfung: just doctor“
kubernetes Image bauen (mit Registry-Präfix auch taggen und pushen), kubectl apply -k deploy/k8s, warten, bis das Deployment web bereit ist (web migriert beim Start selbst), dann Job gbe-migrate neu anlegen und abwarten (Migrationen, Startkonten, Inferenzserver in die Einstellungen), dann kubectl rollout restart für web, worker und sandbox, damit ein neu gebautes Image unter demselben Tag läuft
helm Image bauen und pushen, helm upgrade --install <Release> deploy/helm/gbe -n <Namespace> --create-namespace -f deploy/helm-values.yaml --wait --timeout 15m, kubectl rollout restart für <fullname>-app und <fullname>-sandbox und warten, bis <fullname>-app bereit ist, dann apply_install_settings im Container web

apply_install_settings trägt Servertyp, Adresse und (wenn gesetzt) API-Schlüssel des Inferenzservers aus der .env bei jedem Lauf in die Einstellungen ein, siehe Sprachmodelle und Inferenzserver. Beim ersten Start legt entrypoint.sh die Datenbank an (migrate) und die Startkonten (create_default_users).

Findet der Installer ein Problem, meldet er es mit !. Ein belegter Port (Port … ist schon belegt), bei Kubernetes eine fehlende Adresse des Inferenzservers oder in Modus A eine https://-Adresse mit localhost bzw. 127.0.0.1 („https mit localhost geht mit dem mitgelieferten Caddy nicht: http://localhost:8080 oder einen Servernamen nehmen“) verhindert den Start: Der Installer schreibt die Dateien trotzdem, endet aber mit „Nicht gestartet: erst die mit ! markierten Probleme beheben, dann ./install.sh erneut.“

Bei docker compose besteht der Stack aus compose.yaml (im Repository) und compose.override.yaml: gbe_web, gbe_worker, gbe_sandbox (führt KI-erzeugten Code aus), gbe_db, gbe_valkey und bei Modus A gbe_proxy. Datenbank und Broker liegen in einem privaten Netz, das nur GBE erreicht; die Sandbox teilt ihr Netz nur mit dem Worker.

Modus Proxy erreicht GBE über veröffentlichte Ports
A Netz edge zwischen gbe_proxy und gbe_web gbe_proxy: die Adressen aus den Fragen (80 und 443 bzw. 127.0.0.1:8080)
B, C, D–G und I–L mit docker gemeinsames Docker-Netz, gbe_web:8000 keine
H, D–G und I–L mit host veröffentlichte Adresse von gbe_web nur diese eine

5. Kubernetes

Es gibt zwei Wege in den Cluster:

  • Manifeste (kubernetes): Der Installer schreibt deploy/k8s/, eine Datei je Teil (namespace, storage, db, valkey, backend, sandbox, dazu ingress bzw. REQUIREMENTS.md) und eine kustomization.yaml, die das Secret gbe-env aus deploy/k8s/gbe.env erzeugt. Web, Worker, Sandbox und Valkey sind eigene Deployments, die Datenbank ein StatefulSet. Migrationen laufen im Job deploy/k8s/jobs/migrate.yaml.
  • Helm (helm): Der Installer schreibt nur deploy/helm-values.yaml für das Chart deploy/helm/gbe. Web und Worker laufen dort als zwei Container in einem Pod <fullname>-app.

Eingang in den Cluster:

Art Manifeste Helm was entsteht Sie tun
caddy ja nein mitgelieferter Caddy als Deployment proxy mit Service vom Typ LoadBalancer und PVC caddy-data für die Zertifikate DNS auf die externe Adresse des Service; bei https:// müssen 80 und 443 aus dem Internet erreichbar sein
nginx ja ja Ingress für ingress-nginx mit Body-Grenze und Timeouts als Annotationen Controller vorhanden; TLS über cert-manager-Issuer oder ein eigenes Secret
traefik ja ja Ingress, bei Manifesten mit Middleware gbe-body für die Body-Grenze respondingTimeouts am Entrypoint von Traefik auf mindestens 300 s
haproxy ja ja Ingress für den HAProxy Kubernetes Ingress Controller mit timeout-server Controller vorhanden; TLS wie bei nginx
gateway ja ja HTTPRoute (Gateway API) am angegebenen Gateway, Timeout 300 s TLS am Listener des Gateways; Body-Grenze der Implementierung mindestens so groß wie die Upload-Grenze
none ja ja nur ClusterIP-Services; Manifeste: deploy/k8s/REQUIREMENTS.md Verkehr selbst auf den Service web (Helm: <fullname>-web), Port 8000, leiten

Ohne TLS-Secret und ohne Issuer verwendet ein Ingress-Controller sein Standardzertifikat. Ein eigenes Secret legen Sie so an: kubectl -n gbe create secret tls gbe-tls --cert=… --key=….

Speicher. Das Medien-Volume gbe-media (bei Helm <fullname>-media) nutzen Web und Worker gemeinsam. Bei den Manifesten sind Web und Worker getrennte Pods: Mit ReadWriteOnce bekommt der Worker eine Pod-Affinität zum Web-Pod und läuft immer auf dessen Knoten. Mit ReadWriteMany (eine StorageClass, die das kann) dürfen beide auf verschiedenen Knoten laufen. Helm braucht nur ReadWriteOnce, weil beide Container im selben Pod laufen.

Image. Ohne Registry-Präfix muss gbe:<Tag> schon auf den Knoten liegen; bei kind lädt just k8s-load es hinein. Mit Präfix schiebt just k8s-build es nach <Präfix>/gbe:<Tag>. just k8s-up und just helm-up starten die GBE-Pods jedes Mal neu, damit ein neu gebautes Image auch unter demselben Tag verwendet wird; mit Registry zieht der Cluster es aber nur beim Tag latest immer neu (imagePullPolicy: Always, sonst IfNotPresent).

Entfernen. just k8s-down löscht die Workloads der Manifeste; die PVCs, das Secret gbe-env und der Namespace bleiben. just helm-down entfernt das Release; die PVCs bleiben.

Ohne automatischen Start führen Sie die Schritte selbst aus:

# Manifeste
just k8s-build        # Image bauen (und mit Registry-Präfix hochladen)
just k8s-up           # kubectl apply -k deploy/k8s, Pods neu starten
just k8s-migrate      # Migrationen, Startkonten, Inferenzserver (ein Job)
just k8s-status       # Pods, Services, Volumes, Ingress

# Helm
just k8s-build
just helm-up          # helm upgrade --install …, Pods neu starten, apply_install_settings
just helm-status

6. Prüfen

just doctor           # docker compose
just k8s-doctor       # Kubernetes mit Manifesten
just helm-doctor      # Helm

just doctor prüft im Container gbe_worker mit manage.py doctor Datenbank und Migrationen, Aufgabenwarteschlange und Worker (ein gerade beschäftigter Worker ergibt nur einen Hinweis), den Inferenzserver (Zahl der Modelle, Warnung, wenn das Standardmodell fehlt), das Handbuch und einen Probelauf in der Sandbox. Danach prüft python -m gbe_sandbox.selfcheck im Container gbe_sandbox, dass die Sandbox keine Geheimnisse kennt und Datenbank und Valkey nicht erreicht, und curl ruft <öffentliche Adresse>/healthz über den Proxy auf (mit Caddys eigener CA ohne Prüfung des Zertifikats). ✓ heißt bestanden, ! ist ein Hinweis, ✗ ein Fehler. Die Kubernetes-Varianten prüfen die ersten beiden Teile ohne den Aufruf über die öffentliche Adresse. Meldungen und Abhilfen stehen in der Fehlerbehebung.

/healthz antwortet ohne Anmeldung mit ok (HTTP 200) bzw. mit 503, solange die Datenbank nicht erreichbar ist. Proxys und Load Balancer können es als Gesundheitsprüfung nutzen.

7. Erste Anmeldung

Die Passwörter der Startkonten stehen in der .env:

grep '^DEFAULT_' .env

DEFAULT_ADMIN_PASSWORD gehört zu Admin (Administrator, Zugang zu /admin/), DEFAULT_USER_PASSWORD zu User. Bei Helm stehen sie zusätzlich in deploy/helm-values.yaml (startAccounts). Melden Sie sich unter <öffentliche Adresse>/anmelden/ als Admin an, ändern Sie auf der Seite Benutzer die Passwörter der Startkonten (Konto öffnen → Passwort setzen) und legen Sie über Neues Konto persönliche Konten an.

Die Werte in der .env gelten nur, wenn create_default_users ein Konto neu anlegt. Ein bestehendes Konto behält sein Passwort; nach einer Änderung auf der Seite Benutzer ist der Wert in der .env also veraltet.

Warning

Startkonten nicht löschen

create_default_users läuft bei jedem Start. Fehlt ein Konto namens Admin oder User, legt es das Konto neu an, mit dem Passwort aus DEFAULT_ADMIN_PASSWORD bzw. DEFAULT_USER_PASSWORD. Ohne diese Werte, etwa im lokalen Stack, verwendet es das öffentlich bekannte Passwort aus Backend/gbe/management/commands/create_default_users.py. Brauchen Sie ein Konto nicht, deaktivieren Sie es (Seite Benutzer → Konto öffnen → Deaktivieren), statt es zu löschen; die Seite Benutzer lässt Startkonten ohnehin nicht löschen.

Mehr zu Konten unter Benutzer und Anmeldung.

Unbeaufsichtigte Installation mit answers.toml

Jede Frage hat einen Schlüssel gleichen Namens (siehe die Tabellen oben). Fehlende Schlüssel nehmen den Wert aus der vorhandenen .env, sonst den Standard. Ohne start = true startet der Installer dabei nichts. Die Datei darf an beliebiger Stelle liegen; install.sh stellt sie dem Installer-Container schreibgeschützt bereit.

public_url = "https://gbe.example.org"
orchestrator = "compose"
proxy_mode = "D"
proxy_location = "host"
bind_host = "127.0.0.1"
web_port = 8000
llm_server_type = "vllm"
ollama_base_url = "http://host.docker.internal:8000"
llm_api_key = "<Schlüssel des vLLM-Servers>"
upload_max_mb = 50
compose_project = "gbe"
start = true
tls_cert = "/etc/letsencrypt/live/gbe.example.org/fullchain.pem"
tls_key = "/etc/letsencrypt/live/gbe.example.org/privkey.pem"
./install.sh --answers /etc/gbe/answers.toml

Fehlt die Datei, bricht install.sh mit „Antwortdatei nicht gefunden: …“ ab. Einen Schlüssel, den der Installer nicht kennt, meldet er mit „! : unbekannte Antwort <Schlüssel> wird ignoriert (Tippfehler?)“ und lässt ihn weg. Eine answers.toml im Wurzelverzeichnis des Repositorys ist in Git ausgeschlossen.

Weitere Optionen:

Option Wirkung
--answers DATEI Antworten aus einer TOML-Datei, ohne Rückfragen
--rotate-secrets neuer SECRET_KEY und neues SANDBOX_TOKEN; alle Sitzungen enden. Das Datenbankpasswort bleibt, siehe Konfiguration.
--no-start nur Dateien schreiben, nichts starten
--dry-run ausgeben, was geschrieben würde, ohne etwas zu schreiben
--out VERZ in ein anderes Verzeichnis schreiben (für Prüfungen); die bisherigen Antworten kommen dann auch von dort

Erneut installieren

Ein weiterer Lauf von ./install.sh schlägt alle bisherigen Antworten vor und behält die Geheimnisse. Zeilen, die Sie selbst in die .env geschrieben haben (etwa HTTPS_PROXY), übernimmt er unter # kept from the previous .env. Die Daten in den Volumes bleiben erhalten. Einzelheiten unter Konfiguration.

Umstieg vom bisherigen Installer

Bis Release 0.6 hieß der Installer deploy/install.sh und schrieb nach deploy/generated/. Der neue Installer übernimmt dessen Antworten und Geheimnisse (install.conf, .env, bei Kubernetes values.yaml), solange im Wurzelverzeichnis noch keine .env liegt; er meldet das mit „Antworten des bisherigen Installers (deploy/generated/) werden übernommen.“ Der Name der Installation wird zum Compose-Projekt bzw. Helm-Release, die Volumes <Name>_gbe_db und <Name>_gbe_media bzw. die PVCs des Charts bleiben also in Gebrauch.

  1. Sichern Sie Datenbank, Medien und deploy/generated/, siehe Sicherung und Wiederherstellung.

  2. Halten Sie bei Docker den alten Stack an (die Volumes bleiben):

    docker compose -p <Name> -f deploy/generated/docker-compose.yml down
    
  3. Neuen Stand holen und den Installer ausführen; prüfen Sie dabei die vorgeschlagenen Antworten:

    git pull
    ./install.sh
    
bisher (GBE_PROXY) jetzt
none (mitgelieferter Caddy) A
caddy, traefik B, C (nur in Docker; lief der Proxy direkt auf dem Server, wählen Sie H)
nginx, apache, haproxy, npm, envoy, kong, cloudflared D, E, F, G, I, J, K, mit derselben Antwort auf „Wo läuft der Proxy?“
other H
Betriebsart Kubernetes helm mit demselben Release; Ingress nginx, bei Traefik traefik, bei HAProxy haproxy, bei Envoy gateway

Danach gilt:

  • Die öffentliche Adresse ist https://<bisheriger Hostname>, mit Port, wenn er nicht 443 war.
  • Ein Proxy in Docker erreicht GBE unter gbe_web:8000; einen eigenen Netzwerk-Alias gibt es nicht mehr. Vergleichen Sie Ihre Proxy-Konfiguration mit der neu erzeugten unter deploy/proxy/.
  • Bei Helm heißt das Image jetzt <Präfix>/gbe:<Tag>; aus einem bisherigen Repository registry.example.org/gbe wird das Präfix registry.example.org.
  • Die Passwörter der Startkonten übernimmt der Installer in die .env; eine credentials.txt gibt es nicht mehr.
  • just doctor ersetzt deploy/install.sh --verify.

Läuft alles, archivieren Sie deploy/generated/ verschlüsselt und löschen es; es enthält Geheimnisse.

Eine noch ältere Installation aus Backend/docker-compose.yml (Compose-Projekt backend) übernimmt der Installer nicht von selbst. Legen Sie vor dem ersten Lauf eine .env im Wurzelverzeichnis an, mit COMPOSE_PROJECT_NAME=backend sowie SECRET_KEY und DB_PASSWORD aus Backend/.env (ohne Eintrag dort gilt das Datenbankpasswort gbepw), halten Sie den alten Stack an (cd Backend && docker compose -p backend down) und führen Sie ./install.sh aus. Die Startkonten existieren dort schon und behalten ihre bisherigen Passwörter.

Checkliste

  • DNS-Eintrag des Hostnamens zeigt auf den Reverse Proxy.
  • just doctor (bzw. just k8s-doctor, just helm-doctor) endet mit „Alle Prüfungen bestanden.“ und ohne ✗.
  • Anmeldung als Admin funktioniert; Passwörter der Startkonten geändert, persönliche Konten angelegt.
  • Eine erste Sicherung mit just backup (Kubernetes: just k8s-backup bzw. just helm-backup) liegt außerhalb des Servers; sie enthält auch .env und die Kubernetes-Dateien, siehe Sicherung und Wiederherstellung.
  • Testlauf: eine Datenquelle hinzufügen und einen kurzen Bericht erzeugen.

Manuelle Installation

./install.sh tut zweierlei: Der Installer (python -m installer) leitet aus Ihren Antworten Werte ab und schreibt daraus Dateien (.env, compose.override.yaml und die Proxy-Konfiguration, Kubernetes-Manifeste oder Helm-Werte). Danach führt install.sh auf dem Host einige Befehle aus (Image bauen, starten, Einstellungen übernehmen). Diese Seite zeigt beides als einfache Dateien und Befehle, für alle drei Betriebsarten und ohne Installer und ohne just. Damit können Sie GBE in eine eigene Automatisierung einbauen oder weiter vom Standard abweichen, als die Fragen des Installers erlauben.

Es gelten dieselben Voraussetzungen wie für den Installer, nur just und der Zugriff auf ghcr.io und PyPI für den Installer-Container entfallen. Alle Pfade sind relativ zum Wurzelverzeichnis des Repositorys. Den Aufbau des Stacks beschreibt Architektur und Betrieb.

Quellen der Wahrheit

Die Dateien auf dieser Seite sind genau die, die der Installer schreibt. Maßgeblich sind:

Was Wo
Vorlagen aller erzeugten Dateien installer/templates/*.j2, Kubernetes unter installer/templates/k8s/
Ableitung jedes Werts aus den Antworten installer/__init__.py → template_vars()
fertig gerenderte Beispiele jeder Variante installer/tests/snapshots/<Variante>/
Befehle nach dem Schreiben install.sh (Ende der Datei) und justfile

Die Beispiele unter installer/tests/snapshots/ sind mit dem Host gbe.example.org und Platzhaltern statt Geheimnissen (SSSS…, PPPP…) erzeugt. Ein Schrägstrich im Pfad ist dort als __ geschrieben: D/deploy__proxy__nginx.conf ist die Datei deploy/proxy/nginx.conf für Modus D.

Verzeichnis Variante
A … L docker compose, Proxy-Modus A–L, https://gbe.example.org, Proxy in Docker am Netz proxy_net
A-local Modus A mit http://localhost:8080
D-host … L-host Modi D–G und I–L mit dem Proxy auf dem Host, http://gbe.example.org
k8s-<Art> Kubernetes-Manifeste mit dem Eingang caddy, nginx, traefik, haproxy, gateway oder none
helm-<Art> Helm-Werte mit dem Eingang nginx, traefik, haproxy, gateway oder none

Die Kubernetes- und Helm-Beispiele verwenden das Registry-Präfix registry.example.org/gbe, den Tag 1.4.0, die StorageClass fast, den ClusterIssuer letsencrypt und den Inferenzserver http://ollama.ai:11434 vom Typ vllm.

Mittelweg: Dateien rendern lassen und anpassen

Oft genügt es, den Installer nur die Dateien schreiben zu lassen und diese anzupassen:

Befehl Wirkung
./install.sh --dry-run stellt die Fragen (oder liest die Antworten aus --answers) und gibt jede Datei nach einer Zeile ===== <Pfad> aus; schreibt und startet nichts. Ohne vorhandene .env sind die Geheimnisse bei jedem Lauf neu.
./install.sh --answers antworten.toml --out render schreibt alle Dateien nach render/ im Repository statt ins Projekt und startet nichts; die bisherigen Antworten kommen aus render/.env, nicht aus der .env des Projekts. Das Verzeichnis muss innerhalb des Repositorys liegen und relativ angegeben werden, weil der Installer im Container nur das Repository sieht. render/ ist nicht in Git ausgeschlossen und enthält Geheimnisse.
./install.sh --no-start schreibt die Dateien ins Projekt und startet nichts

Die Schlüssel der Antwortdatei beschreibt Installation.

Warning

Ein späterer Lauf überschreibt Ihre Änderungen

./install.sh schreibt bei jedem Lauf .env, compose.override.yaml, deploy/proxy/…, deploy/k8s/… und deploy/helm-values.yaml neu; eine geänderte Datei sichert er vorher als <Datei>.bak-JJJJMMTT-HHMMSS. Nach Änderungen von Hand führen Sie den Installer also nicht mehr aus, oder Sie halten Ihre Änderungen in Dateien, die er nicht schreibt (Tabelle unten).

Betriebsart eigene Änderungen, die ein späterer Lauf behält
alle Zeilen in der .env mit Variablen, die der Installer nicht schreibt; er übernimmt sie unter # kept from the previous .env. Werte der Variablen, die er schreibt, schlägt er beim nächsten Lauf vor; DEBUG, ALLOWED_HOSTS, CSRF_TRUSTED_ORIGINS, COMPOSE_PROFILES und GBE_HELM_FULLNAME leitet er immer neu ab, siehe Konfiguration.
docker compose eine eigene Datei, z. B. compose.local.yaml, eingebunden mit der Zeile COMPOSE_FILE=compose.yaml:compose.override.yaml:compose.local.yaml in der .env. Mit COMPOSE_FILE lädt Compose compose.override.yaml nicht mehr von selbst, sie muss deshalb in der Liste stehen.
Kubernetes mit Manifesten eine eigene Kustomization (Overlay) außerhalb von deploy/k8s/, die deploy/k8s als Ressource einbindet und Patches ergänzt; anwenden mit kubectl apply -k <Overlay> statt just k8s-up
Helm eine zweite Werte-Datei nach der des Installers: helm upgrade --install … -f deploy/helm-values.yaml -f eigene-werte.yaml; die spätere Datei gewinnt. just helm-up kennt sie nicht.

Gemeinsame Schritte

Code holen

git clone ssh://git@fj16.bastler.space:223/markusw/GBE_Prototyp2.git gbe
cd gbe

Geheimnisse erzeugen

Der Installer erzeugt die Geheimnisse beim ersten Lauf mit Pythons secrets.token_urlsafe(). Dieselben Werte erhalten Sie so:

Variable Befehl Länge
SECRET_KEY python3 -c 'import secrets; print(secrets.token_urlsafe(48))' 64 Zeichen
DB_PASSWORD python3 -c 'import secrets; print(secrets.token_urlsafe(24))' 32 Zeichen
SANDBOX_TOKEN python3 -c 'import secrets; print(secrets.token_urlsafe(32))' 43 Zeichen
DEFAULT_ADMIN_PASSWORD, DEFAULT_USER_PASSWORD python3 -c 'import secrets; print(secrets.token_urlsafe(12))' je 16 Zeichen

Ohne Python liefert openssl rand -base64 48 | tr '+/' '-_' | tr -d '=\n' einen gleichwertigen Wert (Zahl der Bytes wie oben). Bleiben Sie bei Buchstaben, Ziffern, - und _: Die Werte stehen ohne Anführungszeichen in der .env, und Compose, kubectl und Kustomize behandeln Zeichen wie $, # oder Anführungszeichen unterschiedlich.

DB_PASSWORD übernimmt PostgreSQL nur beim ersten Start mit leerem Volume. Ändern Sie es danach nicht mehr in der .env allein, siehe Geheimnisse erneuern. Ohne DEFAULT_ADMIN_PASSWORD und DEFAULT_USER_PASSWORD legt create_default_users die Startkonten mit den öffentlich bekannten Passwörtern aus Backend/gbe/management/commands/create_default_users.py an; setzen Sie beide immer.

Abgeleitete Werte

Aus der öffentlichen Adresse und den übrigen Antworten leitet template_vars() diese Werte ab. Bilden Sie sie von Hand genauso:

Wert Regel Beispiel
ALLOWED_HOSTS Hostname der öffentlichen Adresse, localhost und 127.0.0.1 (der Installer sortiert alphabetisch; die Reihenfolge ist egal). localhost muss bleiben: Healthcheck und Kubernetes-Proben senden Host: localhost. 127.0.0.1,gbe.example.org,localhost
CSRF_TRUSTED_ORIGINS Schema und Hostname; der Port nur, wenn er nicht 443 (https) bzw. 80 (http) ist https://gbe.example.org, https://gbe.example.org:8443
OLLAMA_BASE_URL docker compose: http://host.docker.internal:<Port> mit 11434 (Ollama), 8080 (llama.cpp), 8000 (vLLM, colibri), wenn der Inferenzserver auf dem Docker-Host läuft. Kubernetes: eine aus dem Cluster erreichbare Adresse, es gibt keinen Standard. http://host.docker.internal:11434
LLM_SERVER_TYPE ollama, llamacpp, vllm oder colibri ollama
LLM_API_KEY der API-Schlüssel, mit dem llama.cpp, vLLM oder colibri gestartet wurde (--api-key); leer: keiner, bei Ollama immer leer. Ohne Leerzeichen, ", ', #, $, \ und `. (leer)
DEBUG immer false false
DB_NAME, DB_USER Standard gbe gbe
Image ohne Registry gbe:<Tag>, mit Registry-Präfix <Präfix>/gbe:<Tag> registry.example.org/gbe/gbe:1.4.0
imagePullPolicy Always nur mit Registry-Präfix und dem Tag latest, sonst IfNotPresent IfNotPresent
Upstream des Proxys gbe_web:8000 (Proxy am selben Docker-Netz), <Adresse>:<Port> von gbe_web (Proxy auf dem Host, Standard 127.0.0.1:8000; bei der Adresse 0.0.0.0 127.0.0.1:<Port>; ein Proxy auf einem anderen Rechner braucht die Adresse des Servers), web:8000 (Manifeste), <fullname>-web:8000 (Helm) gbe_web:8000
Body-Grenze die Upload-Grenze in MB (Standard 50), je nach Proxy in Bytes (52428800) oder KiB (51200). GBE selbst begrenzt Uploads nicht; die Grenze gibt es nur im Proxy. 50m
Timeout 300 s für Lesen und Senden, weil Dialoggeführt im Web-Request auf das Sprachmodell wartet 300s
Helm-fullname der Release-Name, wenn er gbe enthält, sonst <Release>-gbe gbe

Die Bedeutung aller Variablen steht unter Umgebungsvariablen und Konfiguration.

Image bauen

docker compose baut das Image selbst (--build, siehe unten). Für Kubernetes und Helm bauen Sie es vorher und schieben es in eine Registry, die der Cluster erreicht:

docker build -t gbe:1.4.0 Backend
docker tag gbe:1.4.0 registry.example.org/gbe/gbe:1.4.0
docker push registry.example.org/gbe/gbe:1.4.0

Der Build braucht Zugriff auf Docker Hub, ghcr.io, die npm-Registry, PyPI, Debian-Spiegel und den Download-Server von Playwright. Ohne Registry laden Sie das Image direkt auf die Knoten, siehe Image ohne Registry.

docker compose

compose.yaml aus dem Repository bleibt unverändert. Sie schreiben daneben:

Datei Inhalt
.env Einstellungen und Geheimnisse; Compose liest sie für die Variablen in compose.yaml, gbe_web und gbe_worker bekommen alle Werte über env_file
compose.override.yaml der Teil für den Reverse Proxy; Compose lädt die Datei neben compose.yaml von selbst
deploy/proxy/<Datei> Konfiguration des Proxys; compose.yaml bindet nur das Caddyfile des mitgelieferten Caddy (Modus A) ein, andere Proxys binden ihre Datei selbst ein oder übernehmen den Inhalt

.env schreiben

Das Beispiel entspricht installer/tests/snapshots/A/.env (mitgelieferter Caddy, https://gbe.example.org). Die Kommentarzeilen sind entbehrlich.

SECRET_KEY=<64 Zeichen>
DEBUG=false
ALLOWED_HOSTS=127.0.0.1,gbe.example.org,localhost
CSRF_TRUSTED_ORIGINS=https://gbe.example.org
DB_NAME=gbe
DB_USER=gbe
DB_PASSWORD=<32 Zeichen>
SANDBOX_TOKEN=<43 Zeichen>
DEFAULT_ADMIN_PASSWORD=<16 Zeichen>
DEFAULT_USER_PASSWORD=<16 Zeichen>
OLLAMA_BASE_URL=http://host.docker.internal:11434
LLM_SERVER_TYPE=ollama
LLM_API_KEY=
GBE_PUBLIC_URL=https://gbe.example.org
GBE_ORCHESTRATOR=compose
GBE_UPLOAD_MAX_MB=50
GBE_TLS_PUBLIC=yes
GBE_IMAGE_TAG=latest
COMPOSE_PROJECT_NAME=gbe
COMPOSE_PROFILES=bundled-proxy
GBE_PROXY_MODE=A
GBE_PROXY_LOCATION=docker
GBE_PROXY_NETWORK=
GBE_HTTP_BIND=0.0.0.0:80
GBE_HTTPS_BIND=0.0.0.0:443
GBE_ACME_EMAIL=admin@example.org
chmod 600 .env

Wer was davon liest:

Variablen gelesen von ohne Wert
DB_PASSWORD, SANDBOX_TOKEN Compose (${…:?…} in compose.yaml) sowie GBE bzw. Sandbox Compose bricht mit run ./install.sh first ab
SECRET_KEY GBE fehlt die Zeile, gilt der öffentlich bekannte Schlüssel aus config/settings.py; ist sie leer, bricht Django ab. Immer setzen.
ALLOWED_HOSTS GBE jede Anfrage endet mit 400, auch der Healthcheck
CSRF_TRUSTED_ORIGINS GBE Formulare scheitern hinter einem TLS-Proxy mit 403
DEFAULT_ADMIN_PASSWORD, DEFAULT_USER_PASSWORD create_default_users öffentlich bekannte Startpasswörter
OLLAMA_BASE_URL, LLM_SERVER_TYPE, LLM_API_KEY GBE beim ersten Start und apply_install_settings Standard der Einstellungen; dort änderbar (LLM_API_KEY leer: kein Schlüssel bzw. der gespeicherte bleibt)
DEBUG, DB_NAME, DB_USER GBE, DB_NAME und DB_USER auch gbe_db false, gbe, gbe
GBE_IMAGE_TAG Compose (Image gbe:<Tag>) latest
COMPOSE_PROJECT_NAME Compose; bestimmt die Volume-Namen <Projekt>_gbe_db, <Projekt>_gbe_media gbe (name: in compose.yaml)
COMPOSE_PROFILES Compose; bundled-proxy startet gbe_proxy kein gbe_proxy. Nur in Modus A setzen.
GBE_PUBLIC_URL, GBE_TLS_PUBLIC Prüfung (just doctor) keine Prüfung der öffentlichen Adresse
GBE_PROXY_NETWORK install.sh (prüft, ob das Netz existiert) –
übrige GBE_… nur der Installer, als Vorgabe beim nächsten Lauf der Installer schlägt seine Standards vor

Die übrigen GBE_…-Zeilen braucht GBE nicht. Schreiben Sie sie trotzdem, wenn Sie später den Installer verwenden könnten; sonst schlägt er etwa Modus A vor. Je Modus ändern sich nur die Zeilen ab COMPOSE_PROFILES (Vorlage installer/templates/env.j2):

Modus Zeilen
A COMPOSE_PROFILES=bundled-proxy, GBE_PROXY_NETWORK= leer, GBE_HTTP_BIND, GBE_HTTPS_BIND, GBE_ACME_EMAIL
B, C sowie D–G und I–L mit Proxy in Docker COMPOSE_PROFILES= leer, GBE_PROXY_NETWORK=<Netz>; bei C zusätzlich GBE_TRAEFIK_ENTRYPOINT, GBE_TRAEFIK_CERTRESOLVER
H sowie D–G und I–L mit Proxy auf dem Host COMPOSE_PROFILES= leer, GBE_PROXY_NETWORK= leer, GBE_WEB_BIND=127.0.0.1:8000
K zusätzlich GBE_CLOUDFLARE_TUNNEL, GBE_CLOUDFLARE_CREDENTIALS
D, E, I, L mit https:// zusätzlich GBE_TLS_CERT, GBE_TLS_KEY
F mit https:// zusätzlich GBE_TLS_PEM

GBE_PROXY_MODE ist der Buchstabe, GBE_PROXY_LOCATION ist docker oder host (der Installer schreibt docker auch bei Modi ohne diese Wahl).

compose.override.yaml für den Proxy

Es gibt vier Formen. Kopieren Sie die passende aus den Beispielen und ersetzen Sie Host, Netz und Adressen.

Mitgelieferter Caddy (Modus A). gbe_proxy gehört zum Profil bundled-proxy (COMPOSE_PROFILES=bundled-proxy in der .env) und liest deploy/proxy/Caddyfile. Die Override-Datei setzt nur seine Ports (installer/tests/snapshots/A/compose.override.yaml):

services:
  gbe_proxy:
    ports: !override
      - "0.0.0.0:80:80"
      - "0.0.0.0:443:443"
      - "0.0.0.0:443:443/udp"

Bei http:// bleibt nur die erste Zeile, für localhost mit 127.0.0.1:<Port der Adresse> statt 0.0.0.0:80 (A-local: "127.0.0.1:8080:80"). https:// mit localhost oder 127.0.0.1 geht mit dem mitgelieferten Caddy nicht (der Installer lehnt es ab); nehmen Sie http://localhost:8080 oder einen Servernamen. Das passende deploy/proxy/Caddyfile (A/deploy__proxy__Caddyfile):

{
	admin off
	email admin@example.org
	servers {
		timeouts {
			read_body 10m
			read_header 30s
			idle 5m
		}
	}
}

gbe.example.org {
	request_body {
		max_size 50MB
	}
	encode zstd gzip
	reverse_proxy gbe_web:8000 {
		# Dialoggeführt waits for the language model inside the request.
		transport http {
			read_timeout 300s
			write_timeout 300s
		}
	}
}

Die Site-Adresse ist bei https:// der Hostname (Caddy holt das Zertifikat bei Let's Encrypt), bei http:// http://<Hostname>, bei localhost :80. Ist der Name aus dem Internet nicht erreichbar, steht im Site-Block zusätzlich tls internal (Caddys eigene CA; in der .env dann GBE_TLS_PUBLIC=no). Ohne E-Mail-Adresse entfällt die Zeile email.

Proxy in Docker an einem gemeinsamen Netz (B, C sowie D–G und I–L mit docker). gbe_web tritt dem Netz des Proxys bei und ist dort als gbe_web:8000 erreichbar, ohne einen Port zu veröffentlichen (D/compose.override.yaml):

services:
  gbe_web:
    networks: [backend, edge, proxy_net]
networks:
  proxy_net:
    external: true

Das Netz muss vor dem Start existieren, und der Proxy-Container muss ihm ebenfalls angehören:

docker network inspect proxy_net >/dev/null 2>&1 || docker network create proxy_net

Traefik mit Docker-Labels (C). Dazu kommen Labels an gbe_web (C/compose.override.yaml):

services:
  gbe_web:
    networks: [backend, edge, proxy_net]
    labels:
      traefik.enable: "true"
      traefik.docker.network: "proxy_net"
      traefik.http.routers.gbe.rule: "Host(`gbe.example.org`)"
      traefik.http.routers.gbe.entrypoints: "websecure"
      traefik.http.routers.gbe.tls: "true"
      traefik.http.routers.gbe.tls.certresolver: "le"
      traefik.http.routers.gbe.middlewares: "gbe-body,gbe-headers"
      traefik.http.middlewares.gbe-body.buffering.maxRequestBodyBytes: "52428800"
      traefik.http.middlewares.gbe-body.buffering.memRequestBodyBytes: "1048576"
      traefik.http.middlewares.gbe-headers.headers.customrequestheaders.X-Forwarded-Proto: "https"
      traefik.http.services.gbe.loadbalancer.server.port: "8000"
networks:
  proxy_net:
    external: true

Bei http:// entfällt tls: "true", ohne Zertifikatsresolver die Zeile certresolver. Die Antwortzeit begrenzt Traefik am Entrypoint in der statischen Konfiguration; setzen Sie dort mindestens 300 s:

entryPoints:
  websecure:
    address: ":443"
    transport:
      respondingTimeouts:
        readTimeout: 300s
        writeTimeout: 300s

Proxy auf dem Host oder einem anderen Rechner (H sowie D–G und I–L mit host). gbe_web veröffentlicht einen Port (D-host/compose.override.yaml):

services:
  gbe_web:
    ports:
      - "127.0.0.1:8000:8000"

Läuft der Proxy auf einem anderen Rechner, binden Sie an eine Adresse, die er erreicht, tragen in seiner Konfiguration die Adresse dieses Servers als Ziel ein und beschränken den Port per Firewall auf den Proxy; GBE spricht dort unverschlüsseltes HTTP. Der Installer schreibt bei der Adresse 0.0.0.0 das Ziel 127.0.0.1:<Port>, das nur für einen Proxy auf demselben Server passt.

Proxy-Konfiguration

Jeder Proxy muss für GBE:

  • alle Pfade an den Upstream weiterreichen (GBE stellt Seiten, statische Dateien und das Handbuch /docs/ selbst zur Verfügung);
  • Anfragen bis zur Upload-Grenze annehmen (Standard 50 MB);
  • 300 s auf Antworten warten (Lesen und Senden);
  • den Header Host unverändert weitergeben und X-Forwarded-Proto, X-Forwarded-For und X-Forwarded-Host setzen;
  • bei https:// TLS für den Hostnamen terminieren.

Antworten zu komprimieren ist erwünscht, WebSockets braucht GBE nicht. GET /healthz antwortet mit ok (200) bzw. 503, solange die Datenbank nicht erreichbar ist. Geben Sie /media/ nie direkt aus dem Volume frei: GBE prüft bei jeder Datei die Berechtigung.

Vollständiges Beispiel für nginx in Docker mit https:// (D/deploy__proxy__nginx.conf):

server {
    listen 80;
    server_name gbe.example.org;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name gbe.example.org;
    # adjust to your certificate (e.g. /etc/letsencrypt/live/gbe.example.org/fullchain.pem and privkey.pem)
    ssl_certificate     /etc/ssl/certs/gbe.pem;
    ssl_certificate_key /etc/ssl/private/gbe.key;
    client_max_body_size 50m;  # uploaded templates and data files
    gzip on;
    gzip_types application/json text/plain text/css application/javascript image/svg+xml;

    location / {
        # Docker's DNS, asked per request: nginx starts even while GBE is down.
        resolver 127.0.0.11 valid=30s ipv6=off;
        set $gbe_upstream http://gbe_web:8000;
        proxy_pass $gbe_upstream;
        proxy_http_version 1.1;
        proxy_set_header Host              $host;
        proxy_set_header X-Forwarded-For   $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host  $host;
        # Dialoggeführt waits for the language model inside the request.
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
    }
}

Mit nginx auf dem Host (D-host/deploy__proxy__nginx.conf) entfallen resolver und set, dort steht proxy_pass http://127.0.0.1:8000;.

Die übrigen Proxys mit ihren entscheidenden Einstellungen; die vollständigen Dateien stehen in der Vorlage und im Beispiel installer/tests/snapshots/<Modus>/deploy__proxy__<Datei> (Proxy auf dem Host: <Modus>-host):

Modus Datei (Vorlage) Body-Grenze 300 s Besonderheiten
B Caddy caddy.snippet (caddy-snippet.j2) request_body { max_size 50MB } transport http { read_timeout 300s; write_timeout 300s } Site-Block wie in Modus A, ohne globale Optionen; per import in Ihr Caddyfile
C Traefik, Datei-Provider traefik-gbe.yaml (traefik-file.yaml.j2) Middleware buffering.maxRequestBodyBytes: 52428800 respondingTimeouts am Entrypoint Alternative zu den Labels; Service-URL http://gbe_web:8000, Middleware mit customRequestHeaders für X-Forwarded-Proto
E Apache 2.4 apache.conf (apache.conf.j2) LimitRequestBody 52428800 ProxyTimeout 300 Module proxy, proxy_http, headers, deflate, bei https ssl und alias; ProxyPreserveHost On, RequestHeader set X-Forwarded-Proto, RequestHeader unset X-Forwarded-For early
F HAProxy haproxy.cfg (haproxy.cfg.j2) http-request deny deny_status 413 if { req.hdr_val(content-length) gt 52428800 } timeout client und timeout server 300s option forwardfor nach del-header X-Forwarded-For; in Docker resolvers mit nameserver docker 127.0.0.11:53 und init-addr last,libc,none; bei https ssl crt mit einer PEM-Datei aus Zertifikat und Schlüssel
G Nginx Proxy Manager nginx-proxy-manager.md (npm.md.j2) unter Advanced client_max_body_size 50m; proxy_read_timeout 300s;, proxy_send_timeout 300s; Proxy Host in der Weboberfläche: Schema http, Ziel gbe_web, Port 8000 (Proxy auf dem Host: Adresse und Port von gbe_web)
H anderer Proxy REQUIREMENTS.md (requirements.md.j2) wie oben wie oben die Anforderungsliste oben, mit Ihren Werten
I Envoy envoy.yaml (envoy.yaml.j2) keine; Envoy reicht den Body gestreamt weiter. Für eine Grenze den Filter envoy.filters.http.buffer mit max_request_bytes: 52428800 vor dem Router ergänzen (Hinweis im Dateikopf). route: { cluster: gbe_web, timeout: 300s } statische Bootstrap-Konfiguration; use_remote_address: true, Cluster vom Typ STRICT_DNS, Kompression per envoy.filters.http.compressor
J Kong (DB-less) kong.yml (kong.yml.j2) Plugin request-size-limiting mit allowed_payload_size: 50 read_timeout und write_timeout 300000 (ms) preserve_host: true; bei https das Zertifikat in Kong selbst hinterlegen
K Cloudflare Tunnel cloudflared.yml (cloudflared.yml.j2) Cloudflare begrenzt auf 100 MB Cloudflare beendet Anfragen meist nach 100 s Ingress-Regel hostname → service: http://gbe_web:8000 mit httpHostHeader, danach http_status:404
L lighttpd lighttpd.conf (lighttpd.conf.j2) server.max-request-size = 51200 (KiB) server.max-read-idle und server.max-write-idle = 300 proxy.forwarded für for/proto/host; lighttpd löst gbe_web beim Start auf, nach dem Neuerzeugen der Container neu laden

Die Pfade zu Zertifikat und Schlüssel gelten so, wie der Proxy sie sieht (bei einem Proxy-Container also im Container). Der Installer setzt /etc/ssl/certs/gbe.pem und /etc/ssl/private/gbe.key, bei HAProxy /etc/haproxy/certs/gbe.pem.

Konfiguration prüfen

deploy/proxy-check.sh prüft von Hand geschriebene Dateien genauso wie erzeugte, mit dem Image des jeweiligen Werkzeugs (caddy validate, nginx -t, apachectl configtest, haproxy -c, envoy --mode validate, kong config parse, cloudflared tunnel ingress validate, lighttpd -tt, für Kubernetes kubectl kustomize und kubeconform, für Helm helm lint und helm template, dazu docker compose config). GBE muss dafür nicht laufen.

./deploy/proxy-check.sh            # die Dateien im Projekt
./deploy/proxy-check.sh render     # ein anderes Verzeichnis (relativ zum Repository oder absolut)

Das Skript sucht die Dateien unter denselben Pfaden und Namen, die der Installer schreibt: deploy/proxy/<Datei>, compose.override.yaml (zusammen mit compose.yaml und der .env desselben Verzeichnisses, die deshalb vorhanden sein muss), deploy/k8s/kustomization.yaml mit deploy/k8s/jobs/migrate.yaml und deploy/helm-values.yaml. Die Namen gbe_web, web und gbe-web zeigen dabei auf 127.0.0.1. Ein Wegwerf-Zertifikat liegt nur unter den Standardpfaden aus dem vorigen Abschnitt; nennt Ihre Konfiguration andere Pfade, meldet die Prüfung fehlende Dateien, obwohl die Konfiguration stimmen kann. Alles gültig, endet es mit „proxy-check: alles gültig“, sonst mit „proxy-check: FEHLER“.

Starten

docker compose up -d --build --wait
docker compose exec -T gbe_web python manage.py apply_install_settings

up --build baut das Image gbe:<GBE_IMAGE_TAG> aus Backend/, gbe_worker und gbe_sandbox verwenden dasselbe Image. gbe_web legt beim ersten Start die Datenbank an (migrate) und die Startkonten (create_default_users, beides in entrypoint.sh); --wait wartet, bis alle Dienste gesund sind. apply_install_settings schreibt OLLAMA_BASE_URL und LLM_SERVER_TYPE in die Einstellungen und meldet „Inferenzserver: () — aktualisiert“ bzw. „— unverändert“. Bei der ersten Installation füllt GBE die Einstellungen ohnehin aus diesen Werten; nötig ist der Befehl, wenn Sie die Werte später ändern.

Dieselben beiden Befehle übernehmen auch jede spätere Änderung an .env, compose.override.yaml oder ein neues Image, siehe Aktualisierung und Wartung.

Prüfen

docker compose exec -T gbe_worker python manage.py doctor
docker compose exec -T gbe_sandbox python -m gbe_sandbox.selfcheck gbe_db:5432 gbe_valkey:6379
curl -fsS https://gbe.example.org/healthz      # mit Caddys eigener CA zusätzlich -k

doctor endet mit „Alle Prüfungen bestanden.“, der Selbsttest der Sandbox gibt ok aus, /healthz antwortet mit ok. Was die Prüfungen bedeuten, steht unter Installation und Fehlerbehebung.

Kubernetes mit Manifesten

Dateien

Der Installer schreibt deploy/k8s/. Am schnellsten kopieren Sie die Dateien aus installer/tests/snapshots/k8s-<Art>/ (ohne das Präfix deploy__k8s__) und ersetzen Namespace, Image, StorageClass, Größen, Host und Issuer.

Datei Inhalt Werte darin
namespace.yaml Namespace Namespace
storage.yaml PVC gbe-media (Vorlagen, Berichte) Zugriffsart, Größe (20Gi), StorageClass
db.yaml Service und StatefulSet db (postgres:18-alpine) mit volumeClaimTemplates pgdata POSTGRES_USER, POSTGRES_DB und die Argumente von pg_isready (gleich DB_USER und DB_NAME), Größe (10Gi), StorageClass
valkey.yaml Service und Deployment valkey (valkey/valkey:8-alpine) –
backend.yaml Service web und Deployments web und worker Image (zweimal), imagePullPolicy, Zugriffsart des Medien-Volumes
sandbox.yaml Service und Deployment sandbox, NetworkPolicies db-ingress, valkey-ingress, sandbox-ingress Image
ingress.yaml Eingang in den Cluster, siehe unten; entfällt bei none Host, TLS, Issuer, IngressClass
kustomization.yaml Namespace, Liste der Ressourcen, Secret gbe-env aus gbe.env Namespace
gbe.env Kopie der .env, Quelle des Secrets; Rechte 600 –
jobs/migrate.yaml Job gbe-migrate: migrate, create_default_users, apply_install_settings; nicht Teil der Kustomization Image, Namespace (steht in der Datei)

kustomization.yaml aus k8s-nginx:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: gbe
resources:
  - namespace.yaml
  - storage.yaml
  - db.yaml
  - valkey.yaml
  - backend.yaml
  - sandbox.yaml
  - ingress.yaml
secretGenerator:
  - name: gbe-env
    envs: [gbe.env]  # a copy of .env, written by the installer (keep it private, like .env)
generatorOptions:
  disableNameSuffixHash: true  # the migrate Job refers to it by name; pods roll on the env-hash annotation

Secret gbe-env

gbe.env enthält dieselben GBE-Variablen wie die .env bei docker compose (SECRET_KEY bis LLM_API_KEY), ohne die COMPOSE_…-Zeilen; OLLAMA_BASE_URL muss aus dem Cluster erreichbar sein. Web, Worker und der Migrations-Job bekommen alle Werte über envFrom, die Datenbank nur DB_PASSWORD, die Sandbox nur SANDBOX_TOKEN. Kustomize liest gbe.env nur aus dem Verzeichnis der Kustomization:

cp .env deploy/k8s/gbe.env
chmod 600 deploy/k8s/gbe.env

Wollen Sie keine Geheimnisse in einer Datei unter deploy/k8s/, entfernen Sie secretGenerator und generatorOptions aus der Kustomization und legen das Secret selbst an, zum Ändern mit apply:

kubectl create namespace gbe
kubectl -n gbe create secret generic gbe-env --from-env-file=.env --dry-run=client -o yaml | kubectl apply -f -

Die Pod-Vorlagen von web, worker und sandbox tragen die Annotation gbe/env-hash (der Installer setzt die ersten 16 Zeichen des SHA-256 der .env ohne den Zeitstempel im Kopf); ein neuer Wert rollt die Pods neu aus. Ohne diese Annotation starten Sie die Pods nach einer Änderung des Secrets mit kubectl rollout restart neu.

Stolperfallen

  • enableServiceLinks: false steht in jedem Pod. Sonst setzt Kubernetes für jeden Service Variablen wie SANDBOX_PORT=tcp://…; gbe_sandbox liest SANDBOX_PORT als seinen Port und startet nicht.
  • Proben mit Host: localhost: Startup-, Readiness- und Liveness-Probe von web rufen /healthz mit dem Header Host: localhost auf. Ohne ihn sendet das Kubelet die Pod-Adresse, die nicht in ALLOWED_HOSTS steht, und erhält 400.
  • Medien-Volume: Mit ReadWriteOnce haben web und worker die Strategie Recreate, und worker hat eine podAffinity zum Web-Pod (topologyKey: kubernetes.io/hostname), weil das Volume nur auf einem Knoten eingehängt werden kann. Mit ReadWriteMany entfallen beide.
  • NetworkPolicies: db und valkey nehmen nur Verbindungen von Pods mit den Labels gbe/db-access: "true" und app.kubernetes.io/part-of: gbe an (web, worker, Job gbe-migrate), sandbox nur von worker. Ein eigener Pod, der die Datenbank braucht, benötigt dieselben Labels. Wirksam sind die Policies nur mit einem CNI, das sie umsetzt.
  • PostgreSQL 18 legt seine Daten in einem versionierten Verzeichnis unterhalb von /var/lib/postgresql ab; das Volume wird deshalb dort eingehängt, nicht unter /var/lib/postgresql/data wie bei früheren Versionen des Images.
  • Sandbox: läuft als Benutzer 65534 mit schreibgeschütztem Wurzeldateisystem, emptyDir unter /tmp, ohne Capabilities, mit SANDBOX_MEMORY_MB=4096 und einer Speichergrenze von 6Gi. automountServiceAccountToken: false gilt für alle GBE-Pods.
  • imagePullPolicy steht ausdrücklich in jedem Pod. Ohne Registry und mit dem Tag latest wäre der Standard Always, und der Knoten versuchte, gbe:latest von Docker Hub zu holen.

Eingang in den Cluster

Art ingress.yaml enthält Vorlage
caddy ConfigMap caddyfile (Caddyfile wie Modus A mit Upstream web:8000, mit E-Mail-Adresse für Let's Encrypt in der globalen Option email), PVC caddy-data (1 Gi, Zertifikate), Service proxy vom Typ LoadBalancer (Port 80, bei https:// außer mit localhost auch 443), Deployment proxy (caddy:2-alpine, Strategie Recreate) k8s/ingress-caddy.yaml.j2
nginx Ingress mit nginx.ingress.kubernetes.io/proxy-body-size: "50m", proxy-read-timeout: "300", proxy-send-timeout: "300" k8s/ingress-nginx.yaml.j2
traefik Middleware gbe-body (buffering.maxRequestBodyBytes: 52428800) und Ingress mit traefik.ingress.kubernetes.io/router.middlewares: "<Namespace>-gbe-body@kubernetescrd", bei https:// router.tls: "true"; respondingTimeouts am Entrypoint wie bei Compose k8s/ingress-traefik.yaml.j2
haproxy Ingress mit haproxy.org/timeout-server: "300s" k8s/ingress-haproxy.yaml.j2
gateway HTTPRoute gbe mit parentRefs auf Ihr Gateway, timeouts: { request: 300s }; TLS und Body-Grenze am Gateway k8s/ingress-gateway.yaml.j2
none keine Datei; leiten Sie Ihren Proxy auf den Service web, Port 8000 requirements.md.j2

Bei nginx, traefik und haproxy enthält der Ingress mit https:// einen tls-Block mit dem Secret gbe-tls, mit ClusterIssuer zusätzlich cert-manager.io/cluster-issuer: "<Issuer>", und mit IngressClass ingressClassName. Der Ingress für ingress-nginx (k8s-nginx/deploy__k8s__ingress.yaml):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: gbe
  labels:
    app.kubernetes.io/name: ingress
    app.kubernetes.io/part-of: gbe
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
    cert-manager.io/cluster-issuer: "letsencrypt"
spec:
  tls:
    - hosts: [gbe.example.org]
      secretName: gbe-tls
  rules:
    - host: gbe.example.org
      http:
        paths:
          - path: /
            pathType: Prefix
            backend: { service: { name: web, port: { number: 8000 } } }

Ohne Issuer legen Sie das TLS-Secret selbst an: kubectl -n gbe create secret tls gbe-tls --cert=… --key=….

Image ohne Registry

Ohne Registry-Präfix heißt das Image gbe:<Tag> und muss auf jedem Knoten liegen, der GBE-Pods ausführt:

docker build -t gbe:1.4.0 Backend
kind load docker-image gbe:1.4.0 --name kind          # kind
docker save gbe:1.4.0 | sudo k3s ctr images import -  # k3s, auf jedem Knoten

Das Image steht in backend.yaml (zweimal), sandbox.yaml und jobs/migrate.yaml.

Anwenden

kubectl apply -k deploy/k8s
kubectl -n gbe rollout status deployment web --timeout=15m
kubectl -n gbe delete job gbe-migrate --ignore-not-found
kubectl apply -f deploy/k8s/jobs/migrate.yaml
kubectl -n gbe wait --for=condition=complete job/gbe-migrate --timeout=900s
kubectl -n gbe logs job/gbe-migrate --tail=20
kubectl -n gbe rollout restart deployment web worker sandbox

web migriert beim Start selbst (entrypoint.sh); rollout status wartet darauf, damit Web-Pod und Job nie gleichzeitig migrieren. Ein Job lässt sich nicht ändern, deshalb wird der vorige Lauf erst gelöscht. Der Job hängt kein Medien-Volume ein und kann deshalb auch mit ReadWriteOnce auf jedem Knoten laufen. Der Neustart am Ende sorgt dafür, dass ein neu gebautes Image unter demselben Tag verwendet wird; mit Registry und einem anderen Tag als latest (IfNotPresent) behält der Knoten aber das zwischengespeicherte Image, dort hilft nur ein neuer Tag. Bei späteren Änderungen wiederholen Sie dieselben Befehle.

Prüfen

kubectl -n gbe get pods,svc,pvc,ingress -o wide
kubectl -n gbe exec deploy/worker -- python manage.py doctor
kubectl -n gbe exec deploy/sandbox -- python -m gbe_sandbox.selfcheck db:5432 valkey:6379

Helm

Werte-Datei

Das Chart liegt in deploy/helm/gbe; seine Standardwerte stehen in deploy/helm/gbe/values.yaml. Der Installer schreibt deploy/helm-values.yaml (Rechte 600, enthält Geheimnisse). Das Beispiel installer/tests/snapshots/helm-nginx/deploy__helm-values.yaml mit Platzhaltern statt Geheimnissen:

host: "gbe.example.org"
publicScheme: https
publicPort: 443
image:
  repository: "registry.example.org/gbe/gbe"
  tag: "1.4.0"
  pullPolicy: IfNotPresent
ollamaBaseUrl: "http://ollama.ai:11434"
llmServerType: vllm
llmApiKey: ""
secretKey: "<64 Zeichen>"
sandbox:
  token: "<43 Zeichen>"
startAccounts:
  adminPassword: "<16 Zeichen>"
  userPassword: "<16 Zeichen>"
database:
  name: gbe
  user: gbe
  password: "<32 Zeichen>"
  storageSize: 10Gi
media:
  storageSize: 20Gi
storageClass: "fast"
ingress:
  enabled: true
  className: ""
  annotations:  # body limit for uploads, long timeouts (Dialoggeführt waits for the language model)
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
    cert-manager.io/cluster-issuer: "letsencrypt"
  tls:
    enabled: true
    secretName: "gbe-tls"
httpRoute:
  enabled: false
  parentRefs: []
chmod 600 deploy/helm-values.yaml
Schlüssel Bedeutung
host, publicScheme, publicPort öffentliche Adresse; daraus bildet das Chart ALLOWED_HOSTS (<host>,localhost,127.0.0.1) und CSRF_TRUSTED_ORIGINS (mit Port, wenn er nicht der Standardport ist)
image.repository, image.tag, image.pullPolicy <Präfix>/gbe (ohne Registry gbe), Tag, Pull-Policy nach der Regel oben; repository ist Pflicht
ollamaBaseUrl, llmServerType Inferenzserver, aus dem Cluster erreichbar; ollamaBaseUrl ist Pflicht
llmApiKey API-Schlüssel des Inferenzservers; leer: keiner. Steht im Secret <fullname>-env (Schlüssel LLM_API_KEY, nur wenn gesetzt)
secretKey, sandbox.token, database.password Geheimnisse; Pflicht, außer mit existingSecret
startAccounts.adminPassword, startAccounts.userPassword Startpasswörter; leer gelten die öffentlich bekannten
database.name, database.user, database.storageSize, media.storageSize, storageClass Datenbank und Volumes; leere StorageClass: Standard des Clusters
ingress.enabled, ingress.className, ingress.annotations Ingress für nginx, traefik und haproxy; bei gateway und none enabled: false
ingress.tls.enabled, ingress.tls.secretName TLS am Ingress bei https://; der Installer setzt gbe-tls (Standard des Charts: <fullname>-tls)
httpRoute.enabled, httpRoute.parentRefs HTTPRoute für gateway mit - name: <Gateway> und namespace: <Namespace>; hat Vorrang vor dem Ingress

Annotationen je Controller, wie sie der Installer setzt:

Eingang ingress.annotations
nginx nginx.ingress.kubernetes.io/proxy-body-size: "50m", proxy-read-timeout: "300", proxy-send-timeout: "300"
haproxy haproxy.org/timeout-server: "300s"
traefik bei https:// traefik.ingress.kubernetes.io/router.tls: "true", sonst {}; respondingTimeouts am Entrypoint von Traefik auf mindestens 300 s. Eine Body-Grenze gibt es hier nicht: Das Chart legt keine Middleware an, und Traefik begrenzt von sich aus nicht.
gateway, none {}

Mit https:// und ClusterIssuer kommt bei nginx, haproxy und traefik cert-manager.io/cluster-issuer: "<Issuer>" dazu. Den Timeout von 300 s setzt das Chart in der HTTPRoute selbst.

Weitere Schlüssel des Charts, die der Installer nicht setzt: imagePullSecrets, existingSecret, sandbox.memoryMb (4096), sandbox.resources, networkPolicy.enabled (true), worker.enabled (true) und resources.web, resources.worker, resources.postgres, resources.valkey.

Geheimnisse in einem eigenen Secret

Statt die Geheimnisse in die Werte-Datei zu schreiben, verweisen Sie mit existingSecret auf ein Secret mit den Schlüsseln SECRET_KEY, DB_PASSWORD und SANDBOX_TOKEN sowie DEFAULT_ADMIN_PASSWORD und DEFAULT_USER_PASSWORD:

kubectl create namespace gbe
kubectl -n gbe create secret generic gbe-secrets --from-env-file=gbe-secrets.env

gbe-secrets.env enthält die fünf Zeilen SCHLÜSSEL=Wert, bei einem Inferenzserver mit API-Schlüssel als sechste LLM_API_KEY=…; löschen Sie die Datei danach oder geben Sie ihr die Rechte 600. In der Werte-Datei steht dann existingSecret: gbe-secrets; secretKey, sandbox.token, database.password, startAccounts und llmApiKey entfallen, das Chart legt kein eigenes Secret an.

Warning

Startpasswörter im eigenen Secret

Für das Chart sind DEFAULT_ADMIN_PASSWORD und DEFAULT_USER_PASSWORD optional. Fehlen sie im Secret, legt create_default_users die Startkonten mit den öffentlich bekannten Passwörtern an. Ein geändertes eigenes Secret rollt die Pods nicht neu aus (die Prüfsumme checksum/secret sieht es nicht); starten Sie sie danach mit kubectl rollout restart neu.

Installieren

Die Objektnamen beginnen mit dem fullname (Release gbe → gbe, Release prod → prod-gbe):

helm upgrade --install gbe deploy/helm/gbe -n gbe --create-namespace -f deploy/helm-values.yaml --wait --timeout 15m
kubectl -n gbe rollout restart deployment gbe-app gbe-sandbox
kubectl -n gbe rollout status deployment gbe-app --timeout=15m
kubectl -n gbe exec deploy/gbe-app -c web -- python manage.py apply_install_settings

Web und Worker laufen als Container web und worker im Pod <fullname>-app; der Container web migriert beim Start selbst, einen Migrations-Job gibt es bei Helm nicht. Der Neustart nach helm upgrade sorgt wie bei den Manifesten für ein neu gebautes Image unter demselben Tag. Nach der Installation zeigt Helm die Hinweise aus NOTES.txt mit den Befehlen für Status und Protokolle.

Prüfen

helm -n gbe status gbe
kubectl -n gbe get pods -l app.kubernetes.io/instance=gbe
kubectl -n gbe exec deploy/gbe-app -c worker -- python manage.py doctor
kubectl -n gbe exec deploy/gbe-sandbox -- python -m gbe_sandbox.selfcheck gbe-postgres:5432 gbe-valkey:6379

Die Werte-Datei prüfen Sie vorab mit helm lint deploy/helm/gbe -f deploy/helm-values.yaml und helm template gbe deploy/helm/gbe -f deploy/helm-values.yaml.

Befehle ohne just

just liest die .env und nimmt daraus Namespace (GBE_K8S_NAMESPACE), Release (GBE_HELM_RELEASE), fullname (GBE_HELM_FULLNAME) und Tag (GBE_IMAGE_TAG). Die Tabelle zeigt die Standardwerte gbe und latest.

Rezept Befehle
just install ./install.sh
just up docker compose up -d --build --wait, dann docker compose exec -T gbe_web python manage.py apply_install_settings
just down docker compose down
just ps docker compose ps
just logs <Dienst> docker compose logs -f --tail=200 <Dienst>
just doctor die drei Befehle unter „Prüfen“ bei docker compose
just backup [--encrypt] [Verzeichnis] deploy/backup.sh backup [--encrypt] [Verzeichnis], siehe Sicherung und Wiederherstellung (die Einzelschritte unter „Von Hand sichern“)
just restore [--force] [--no-config] <Archiv> deploy/backup.sh restore [--force] [--no-config] <Archiv>
just proxy-check ./deploy/proxy-check.sh .
just proxy-check-all ./deploy/proxy-check.sh --all
just k8s-build docker build -t gbe:latest Backend; mit Registry-Präfix zusätzlich docker tag und docker push nach <Präfix>/gbe:latest
just k8s-load <Cluster> kind load docker-image gbe:latest --name <Cluster>
just k8s-up kubectl apply -k deploy/k8s, dann kubectl -n gbe rollout restart deployment web worker sandbox
just k8s-migrate die fünf Befehle von rollout status deployment web bis logs job/gbe-migrate unter „Anwenden“
just k8s-status kubectl -n gbe get pods,svc,pvc,ingress -o wide
just k8s-logs <Teil> kubectl -n gbe logs -f --tail=200 -l app.kubernetes.io/name=<Teil>
just k8s-doctor die beiden exec-Befehle unter „Prüfen“ bei den Manifesten
just k8s-backup …, just k8s-restore … deploy/backup.sh backup --k8s … bzw. deploy/backup.sh restore --k8s …
just k8s-down kubectl -n gbe delete deployment,statefulset,service,ingress,job,networkpolicy,configmap -l app.kubernetes.io/part-of=gbe, dann kubectl -n gbe delete httproute,middlewares.traefik.io -l app.kubernetes.io/part-of=gbe (Fehler dabei sind unerheblich); PVCs, Secret und Namespace bleiben
just helm-up die vier Befehle unter „Installieren“ bei Helm
just helm-status helm -n gbe status gbe, kubectl -n gbe get pods -l app.kubernetes.io/instance=gbe
just helm-doctor die beiden exec-Befehle unter „Prüfen“ bei Helm; braucht worker.enabled: true
just helm-backup …, just helm-restore … deploy/backup.sh backup --helm … bzw. deploy/backup.sh restore --helm …
just helm-down helm -n gbe uninstall gbe; die PVCs bleiben

Was der Installer zusätzlich tut

Beim manuellen Weg übernehmen Sie auch das, was der Installer nebenbei erledigt:

  • Prüfungen vor dem Start: belegte Ports (Modus A: die HTTP- und HTTPS-Adressen; H und Proxy auf dem Host: der Port von gbe_web), bei Kubernetes eine fehlende Adresse des Inferenzservers, in Modus A eine https://-Adresse mit localhost, vorhandene Werkzeuge (install.sh prüft docker und just vorab, kubectl und helm vor dem Start). Prüfen Sie Ports etwa mit ss -ltn.
  • Gedächtnis: Die .env ist die Vorgabe für den nächsten Lauf; Geheimnisse bleiben erhalten, --rotate-secrets erneuert SECRET_KEY und SANDBOX_TOKEN.
  • Sicherungen: Vor jeder Änderung einer erzeugten Datei legt er <Datei>.bak-JJJJMMTT-HHMMSS an; unveränderte Dateien schreibt er nicht neu.
  • Abgeleitete Werte und Prüfsummen: ALLOWED_HOSTS, CSRF_TRUSTED_ORIGINS, Upstreams, Body-Grenzen, die Annotationen gbe/env-hash und gbe/caddyfile-hash sowie die Kopie deploy/k8s/gbe.env entstehen bei jedem Lauf neu aus denselben Antworten.
  • Rechte 600 für .env, deploy/k8s/gbe.env und deploy/helm-values.yaml.
  • Übernahme des alten Installers aus deploy/generated/, siehe Installation.
  • Hinweise: gefundene Proxys, die „Nächste Schritte:“ und die Prüfung des Docker-Netzes vor docker compose up.

Weiterführend

Nach der Installation im Handbuch unter /docs/ (Kapitel „Administration“) oder direkt in den Quellen: