Stop pretending Vercel is an option
It was never used. The repository lives on a self-hosted Gitea, which Vercel's git integration cannot connect to at all — so the documented route amounted to "mirror to GitHub first", and nobody did. vercel.json is gone, and with it the branch in next.config.ts that switched off `output: "standalone"` when the VERCEL variable was present. That branch was the only functional trace; everything else was documentation and comments describing a second deployment path that did not exist. DEPLOYMENT.md loses its "two supported ways" framing and the whole Vercel section — about fifty lines. Several statements next to it were stale for a different reason and are corrected in the same pass: the outbound-firewall table still listed Supabase's pooler (the database is a container now, nothing leaves the server), the prerequisites still demanded an existing Supabase project, and the .env table still asked for a pooler connection string instead of the two new passwords. The nightly job is described as what it is — a container in docker-compose.yml — rather than as a replacement for Vercel Cron. Migrations keep their references: two comments from July mention Vercel Cron, and they describe what was true when they were written. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
104
DEPLOYMENT.md
104
DEPLOYMENT.md
@@ -1,71 +1,14 @@
|
||||
# Deployment
|
||||
|
||||
Zwei Wege, beide unterstützt. Das Abbild ist umgebungsneutral — es gibt keine
|
||||
Werte mehr, die beim Bauen eingebacken werden —, ein Wechsel ist also
|
||||
jederzeit möglich.
|
||||
Die Anwendung läuft als Docker-Container auf einem eigenen Linux-Server,
|
||||
zusammen mit ihrer Datenbank. Das Abbild ist umgebungsneutral — es gibt
|
||||
keine Werte, die beim Bauen eingebacken werden; alles kommt zur Laufzeit
|
||||
aus `.env`.
|
||||
|
||||
| | passt, wenn |
|
||||
|---|---|
|
||||
| [Vercel](#vercel) | ihr nichts betreiben wollt; schnellster Weg |
|
||||
| [Docker](#deployment-mit-docker) | es in eure eigene Infrastruktur soll |
|
||||
|
||||
**In beiden Fällen gleich:** die Umgebungsvariablen aus [Abschnitt 1](#1-env-anlegen),
|
||||
die Umleitungs-URI in der Entra-Registrierung, und dass eine neue Person nach
|
||||
ihrer ersten Anmeldung eine `profiles`-Zeile braucht (siehe
|
||||
[docs/entra-sso.md](docs/entra-sso.md)).
|
||||
|
||||
## Vercel
|
||||
|
||||
Das Repository liegt auf `git.elycon.solutions` — einem selbst betriebenen
|
||||
Git. **Vercels Git-Anbindung kann nur GitHub, GitLab und Bitbucket**, dieses
|
||||
Repository lässt sich dort also nicht verknüpfen. Zwei Möglichkeiten:
|
||||
|
||||
### a) Von der Arbeitsstation ausrollen (ohne GitHub)
|
||||
|
||||
```bash
|
||||
npx vercel login
|
||||
npx vercel link
|
||||
npx vercel --prod
|
||||
```
|
||||
|
||||
Funktioniert mit jedem Repository. Der Preis: kein automatisches Ausrollen
|
||||
bei einem Push — jede Veröffentlichung ist ein bewusster Befehl. Für zwei
|
||||
Personen ist das eher Vorteil als Nachteil.
|
||||
|
||||
### b) Zusätzlich nach GitHub spiegeln
|
||||
|
||||
```bash
|
||||
git remote add github git@github.com:<konto>/alpenwerk-hr.git
|
||||
git push github feat/sap-om-org-model
|
||||
```
|
||||
|
||||
Danach das GitHub-Repository in Vercel verbinden. Ab dann rollt jeder Push
|
||||
aus. Zwei Fernziele bedeuten aber auch: beide müssen gepflegt werden.
|
||||
|
||||
### Danach
|
||||
|
||||
1. **Umgebungsvariablen** im Vercel-Projekt setzen (Settings → Environment
|
||||
Variables), dieselben wie in [Abschnitt 1](#1-env-anlegen). `AUTH_URL` ist
|
||||
nicht nötig, Vercel setzt den Host selbst.
|
||||
2. **Umleitungs-URI** in der Entra-Registrierung ergänzen:
|
||||
`https://<projekt>.vercel.app/api/auth/callback/microsoft-entra-id`
|
||||
3. Der nächtliche Lauf ist über `vercel.json` bereits eingerichtet.
|
||||
|
||||
Zwei Eigenheiten der Plattform, die im Code berücksichtigt sind:
|
||||
`output: "standalone"` entfällt dort automatisch (Vercel baut selbst), und
|
||||
`/api/import` ist auf 60 Sekunden begrenzt — die Obergrenze des kostenlosen
|
||||
Tarifs. Im Pro-Tarif liessen sich 300 setzen, falls eine Importdatei mit
|
||||
vielen tausend Zeilen ansteht.
|
||||
|
||||
Der Verbindungspool passt zu serverlosen Aufrufen, **weil** `DATABASE_URL`
|
||||
auf den Transaktions-Modus zeigt (Port 6543). Mit dem Sitzungs-Modus wären
|
||||
die 15 Verbindungen des Tarifs nach wenigen gleichzeitigen Aufrufen
|
||||
verbraucht.
|
||||
|
||||
## Deployment mit Docker
|
||||
|
||||
Dieser Guide beschreibt, wie die App stattdessen als Docker-Container auf
|
||||
einem eigenen Linux-Server läuft.
|
||||
Zu klären, bevor es losgeht: die Umgebungsvariablen aus
|
||||
[Abschnitt 1](#1-env-anlegen), die Umleitungs-URI in der
|
||||
Entra-Registrierung, und dass eine neue Person nach ihrer ersten Anmeldung
|
||||
eine `profiles`-Zeile braucht (siehe [docs/entra-sso.md](docs/entra-sso.md)).
|
||||
|
||||
## Was wird containerisiert – und was nicht
|
||||
|
||||
@@ -84,12 +27,10 @@ einem eigenen Linux-Server läuft.
|
||||
- **Ebenfalls nicht containerisiert:** die Anmeldung. Sie läuft über
|
||||
Microsoft Entra ID; die App hält nur das Sitzungscookie (Auth.js). Es gibt
|
||||
keinen Anmeldedienst, der mit ausgerollt werden müsste.
|
||||
- **Ersetzt:** der Vercel-Cron-Job aus `vercel.json` (täglich 03:00 Uhr,
|
||||
ruft `/api/cron/apply-pending-changes` auf, um fällige Versetzungen/
|
||||
Beförderungen/Karenz/Reorg-Änderungen zu übernehmen). Da es außerhalb von
|
||||
Vercel kein Vercel-Cron gibt, übernimmt das im `docker-compose.yml`
|
||||
enthaltene `cron`-Sidecar-Container diese Aufgabe mit demselben Schema
|
||||
und demselben Bearer-Secret, das die Route bereits erwartet.
|
||||
- **Der nächtliche Lauf** ist ein eigener Container (`cron`): täglich 03:00
|
||||
Uhr ruft er `/api/cron/apply-pending-changes` auf, um fällige Versetzungen,
|
||||
Beförderungen, Karenz- und Reorg-Änderungen zu übernehmen. Ausgewiesen wird
|
||||
der Aufruf über `CRON_SECRET` — es gibt dabei keine angemeldete Person.
|
||||
|
||||
## Betrieb im Firmennetz — zwei Dinge vorab
|
||||
|
||||
@@ -103,12 +44,13 @@ nichts, ausgehend zwingend:
|
||||
| Ziel | Wofür | Ohne das |
|
||||
|---|---|---|
|
||||
| `login.microsoftonline.com` (443) | Auth.js tauscht den Anmeldecode **serverseitig** gegen ein Token und lädt die Konfiguration des Ausstellers | keine Anmeldung möglich |
|
||||
| Die Datenbank (Supabase: `*.pooler.supabase.com`, 6543) | jede Abfrage | die App startet, zeigt aber nichts |
|
||||
|
||||
Dass die Anmeldung im Browser der Person stattfindet, genügt **nicht** — der
|
||||
Tausch von Code gegen Token läuft vom Server aus. Liegt die VM in einem
|
||||
abgeschotteten Netz, ist entweder ein Proxy nötig oder eine PostgreSQL-
|
||||
Instanz im selben Netz statt Supabase.
|
||||
abgeschotteten Netz, braucht es dafür einen Proxy.
|
||||
|
||||
Die Datenbank taucht hier nicht mehr auf: sie läuft als Container daneben, im
|
||||
selben Compose-Netz. Nach aussen geht dafür nichts.
|
||||
|
||||
**2. HTTPS ist Pflicht, auch intern.** Entra ID akzeptiert `http` nur für
|
||||
`localhost`. Der praktikable Weg ohne öffentliche Erreichbarkeit: ein
|
||||
@@ -126,11 +68,9 @@ bei zwanzig nicht.
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- Docker + Docker Compose (v2, das im Docker Desktop/Docker Engine
|
||||
enthaltene `docker compose`) auf dem Zielserver.
|
||||
- Ein bestehendes Supabase-Projekt mit den Migrationen aus
|
||||
`supabase/migrations/` bereits eingespielt (`supabase db push` bzw. wie
|
||||
bisher).
|
||||
- Docker + Docker Compose (v2, das in Docker Engine enthaltene
|
||||
`docker compose`) auf dem Zielserver. Sonst nichts — weder Node noch psql;
|
||||
was gebraucht wird, kommt aus Containern.
|
||||
|
||||
## 1. `.env` anlegen
|
||||
|
||||
@@ -142,8 +82,10 @@ Werte eintragen:
|
||||
|
||||
| Variable | Woher |
|
||||
|---|---|
|
||||
| `DATABASE_URL` | Verbindungsstring der PostgreSQL-Instanz. Die Rolle darf **kein** `BYPASSRLS` haben; hinter einem Pooler den **Transaktions-Modus** (bei Supabase Port 6543) |
|
||||
| `DATABASE_SSL` | nur setzen (`false`), wenn die Datenbank ohne TLS läuft |
|
||||
| `POSTGRES_PASSWORD` | selbst erzeugen: `openssl rand -base64 24` — das des Verwalters |
|
||||
| `APP_DB_PASSWORD` | selbst erzeugen — das der Anwendungsrolle `alpenwerk_app` |
|
||||
| `DATABASE_URL` | `postgresql://alpenwerk_app:<APP_DB_PASSWORD>@db:5432/alpenwerk`. Die Rolle darf **kein** `BYPASSRLS` haben — `deploy/db-init` legt sie genau so an |
|
||||
| `DATABASE_SSL` | `false` im Compose-Netz; die Verbindung verlässt den Server nicht |
|
||||
| `AUTH_SECRET` | selbst generieren: `openssl rand -base64 32` |
|
||||
| `AUTH_MICROSOFT_ENTRA_ID_ID` | Entra-Portal → App-Registrierung → Übersicht |
|
||||
| `AUTH_MICROSOFT_ENTRA_ID_SECRET` | Entra-Portal → Zertifikate & Geheimnisse (nur einmal sichtbar!) |
|
||||
|
||||
Reference in New Issue
Block a user