Files
alpenwerk-hr/DEPLOYMENT.md
Maximilian Stubhan ecbda3f3a5 Make the build fit Vercel without breaking the container
Two settings were wrong for a platform build.

output: "standalone" tells Next.js to emit a self-contained server, which is
what the Dockerfile copies in — and what Vercel neither needs nor expects,
since it builds and packages the app itself. It is now conditional on the
VERCEL variable, which every build there sets, so each path gets what it
wants. Verified both ways: with VERCEL=1 no standalone directory appears,
without it one does.

/api/import declared maxDuration = 120. The free tier caps at 60 and refuses
anything higher, so the deployment would have failed on a value chosen for a
self-hosted server. Lowered, with the reason and the Pro ceiling written
next to it.

DEPLOYMENT.md now covers both paths, and says plainly that the repository
cannot be connected: git.elycon.solutions is self-hosted, and Vercel's git
integration only speaks GitHub, GitLab and Bitbucket. Deploying from the
workstation with the CLI works with any repository and is the shorter road;
mirroring to GitHub is written down as the alternative, with its cost — two
remotes to keep in step.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 15:17:24 +02:00

8.6 KiB
Raw Blame History

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.

passt, wenn
Vercel ihr nichts betreiben wollt; schnellster Weg
Docker es in eure eigene Infrastruktur soll

In beiden Fällen gleich: die Umgebungsvariablen aus Abschnitt 1, 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).

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)

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

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. 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 beliebigen Server läuft.

Was wird containerisiert – und was nicht

  • Containerisiert: nur die Next.js-App selbst (Dockerfile).
  • Nicht containerisiert: Supabase (Datenbank + Auth). Die App verbindet sich per URL/Key zu einem bestehenden Supabase-Projekt (Cloud oder selbst gehostet) – das bleibt unverändert. supabase/ in diesem Repo ist nur die lokale Dev-/Migrations-Umgebung (supabase start), kein Teil des Deployments.
  • 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.

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).

1. .env anlegen

cp .env.example .env

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
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!)
AUTH_MICROSOFT_ENTRA_ID_ISSUER https://login.microsoftonline.com/<verzeichnis-id>/v2.0
CRON_SECRET selbst generieren: openssl rand -hex 32

Details zur Entra-Registrierung: docs/entra-sso.md.

Wichtig zum Verständnis:

  • Nichts davon wird in das Image eingebacken. Es gibt keine NEXT_PUBLIC_*-Variablen mehr; alle Werte liest die Anwendung zur Laufzeit über env_file. Eine Änderung braucht deshalb nur einen Neustart, keinen neuen Build — und dasselbe Image läuft in Test und Produktion.
  • .env steht schon in .gitignore – nicht committen.

2. Bauen und lokal testen

docker compose build
docker compose up -d
docker compose logs -f app

App ist danach unter http://localhost:3000 erreichbar. Healthcheck prüft GET /login; Status siehe docker compose ps.

Cron-Sidecar prüfen:

docker compose logs -f cron

3. Auf einem Server deployen

Einfachste Variante – Repo direkt auf dem Server bauen:

git clone <repo-url> && cd manner-app
cp .env.example .env   # Werte eintragen
docker compose up -d --build

Alternative für CI/CD (Image einmal bauen, überall pullen): Image in einer Registry (GHCR, Docker Hub, …) bauen und pushen, auf dem Server nur docker compose pull && docker compose up -d ausführen. Dafür in docker-compose.yml zusätzlich image: <registry>/<name>:<tag> setzen und den Build in der CI-Pipeline mit den --build-arg-Werten für NEXT_PUBLIC_* laufen lassen.

4. Reverse Proxy + HTTPS

Next.js selbst sollte laut den offiziellen Docs nicht direkt exponiert werden – ein Reverse Proxy übernimmt TLS, Rate-Limiting und Request- Validierung. Beispiel mit Caddy (automatisches HTTPS via Let's Encrypt):

# /etc/caddy/Caddyfile
hr.example.com {
    reverse_proxy localhost:3000
}

docker-compose.yml published Port 3000 aktuell auf den Host – bei Verwendung eines Reverse Proxys auf demselben Host kann das Publishing auf 127.0.0.1:3000:3000 eingeschränkt werden, damit der Container-Port nicht direkt von außen erreichbar ist.

5. Updates ausrollen

git pull
docker compose build
docker compose up -d

Kurzer Downtime-Moment beim Neustart des app-Containers ist bei dieser Single-Instance-Compose-Konfiguration normal. Für Zero-Downtime-Deployments wäre eine zweite Instanz + Load Balancer nötig (siehe Abschnitt 6).

Datenbank-Migrationen (supabase/migrations/*.sql) werden weiterhin über die Supabase CLI gegen das Supabase-Projekt gefahren, unabhängig vom App-Deployment:

supabase db push

6. Hinweis bei mehreren Replicas

Läuft die App skaliert (mehrere app-Container hinter einem Load Balancer), muss NEXT_SERVER_ACTIONS_ENCRYPTION_KEY explizit gesetzt und auf allen Instanzen identisch sein – sonst schlagen Server Actions (actions/*.ts, z. B. Mitarbeiter- und Positions-Mutationen) mit "Failed to find Server Action" fehl, wenn eine Anfrage auf einer anderen Instanz landet als der, die das Formular gerendert hat. Erzeugen mit:

openssl rand -base64 32

Als zusätzliche Env-Variable in .env eintragen. Bei der aktuellen Single-Instance-Compose-Konfiguration ist das nicht nötig.

Troubleshooting

  • Anmeldung endet auf /login?error=…: die Umleitungs-URI in der Entra-Registrierung muss exakt https://<host>/api/auth/callback/microsoft-entra-id lauten. Steht die Anwendung hinter einem Reverse Proxy unter einer anderen Adresse, als sie selbst sieht, zusätzlich AUTH_URL setzen.
  • Angemeldet, aber sofort zurück auf /login?error=no_hr_access: die Anmeldung hat funktioniert, es fehlt die Freischaltung. Es braucht eine profiles-Zeile mit role = 'hr' und is_active = true auf derselben Kennung, die in app_users steht.
  • Cron läuft nicht: docker compose logs cron – prüft, ob /etc/crontabs/root korrekt geschrieben wurde und ob CRON_SECRET in .env gesetzt ist (leer/fehlend führt serverseitig zu 401).
  • max clients reached in session mode: der Verbindungsstring zeigt auf den Sitzungs-Modus des Poolers. Auf den Transaktions-Modus wechseln (bei Supabase Port 6543).
  • Healthcheck rot: docker compose logs app – meist DATABASE_URL fehlend oder nicht erreichbar. Der Pool baut die Verbindung erst beim ersten Zugriff auf, der Fehler steht deshalb im Log der Anfrage, nicht im Start-Log.