Fehler: claude-haiku-4-5 lehnt output_config.effort mit einem 400 ab, jede Karte lief in einen Lesefehler. effort und der server-seitige Fallback werden jetzt nur an Modelle geschickt, die sie annehmen; ein unbekanntes Modell mit engerem Parametersatz wird einmal ohne Zusatzparameter wiederholt, statt die Karte zu verlieren. Tests decken die Zuordnung ab. Design: Grau kommt als Hierarchiestufe dazu. Drei Ebenen für Fläche (weiß / grau gefüllt / schwarz), Text (schwarz / --ink-2 / --ink-3) und Linie (3px / 1px schwarz / graue Haarlinie). Vorher trug alles dieselbe 3px-Kante und dasselbe Schwarz, dadurch war keine Ordnung erkennbar. Konkret: nicht gewählte Filter treten zurück, Listenzeilen bekommen drei Textstufen und Statuspillen statt einer gleichförmigen Zeile, das Kopfband über Listen ist grau hinterlegt, zweitrangige und zerstörende Aktionen sind leiser als die primäre. iOS: Datumsfelder zentrieren ihren Wert und sacken in der Höhe ein. Die WebKit-Pseudoelemente ziehen sie auf die Form der übrigen Felder. Dazu die Domain in der Dokumentation auf scanner.lucas-orth.de. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6.0 KiB
Deployment-Konventionen
Verbindlich fuer jede App, die auf dem VPS per Tag-Push deployt wird. Abweichungen brauchen einen Grund, der hier nicht steht.
Ablauf
git tag v1.2.3 && git push --tags
-> Gitea Actions (act_runner auf dem VPS)
-> npm ci / build / test <- rot bricht ab, nichts wird deployt
-> .env aus Repo-Secret DOTENV
-> docker compose up -d --build --wait
Kein SCP, kein manuelles Neubauen. Der Code landet nie im Dateisystem des Hosts - der Job-Container klont, baut gegen den Docker-Daemon des Hosts und startet den Container.
Infrastruktur (existiert bereits, nicht neu anlegen)
| Ding | Wert |
|---|---|
| Git | Gitea, https://gitea.lucas-orth.de, Container gitea, intern :3000 |
| Runner | act_runner, Container gitea-runner, Label ubuntu-latest |
| Netz CI | gitea-ci - Gitea + Runner + Job-Container |
| Netz Web | nginx-proxy-manager_default - Proxy + alle App-Container |
| Job-Image | catthehacker/ubuntu:act-latest (node + docker-cli + compose) |
Die beiden Netze sind bewusst getrennt: der Runner spricht mit Gitea, nicht mit dem Proxy. App-Container haengen im Web-Netz, nicht im CI-Netz.
docker-compose.yml
# Fester Projektname. Ohne das leitet Compose ihn aus dem Verzeichnisnamen
# ab - und der ist im CI-Job ein anderer als auf dem Host.
name: business-card-scanner
services:
app:
# Versionstag ermoeglicht Rollback ohne Rebuild.
image: business-card-scanner:${IMAGE_TAG:-latest}
build: .
# Fester Name: das ist die Adresse, auf die der Proxy zeigt.
container_name: business-card-scanner
restart: unless-stopped
# Wird im Workflow aus dem Secret DOTENV erzeugt.
env_file: .env
networks:
- nginx-proxy-manager_default
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8080/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 60s
timeout: 5s
retries: 3
networks:
nginx-proxy-manager_default:
external: true
volumes:
business-card-scanner-data:
# Fester Name, damit das Volume nicht am Projektnamen haengt.
name: business-card-scanner-data
Kein ports:. Der Proxy erreicht den Container ueber das gemeinsame Netz
unter seinem Container-Namen. Ein Port-Mapping wuerde den Dienst zusaetzlich
offen ans Internet haengen.
Healthcheck ist Pflicht. Der Deploy-Schritt nutzt --wait und macht ihn
zum Gate. Ohne Healthcheck wird der Lauf gruen, auch wenn der Container in
einer Crash-Schleife haengt.
Keine relativen Bind-Mounts (./data:/data). Compose laeuft im
Job-Container, der Pfad wuerde auf dem Host ins Leere zeigen. Persistente
Daten gehoeren in ein named volume mit festem name:.
.gitea/workflows/deploy.yml
name: Build & Deploy
on:
push:
tags:
- 'v*'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
# Intern klonen. Die oeffentliche Domain ist aus Containern heraus
# NICHT erreichbar: Gitea laeuft auf demselben Host, das Paket geht
# an die oeffentliche IP und findet nicht zurueck (NAT-Hairpin).
- uses: actions/checkout@v4
with:
github-server-url: http://gitea:3000
# --- Build & Tests, projektspezifisch ---
- name: Tag ermitteln
run: echo "IMAGE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
- name: .env aus Secret erzeugen
env:
DOTENV: ${{ secrets.DOTENV }}
run: |
if [ -z "$DOTENV" ]; then
echo "Secret DOTENV ist leer oder nicht gesetzt."
exit 1
fi
umask 077
echo "$DOTENV" > .env
- name: Deployen
run: docker compose up -d --build --remove-orphans --wait --wait-timeout 180
- name: Alte Layer aufraeumen
run: docker image prune -f
Tests gehoeren vor den Deploy-Schritt. Ein roter Lauf darf den Server nicht anfassen.
Dockerfile
- Multi-Stage: Build-Abhaengigkeiten landen nicht im finalen Image.
USER nodebzw. non-root im finalen Stage.EXPOSE 8080dokumentiert den internen Port (oeffnet nichts).- Ein Health-Endpoint (
/healthz), auf den der Healthcheck zeigt.
Secrets
Ein einziges Repo-Secret DOTENV mit dem kompletten .env-Inhalt,
mehrzeilig. Anlegen unter Repo -> Einstellungen -> Actions -> Secrets.
Gitea speichert Secrets write-only: nach dem Anlegen nicht mehr lesbar, nur ersetzbar. Die kanonische Fassung gehoert deshalb in den Passwortmanager, Gitea haelt nur die Arbeitskopie fuer die Pipeline.
Der Wert wird ueber env: an die Shell gereicht, nie direkt interpoliert -
sonst zerlegt der Shell-Parser Werte mit $, Backticks oder Quotes.
Nginx Proxy Manager
Neuer Proxy Host:
| Feld | Wert |
|---|---|
| Domain Names | scanner.lucas-orth.de |
| Scheme | http |
| Forward Hostname / IP | business-card-scanner (= container_name) |
| Forward Port | 8080 |
Danach Let's-Encrypt-Zertifikat ausstellen und "Force SSL" aktivieren.
Weil der Container-Name die Adresse ist: aendert sich container_name,
zeigt der Proxy-Host ins Leere.
Bestehende App umstellen
Fuer dieses Projekt nicht relevant - es lief nie manuell auf dem Server.
Erstes Deployment einer App
- Repo in Gitea anlegen, pushen.
- Secret
DOTENVsetzen. - Pruefen, ob schon ein Container mit dem Namen laeuft
(
docker ps -a --filter name=business-card-scanner). Falls ja: im alten Verzeichnis einmaldocker compose down, sonst kollidiert der feste Container-Name. - Proxy Host im NPM anlegen.
git tag v1.0.0 && git push origin v1.0.0
Fallstricke
| Symptom | Ursache |
|---|---|
| Job haengt beim Checkout | github-server-url fehlt, klont ueber die oeffentliche Domain |
network not found |
App-Container im CI-Netz statt im Web-Netz (oder umgekehrt) |
| Zweiter Container statt Update | name: im Compose fehlt, Projektname weicht ab |
| Lauf gruen, App tot | --wait fehlt oder kein Healthcheck definiert |
| Volume leer nach Umstellung | Projektname geaendert, altes Volume hiess anders |
| Proxy liefert 502 | container_name geaendert oder Container nicht im Web-Netz |