docs(quick-260914-ku1): Betriebshandbuch — Zwei Kanäle Live und Beta, Freigabe, Hotfix ohne Datenbankänderung, neuer Live-Server; ci-cd-setup auf gemessenen Stand
- anleitung-betrieb.md: neues Kapitel 9 (Kanal, IMAGE_TAG je Server, Freigabe, Hotfix-Ablauf mit Regel "Keine Datenbankaenderung als Hotfix", drei Kontrollwege, Einrichtung des Live-Servers, Erstfreigabe v1.0.0); Inhaltsverzeichnis, Tabelle in Kapitel 1, IMAGE_TAG in Kapitel 3, Etiketten in Kapitel 4, Startzeile in Kapitel 7 - ci-cd-setup.md: REGISTRY_TOKEN und Push ueber localhost:3002, Trigger main/live/v*, Jobs quality -> test -> publish, Etiketten- und Build-Arg-Tabellen, D-13 ueberholt, Tag-/Branch-Schutz-Empfehlung (T-KU1-04), Fehlerbehebung fuer den Stempel Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
This commit is contained in:
+184
-9
@@ -19,6 +19,7 @@ Betrieb der bereits laufenden Installation, nicht deren automatisierten Build.
|
|||||||
6. [Sicherung und Wiederherstellung](#6-sicherung-und-wiederherstellung)
|
6. [Sicherung und Wiederherstellung](#6-sicherung-und-wiederherstellung)
|
||||||
7. [Protokolle und Fehlersuche](#7-protokolle-und-fehlersuche)
|
7. [Protokolle und Fehlersuche](#7-protokolle-und-fehlersuche)
|
||||||
8. [Abgrenzung zur CI/CD-Pipeline](#8-abgrenzung-zur-cicd-pipeline)
|
8. [Abgrenzung zur CI/CD-Pipeline](#8-abgrenzung-zur-cicd-pipeline)
|
||||||
|
9. [Zwei Kanäle: Live und Beta](#9-zwei-kanäle-live-und-beta)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -29,8 +30,8 @@ Tessera besteht aus drei Containern, definiert in `docker-compose.yml` (Basis) u
|
|||||||
|
|
||||||
| Dienst | Image / Build | Host-Port | Zweck |
|
| Dienst | Image / Build | Host-Port | Zweck |
|
||||||
|--------|---------------|-----------|-------|
|
|--------|---------------|-----------|-------|
|
||||||
| `web` | `apps/web/Dockerfile` (Basis) bzw. `git.vicolab.de/schalli/tessera-ctl/web:latest` (Prod) | 3000 | Next.js-Frontend (Portal, Dashboard) |
|
| `web` | `apps/web/Dockerfile` (Basis) bzw. `git.vicolab.de/schalli/tessera-ctl/web:${IMAGE_TAG}` (Prod; `beta` oder `live`, siehe Kapitel 9) | 3000 | Next.js-Frontend (Portal, Dashboard) |
|
||||||
| `api` | `apps/api/Dockerfile` (Basis) bzw. `git.vicolab.de/schalli/tessera-ctl/api:latest` (Prod) | 3001 | NestJS-Backend (REST-API, Prisma/PostgreSQL-Zugriff) |
|
| `api` | `apps/api/Dockerfile` (Basis) bzw. `git.vicolab.de/schalli/tessera-ctl/api:${IMAGE_TAG}` (Prod; `beta` oder `live`, siehe Kapitel 9) | 3001 | NestJS-Backend (REST-API, Prisma/PostgreSQL-Zugriff) |
|
||||||
| `db` | `postgres:16-alpine` | kein Host-Port | PostgreSQL-Datenbank |
|
| `db` | `postgres:16-alpine` | kein Host-Port | PostgreSQL-Datenbank |
|
||||||
|
|
||||||
Zusätzlich existiert `docker-compose.dev.yml` (Bind-Mounts für Live-Reload,
|
Zusätzlich existiert `docker-compose.dev.yml` (Bind-Mounts für Live-Reload,
|
||||||
@@ -159,6 +160,7 @@ Zugangsdaten.
|
|||||||
| `TESSERA_SMTP_HOST` / `_PORT` / `_SECURE` / `_USER` / `_PASSWORD` / `_FROM` | nein (aber ohne Host kein Mailversand) | Host leer, Port `587`, `_SECURE=false` | SMTP-Relay für ausgehende Mails (Passwort-Reset, Benachrichtigungen). |
|
| `TESSERA_SMTP_HOST` / `_PORT` / `_SECURE` / `_USER` / `_PASSWORD` / `_FROM` | nein (aber ohne Host kein Mailversand) | Host leer, Port `587`, `_SECURE=false` | SMTP-Relay für ausgehende Mails (Passwort-Reset, Benachrichtigungen). |
|
||||||
| `APP_URL` | empfohlen | `http://localhost:3001` (für `NEXT_PUBLIC_API_URL`) / `http://localhost:3000` (für `TESSERA_APP_URL`) | Öffentliche Basis-URL der Web-Oberfläche. Wird serverseitig u. a. für in E-Mails generierte Links verwendet (`TESSERA_APP_URL`). |
|
| `APP_URL` | empfohlen | `http://localhost:3001` (für `NEXT_PUBLIC_API_URL`) / `http://localhost:3000` (für `TESSERA_APP_URL`) | Öffentliche Basis-URL der Web-Oberfläche. Wird serverseitig u. a. für in E-Mails generierte Links verwendet (`TESSERA_APP_URL`). |
|
||||||
| `API_INTERNAL_URL` | fest verdrahtet | `http://api:3001` | Adresse, unter der `web` die API **innerhalb** des Docker-Netzes erreicht; dorthin schreibt Next.js die `/api-proxy/*`-Rewrites um. In der Regel nicht ändern. |
|
| `API_INTERNAL_URL` | fest verdrahtet | `http://api:3001` | Adresse, unter der `web` die API **innerhalb** des Docker-Netzes erreicht; dorthin schreibt Next.js die `/api-proxy/*`-Rewrites um. In der Regel nicht ändern. |
|
||||||
|
| `IMAGE_TAG` | empfohlen | `beta` | Welcher Kanal auf diesem Server läuft: `beta` (alle Neuerungen, alpha) oder `live` (nur freigegebene Versionen, tessera.ctl.de). Siehe Kapitel 9. |
|
||||||
|
|
||||||
Hinweis zu `NEXT_PUBLIC_API_URL`: Diese Variable wird beim Image-Build bereits fest
|
Hinweis zu `NEXT_PUBLIC_API_URL`: Diese Variable wird beim Image-Build bereits fest
|
||||||
auf `/api-proxy` gesetzt (`ENV NEXT_PUBLIC_API_URL=/api-proxy` in
|
auf `/api-proxy` gesetzt (`ENV NEXT_PUBLIC_API_URL=/api-proxy` in
|
||||||
@@ -191,7 +193,7 @@ docker compose -f docker-compose.prod.yml up -d --force-recreate api web
|
|||||||
**Verifizierte Falle:** `docker compose up -d` **ohne** `--force-recreate` ersetzt
|
**Verifizierte Falle:** `docker compose up -d` **ohne** `--force-recreate` ersetzt
|
||||||
einen bereits laufenden Container **nicht**, wenn Compose der Meinung ist, an der
|
einen bereits laufenden Container **nicht**, wenn Compose der Meinung ist, an der
|
||||||
Service-Definition habe sich nichts geändert – auch wenn `pull` gerade ein neues
|
Service-Definition habe sich nichts geändert – auch wenn `pull` gerade ein neues
|
||||||
Image unter demselben Tag (`:latest`) heruntergeladen hat. Der alte Container läuft
|
Image unter demselben Etikett (`beta` bzw. `live`) heruntergeladen hat. Der alte Container läuft
|
||||||
dann unverändert mit dem alten Code weiter, ohne Fehlermeldung. Das Deployment
|
dann unverändert mit dem alten Code weiter, ohne Fehlermeldung. Das Deployment
|
||||||
wirkt erfolgreich, ist es aber nicht. Dieser Fehler ist dem Team schon mehrfach
|
wirkt erfolgreich, ist es aber nicht. Dieser Fehler ist dem Team schon mehrfach
|
||||||
passiert. Deshalb: nach jedem `pull` **immer** `--force-recreate` verwenden (nur
|
passiert. Deshalb: nach jedem `pull` **immer** `--force-recreate` verwenden (nur
|
||||||
@@ -204,12 +206,15 @@ Erstellungszeitpunkt des zugehörigen Images vergleichen. Der Container muss
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker inspect -f '{{.State.StartedAt}}' tessera-api-1
|
docker inspect -f '{{.State.StartedAt}}' tessera-api-1
|
||||||
docker image inspect -f '{{.Created}}' git.vicolab.de/schalli/tessera-ctl/api:latest
|
docker image inspect -f '{{.Created}}' git.vicolab.de/schalli/tessera-ctl/api:beta
|
||||||
|
|
||||||
docker inspect -f '{{.State.StartedAt}}' tessera-web-1
|
docker inspect -f '{{.State.StartedAt}}' tessera-web-1
|
||||||
docker image inspect -f '{{.Created}}' git.vicolab.de/schalli/tessera-ctl/web:latest
|
docker image inspect -f '{{.Created}}' git.vicolab.de/schalli/tessera-ctl/web:beta
|
||||||
```
|
```
|
||||||
|
|
||||||
|
(Auf dem Live-Server statt `:beta` jeweils `:live` einsetzen – das Etikett, das in
|
||||||
|
der `.env` als `IMAGE_TAG` steht, siehe Kapitel 9.)
|
||||||
|
|
||||||
(Container-Namen mit `docker compose -f docker-compose.prod.yml ps` prüfen, falls
|
(Container-Namen mit `docker compose -f docker-compose.prod.yml ps` prüfen, falls
|
||||||
sie auf dem Server abweichen.) Liegt `StartedAt` **vor** `Created` des Images, läuft
|
sie auf dem Server abweichen.) Liegt `StartedAt` **vor** `Created` des Images, läuft
|
||||||
noch die alte Version – dann `--force-recreate` nachholen.
|
noch die alte Version – dann `--force-recreate` nachholen.
|
||||||
@@ -317,8 +322,10 @@ docker compose -f docker-compose.prod.yml logs -f db
|
|||||||
|
|
||||||
**Gesunder Start sieht so aus:** `db` wird `healthy`, danach startet `api` und
|
**Gesunder Start sieht so aus:** `db` wird `healthy`, danach startet `api` und
|
||||||
protokolliert die durchgelaufenen Prisma-Migrationen sowie zuletzt
|
protokolliert die durchgelaufenen Prisma-Migrationen sowie zuletzt
|
||||||
`Tessera API running on port 3001` (aus `apps/api/src/main.ts`). Erst danach startet
|
`Tessera API running on port 3001` und direkt darunter eine Zeile wie
|
||||||
`web`, weil `depends_on: api: condition: service_healthy` das erzwingt.
|
`Tessera API v1.0.0 (live) abc1234` (beides aus `apps/api/src/main.ts`) – Version,
|
||||||
|
Kanal und Kurzkennung des Standes, der gerade läuft (siehe Kapitel 9). Erst danach
|
||||||
|
startet `web`, weil `depends_on: api: condition: service_healthy` das erzwingt.
|
||||||
|
|
||||||
| Symptom | Wahrscheinliche Ursache | Prüfen / Beheben |
|
| Symptom | Wahrscheinliche Ursache | Prüfen / Beheben |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
@@ -342,5 +349,173 @@ Berechtigungen des `act_runner`-Docker-Socket-Mounts) aufgesetzt wird, ist bewus
|
|||||||
nicht Teil dieses Dokuments – das steht vollständig in
|
nicht Teil dieses Dokuments – das steht vollständig in
|
||||||
[`docs/ci-cd-setup.md`](./ci-cd-setup.md). Die Grenze zwischen beiden Dokumenten:
|
[`docs/ci-cd-setup.md`](./ci-cd-setup.md). Die Grenze zwischen beiden Dokumenten:
|
||||||
Sobald ein neues Image lokal vorliegt oder in einer Registry verfügbar ist, beginnt
|
Sobald ein neues Image lokal vorliegt oder in einer Registry verfügbar ist, beginnt
|
||||||
dieses Betriebshandbuch (Kapitel 4); alles davor – wie das Image entsteht – gehört
|
dieses Betriebshandbuch (Kapitel 4 und 9); alles davor – wie das Image entsteht –
|
||||||
in das CI/CD-Runbook.
|
gehört in das CI/CD-Runbook.
|
||||||
|
|
||||||
|
## 9. Zwei Kanäle: Live und Beta
|
||||||
|
|
||||||
|
Seit September 2026 gibt es Tessera in zwei Ausgaben, die getrennt voneinander
|
||||||
|
laufen. Dieses Kapitel erklärt, was das bedeutet, welche Zeile auf welchem Server
|
||||||
|
stehen muss, wie eine Version freigegeben wird, wie ein dringender Fehler auf Live
|
||||||
|
behoben wird, und wie Sie jederzeit sehen, welche Fassung gerade läuft.
|
||||||
|
|
||||||
|
### Was ein Kanal ist
|
||||||
|
|
||||||
|
Ein Kanal ist eine Ausgabe von Tessera, die auf einem bestimmten Server läuft und
|
||||||
|
nach eigenen Regeln neue Stände bekommt. Es gibt zwei:
|
||||||
|
|
||||||
|
- **Beta** – alles Neue, sofort nach jeder Änderung. Läuft unter
|
||||||
|
`alpha.tessera.ctl.de`. Das Etikett (die Kennzeichnung des Docker-Images in der
|
||||||
|
Registry) heißt `beta`. Das ältere Etikett `latest` ist nur ein zweiter Name für
|
||||||
|
genau dasselbe Beta-Image; es bleibt vorerst bestehen, damit nichts kaputtgeht,
|
||||||
|
und kann später wegfallen.
|
||||||
|
- **Live** – nur freigegebene Versionen mit einer Nummer. Läuft unter
|
||||||
|
`tessera.ctl.de` auf dem neuen Server. Das Etikett heißt `live`; zusätzlich trägt
|
||||||
|
jede freigegebene Version ihre Nummer als eigenes Etikett (`v1.0.0`, `v1.0.1`, …),
|
||||||
|
damit man jederzeit auch einen älteren Stand gezielt holen kann.
|
||||||
|
|
||||||
|
Die Versionsnummer kommt aus der Freigabe (in Git heißt das „Tag“ – eine Markierung
|
||||||
|
an einem bestimmten Stand), nicht aus einer Datei im Code. Zwischen zwei Freigaben
|
||||||
|
zeigt die Beta eine Kennung wie `v1.0.0-12-gabc1234`: das bedeutet „12 Änderungen
|
||||||
|
nach Version 1.0.0, Stand abc1234“. Vor der allerersten Freigabe steht dort nur die
|
||||||
|
Kurzkennung des Standes (sieben Zeichen, z. B. `abc1234`).
|
||||||
|
|
||||||
|
### Die eine Zeile je Server
|
||||||
|
|
||||||
|
Welchen Kanal ein Server bekommt, entscheidet **eine einzige Zeile** in der Datei
|
||||||
|
`/opt/tessera/.env`:
|
||||||
|
|
||||||
|
- auf **alpha** (Beta): `IMAGE_TAG=beta`
|
||||||
|
- auf dem **neuen Live-Server**: `IMAGE_TAG=live`
|
||||||
|
|
||||||
|
Fehlt die Zeile ganz, nimmt die Compose-Datei von selbst `beta`. Für den Live-Server
|
||||||
|
ist die Zeile also Pflicht, sonst zieht er die Beta.
|
||||||
|
|
||||||
|
Weil `/opt/tessera` keine Arbeitskopie des Repositorys ist (siehe Kapitel 3,
|
||||||
|
„Konfigurationsdrift“), muss die Compose-Datei auf dem Server einmal von Hand
|
||||||
|
angepasst werden. Auf alpha ist das die Datei `/opt/tessera/docker-compose.prod.yml`
|
||||||
|
(die `.env` dort verweist mit `COMPOSE_FILE` auf sie). Zuerst eine Sicherung:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/tessera
|
||||||
|
cp docker-compose.prod.yml docker-compose.prod.yml.bak.$(date +%Y%m%d)
|
||||||
|
```
|
||||||
|
|
||||||
|
Dann die zwei `image:`-Zeilen (eine beim Dienst `web`, eine beim Dienst `api`) auf
|
||||||
|
diese Form bringen – der einzige Unterschied zu heute ist das Ende der Zeile:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
image: git.vicolab.de/schalli/tessera-ctl/web:${IMAGE_TAG:-beta}
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
image: git.vicolab.de/schalli/tessera-ctl/api:${IMAGE_TAG:-beta}
|
||||||
|
```
|
||||||
|
|
||||||
|
Danach wie in Kapitel 4:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.prod.yml pull
|
||||||
|
docker compose -f docker-compose.prod.yml up -d --force-recreate api web
|
||||||
|
```
|
||||||
|
|
||||||
|
Hinweis: Die Vorlage `.env.prod.example` im Repository enthält die Zeile
|
||||||
|
`IMAGE_TAG` noch nicht. Wer eine neue `.env` aus der Vorlage anlegt, ergänzt die
|
||||||
|
Zeile von Hand.
|
||||||
|
|
||||||
|
### Eine Version freigeben
|
||||||
|
|
||||||
|
Das Freigeben erledigt Claude; Sie sagen nur „Version X freigeben“. Zur Einordnung,
|
||||||
|
was dabei passiert:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout live
|
||||||
|
git merge --ff-only main
|
||||||
|
git tag -a vX.Y.Z -m "Tessera X.Y.Z"
|
||||||
|
git push origin live vX.Y.Z
|
||||||
|
```
|
||||||
|
|
||||||
|
Der zweite Befehl übernimmt den Stand der Beta in den Live-Zweig. Wenn er sich
|
||||||
|
weigert, ist eine frühere Korrektur (siehe Hotfix, Schritt 5) noch nicht zurück in
|
||||||
|
`main` – dann wird erst das nachgeholt. Der Push löst die Pipeline zweimal aus: der
|
||||||
|
Zweig `live` wird nur geprüft, der Tag `vX.Y.Z` wird gebaut und als `live` und
|
||||||
|
`vX.Y.Z` abgelegt. Das dauert etwa vier bis sechs Minuten.
|
||||||
|
|
||||||
|
Danach spielen Sie die Version auf dem Live-Server ein – Kapitel 4 gilt unverändert:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.prod.yml pull
|
||||||
|
docker compose -f docker-compose.prod.yml up -d --force-recreate api web
|
||||||
|
```
|
||||||
|
|
||||||
|
**Erstfreigabe v1.0.0:** Den Zweig `live` gibt es noch nicht. Er entsteht beim
|
||||||
|
ersten Mal aus `main` (`git checkout -b live main`), bekommt den Tag `v1.0.0` und
|
||||||
|
wird zusammen mit dem Tag gepusht. Das erfolgt, sobald der Knopf „Fehler melden“
|
||||||
|
eingebaut ist – nicht in diesem Durchlauf.
|
||||||
|
|
||||||
|
### Einen Fehler auf Live beheben (Hotfix)
|
||||||
|
|
||||||
|
Ein Hotfix ist eine kleine Korrektur, die auf Live landen muss, **ohne** die
|
||||||
|
Neuerungen der Beta mitzunehmen. Ablauf (Claude führt die Git-Schritte aus, Sie
|
||||||
|
spielen ein):
|
||||||
|
|
||||||
|
1. Den Live-Stand holen: `git checkout live && git pull`.
|
||||||
|
2. Einen Korrekturzweig `hotfix/<kurzer-name>` von `live` anlegen.
|
||||||
|
3. Die Korrektur machen und die Tests laufen lassen.
|
||||||
|
4. Nach `live` mergen, die nächste Nummer vergeben (`vX.Y.(Z+1)`, also z. B.
|
||||||
|
`v1.0.1` nach `v1.0.0`) und beides pushen: `git push origin live vX.Y.(Z+1)`.
|
||||||
|
Danach spielen Sie die Version auf dem Live-Server ein (Befehle wie oben).
|
||||||
|
5. Die Korrektur in die Beta übernehmen: `git checkout main && git merge live`.
|
||||||
|
Vorher wird geprüft, ob die Korrektur dort noch zusammenpasst (Konflikte, Tests),
|
||||||
|
dann `git push` – die Beta bekommt sie mit dem nächsten Pipeline-Lauf.
|
||||||
|
|
||||||
|
**Keine Datenbankänderung als Hotfix.** Der Grund in Alltagssprache:
|
||||||
|
Datenbankänderungen (Migrationen) tragen einen Zeitstempel im Namen und werden in
|
||||||
|
dieser Reihenfolge ausgeführt. Die Beta hat womöglich schon neuere Änderungen
|
||||||
|
eingespielt. Eine Hotfix-Änderung mit noch späterem Zeitstempel landet beim
|
||||||
|
Übernehmen in die Beta hinter Änderungen, die sie eigentlich nicht kennt – das ist
|
||||||
|
der eine Fall, der beim Zusammenführen still kaputtgehen kann. Braucht eine Korrektur
|
||||||
|
eine Datenbankänderung, wird sie als reguläre Version über `main` freigegeben.
|
||||||
|
|
||||||
|
### Woran Sie erkennen, welche Version läuft
|
||||||
|
|
||||||
|
Drei Wege, vom einfachsten zum genauesten:
|
||||||
|
|
||||||
|
1. **In der Oberfläche:** Unten in der Seitenleiste steht `v1.0.0 · Live` bzw.
|
||||||
|
`v1.0.0-12-gabc1234 · Beta`. Wenn Sie die Maus darüber halten, erscheinen die
|
||||||
|
Kurzkennung des Standes und die Version, die der Server meldet. Weichen
|
||||||
|
Oberfläche und Server voneinander ab, wurde nur einer der beiden Container neu
|
||||||
|
erstellt – dann Kapitel 4 anwenden (`--force-recreate api web`).
|
||||||
|
2. **Auf dem Server per Abfrage:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s http://localhost:3001/health/version
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Antwort enthält die Felder `version`, `channel` (`beta` oder `live`),
|
||||||
|
`commit` (Kurzkennung) und `buildTime` (wann das Image gebaut wurde).
|
||||||
|
3. **Im Protokoll:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.prod.yml logs api | grep "Tessera API"
|
||||||
|
```
|
||||||
|
|
||||||
|
Zeigt die Startzeile `Tessera API v1.0.0 (live) abc1234` (siehe Kapitel 7).
|
||||||
|
|
||||||
|
### Den neuen Live-Server einrichten
|
||||||
|
|
||||||
|
Kapitel 2 gilt vollständig. Die Abweichungen gegenüber alpha:
|
||||||
|
|
||||||
|
- In der `.env` steht `IMAGE_TAG=live`.
|
||||||
|
- Eigene, neu erzeugte Geheimnisse: `JWT_SECRET`, `TESSERA_ENCRYPTION_KEY`,
|
||||||
|
`DB_PASSWORD` und das Admin-Passwort. Nichts davon von alpha übernehmen.
|
||||||
|
- Eine eigene, leere Datenbank. Die API legt beim ersten Start den ersten Admin an
|
||||||
|
(Kapitel 2, Schritt 4). Die alpha-Datenbank wird **nicht** kopiert – es sei denn,
|
||||||
|
das wird ausdrücklich gewünscht. In diesem Fall gilt Kapitel 6 (Wiederherstellung)
|
||||||
|
**und** der Live-Server muss denselben `TESSERA_ENCRYPTION_KEY` wie alpha
|
||||||
|
bekommen, sonst sind alle gespeicherten Zugangsdaten unbrauchbar.
|
||||||
|
- `APP_URL=https://tessera.ctl.de`.
|
||||||
|
- Der erste `pull` holt das Etikett `live`. Vor der Erstfreigabe v1.0.0 gibt es
|
||||||
|
dieses Etikett noch nicht – deshalb erst freigeben, dann installieren. Für einen
|
||||||
|
Probelauf davor kann vorübergehend `IMAGE_TAG=beta` stehen; danach auf `live`
|
||||||
|
umstellen und `pull` + `up -d --force-recreate api web` wiederholen.
|
||||||
|
|||||||
+81
-19
@@ -80,11 +80,24 @@ docker ps --filter name=gitea-runner
|
|||||||
|
|
||||||
### Gitea Secrets (fuer die CI-Pipeline)
|
### Gitea Secrets (fuer die CI-Pipeline)
|
||||||
|
|
||||||
In Gitea unter **Repository > Settings > Actions > Secrets** koennen Secrets
|
In Gitea unter **Repository > Settings > Actions > Secrets** werden die Secrets
|
||||||
fuer die Pipeline konfiguriert werden. Aktuell werden keine Secrets in der
|
fuer die Pipeline konfiguriert. Benoetigt wird genau eines:
|
||||||
Pipeline benoetigt, da Images lokal gebaut und deployed werden (kein Registry-Push).
|
|
||||||
|
|
||||||
Falls kuenftig Deploy-Pfade oder Credentials benoetigt werden:
|
| Secret | Beschreibung |
|
||||||
|
|--------|--------------|
|
||||||
|
| `REGISTRY_TOKEN` | Gitea-Zugangstoken (Access Token) mit Schreibrecht auf Pakete (`package: write`). Wird im Job `publish` fuer `docker login localhost:3002 --password-stdin` verwendet. |
|
||||||
|
|
||||||
|
Das Token erscheint nie im Log: es wird per `--password-stdin` uebergeben und
|
||||||
|
Gitea maskiert Secret-Werte in der Job-Ausgabe. Das Veroeffentlichungs-Skript
|
||||||
|
`.gitea/scripts/publish-images.sh` kennt das Token nicht; der Login bleibt im
|
||||||
|
Workflow.
|
||||||
|
|
||||||
|
Der Push geht ueber `localhost:3002` (Gitea laeuft auf demselben Rechner wie der
|
||||||
|
Runner), weil der Nginx Proxy Manager vor `git.vicolab.de` grosse Image-Blobs
|
||||||
|
blockt. Das Pullen auf den Servern laeuft ueber `git.vicolab.de`
|
||||||
|
(`docker-compose.prod.yml`).
|
||||||
|
|
||||||
|
Weitere Secrets bei Bedarf:
|
||||||
|
|
||||||
1. In Gitea **Settings > Actions > Secrets** den Secret anlegen
|
1. In Gitea **Settings > Actions > Secrets** den Secret anlegen
|
||||||
2. In `.gitea/workflows/ci.yml` ueber `${{ secrets.SECRET_NAME }}` referenzieren
|
2. In `.gitea/workflows/ci.yml` ueber `${{ secrets.SECRET_NAME }}` referenzieren
|
||||||
@@ -94,27 +107,62 @@ hardcoden.
|
|||||||
|
|
||||||
## 4. Pipeline-Ueberblick
|
## 4. Pipeline-Ueberblick
|
||||||
|
|
||||||
Die CI/CD-Pipeline (`.gitea/workflows/ci.yml`) wird bei jedem Push auf `main`
|
Die CI/CD-Pipeline (`.gitea/workflows/ci.yml`) laeuft bei jedem Push auf die
|
||||||
ausgefuehrt und besteht aus drei aufeinander aufbauenden Jobs:
|
Zweige `main` und `live` sowie bei jedem Tag `v*` (z. B. `v1.0.0`) und besteht
|
||||||
|
aus drei aufeinander aufbauenden Jobs:
|
||||||
|
|
||||||
1. **quality** -- Lint (Biome) und TypeScript Type-Check
|
1. **quality** -- Lint und TypeScript Type-Check (Lint ist derzeit ein Leerlauf,
|
||||||
|
siehe WINDOWS #35; der Type-Check ist echt)
|
||||||
2. **test** -- Vitest Unit- und Integrationstests
|
2. **test** -- Vitest Unit- und Integrationstests
|
||||||
3. **build-deploy** -- Docker Images bauen und Services neu starten
|
3. **publish** -- Docker Images mit Versionsstempel bauen und in die Gitea-Registry
|
||||||
|
veroeffentlichen
|
||||||
|
|
||||||
Ablauf: `quality` -> `test` -> `build-deploy` (jeder Job nur bei Erfolg des
|
Ablauf: `quality` -> `test` -> `publish` (jeder Job nur bei Erfolg des
|
||||||
vorherigen).
|
vorherigen). Der Job `publish` besteht aus drei Schritten: `actions/checkout@v4`
|
||||||
|
mit `fetch-depth: 0` (volle Historie samt Tags, sonst liefert `git describe`
|
||||||
|
nichts), Login in die Registry (siehe Abschnitt 3) und der Aufruf von
|
||||||
|
`.gitea/scripts/publish-images.sh`.
|
||||||
|
|
||||||
### Kein Registry-Push
|
### Zwei Kanaele: Etiketten je Anlass
|
||||||
|
|
||||||
Images werden **lokal auf dem Server gebaut** und nicht in eine Registry
|
Das Skript `.gitea/scripts/publish-images.sh` entscheidet allein anhand
|
||||||
gepusht (D-13). Da der Runner und die Applikation auf demselben Server laufen,
|
`GITHUB_REF`, ob und unter welchen Etiketten veroeffentlicht wird:
|
||||||
baut die Pipeline die Images direkt mit `docker compose build` und startet die
|
|
||||||
Services mit `docker compose up -d` neu.
|
|
||||||
|
|
||||||
Vorteile:
|
| Anlass | Kanal (`APP_CHANNEL`) | Etiketten in der Registry |
|
||||||
- Keine Registry-Infrastruktur noetig
|
|--------|----------------------|---------------------------|
|
||||||
- Schnellerer Deploy (kein Push/Pull ueber Netzwerk)
|
| Push auf `main` | `beta` | `beta` und `latest` (`latest` ist nur ein Alias fuer `beta` und entfaellt spaeter) |
|
||||||
- Einfachere Konfiguration
|
| Tag `vX.Y.Z` | `live` | `live` und `vX.Y.Z` |
|
||||||
|
| Push auf `live` ohne Tag | -- | keine; der Lauf prueft nur (`quality`, `test`), das Skript endet mit "nichts zu tun" |
|
||||||
|
|
||||||
|
Das Kanalmodell fuer den Betrieb (welcher Server welches Etikett zieht, Freigabe,
|
||||||
|
Hotfix) steht in `docs/anleitung-betrieb.md`, Kapitel 9.
|
||||||
|
|
||||||
|
### Versionsstempel
|
||||||
|
|
||||||
|
Das Skript berechnet vier Werte und gibt sie als `--build-arg` an beide
|
||||||
|
Dockerfiles (`apps/web/Dockerfile`, `apps/api/Dockerfile`):
|
||||||
|
|
||||||
|
| Build-Arg | Quelle |
|
||||||
|
|-----------|--------|
|
||||||
|
| `APP_VERSION` | `git describe --tags --always` (ohne Tag: kurzer Commit-SHA) |
|
||||||
|
| `APP_CHANNEL` | `beta` oder `live`, siehe Tabelle oben |
|
||||||
|
| `APP_COMMIT` | `git rev-parse --short HEAD` |
|
||||||
|
| `APP_BUILD_TIME` | `date -u`, ISO-Format |
|
||||||
|
|
||||||
|
Die API liest die Werte zur Laufzeit (`GET /health/version`, Startzeile im Log).
|
||||||
|
Das Web-Image bettet `NEXT_PUBLIC_APP_*` beim Build in das Browser-Bundle ein --
|
||||||
|
deshalb laeuft die `builder`-Stufe des Web-Images jetzt bei jedem Pipeline-Lauf
|
||||||
|
neu, die Laufzeit liegt eher bei 4-6 statt 2 Minuten.
|
||||||
|
|
||||||
|
Lokale Probe ohne Docker-Aufruf:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GITHUB_REF=refs/tags/v1.2.3 sh .gitea/scripts/publish-images.sh --print-plan
|
||||||
|
```
|
||||||
|
|
||||||
|
Hinweis: Die fruehere Entscheidung D-13 (Images lokal bauen, keine Registry) ist
|
||||||
|
ueberholt -- seit der Einfuehrung der Gitea-Registry werden Images gepusht und von
|
||||||
|
den Servern per `docker compose pull` geholt.
|
||||||
|
|
||||||
## 5. Sicherheitshinweise
|
## 5. Sicherheitshinweise
|
||||||
|
|
||||||
@@ -146,6 +194,12 @@ Workflow-Dateien in `.gitea/workflows/` werden direkt aus dem Repository
|
|||||||
geladen. Da nur vertrauenswuerdiger Code gepusht wird (D-11, Claude als
|
geladen. Da nur vertrauenswuerdiger Code gepusht wird (D-11, Claude als
|
||||||
einziger Committer), ist das Risiko einer manipulierten Pipeline minimal.
|
einziger Committer), ist das Risiko einer manipulierten Pipeline minimal.
|
||||||
|
|
||||||
|
Ein Tag-Push `v*` ist der Freigabe-Hebel fuer Live: wer ihn setzen darf, kann
|
||||||
|
das `live`-Etikett neu belegen. Heute hat nur das Konto `schalli` Schreibrecht
|
||||||
|
(0 Kollaborateure, keine Branch-Regeln). Kommen weitere Konten dazu, in Gitea
|
||||||
|
unter **Repository > Settings > Branches / Tags** eine Tag-Schutzregel fuer `v*`
|
||||||
|
und einen Branch-Schutz fuer `live` anlegen (T-KU1-04).
|
||||||
|
|
||||||
## 6. Fehlerbehebung
|
## 6. Fehlerbehebung
|
||||||
|
|
||||||
### Runner registriert sich nicht
|
### Runner registriert sich nicht
|
||||||
@@ -167,3 +221,11 @@ einziger Committer), ist das Risiko einer manipulierten Pipeline minimal.
|
|||||||
erreichbar ist
|
erreichbar ist
|
||||||
2. Docker Daemon Status pruefen: `docker info`
|
2. Docker Daemon Status pruefen: `docker info`
|
||||||
3. Disk Space pruefen: `df -h`
|
3. Disk Space pruefen: `df -h`
|
||||||
|
|
||||||
|
### Stempel zeigt `dev` oder nur eine Kurzkennung statt des Tags
|
||||||
|
|
||||||
|
1. Im Job `publish` pruefen, dass `actions/checkout@v4` mit `fetch-depth: 0`
|
||||||
|
auscheckt -- ohne Tags liefert `git describe --tags --always` nur den SHA
|
||||||
|
2. Pruefen, ob der Tag wirklich gepusht wurde: `git ls-remote --tags origin`
|
||||||
|
3. `dev` bedeutet: das Image wurde ohne Build-Args gebaut (lokal statt ueber
|
||||||
|
das Skript) -- das ist fuer lokale Builds normal
|
||||||
|
|||||||
Reference in New Issue
Block a user