# Deployment mit Docker Dieser Guide beschreibt, wie die App (bisher auf Vercel deployed, siehe `vercel.json`) 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 ```bash cp .env.example .env ``` Werte eintragen: | Variable | Woher | |---|---| | `NEXT_PUBLIC_SUPABASE_URL` | Supabase-Projekt → Settings → API | | `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Supabase-Projekt → Settings → API | | `SUPABASE_SERVICE_ROLE_KEY` | Supabase-Projekt → Settings → API (geheim!) | | `CRON_SECRET` | selbst generieren: `openssl rand -hex 32` | Wichtig zum Verständnis: - `NEXT_PUBLIC_*`-Variablen werden **beim Build** in das Browser-Bundle eingebacken (Next.js-Verhalten, nicht Docker-spezifisch). Ändern sich diese Werte, muss das Image **neu gebaut** werden – ein reiner Container- Neustart reicht nicht. - `SUPABASE_SERVICE_ROLE_KEY` und `CRON_SECRET` sind Server-only-Secrets. Sie werden bewusst **nicht** als Build-Arg übergeben (das würde sie im Image-Layer-History sichtbar machen), sondern erst zur Laufzeit über `env_file` injiziert. - `.env` steht schon in `.gitignore` – nicht committen. ## 2. Bauen und lokal testen ```bash 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: ```bash docker compose logs -f cron ``` ## 3. Auf einem Server deployen Einfachste Variante – Repo direkt auf dem Server bauen: ```bash git clone && 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: /:` 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](https://caddyserver.com/) (automatisches HTTPS via Let's Encrypt): ```caddyfile # /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 ```bash 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: ```bash 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: ```bash 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 - **Login-Redirect-Loop / `proxy.ts` verhält sich falsch:** meist falsche `NEXT_PUBLIC_SUPABASE_URL`/`ANON_KEY` – Image neu bauen (siehe oben, diese Werte sind eingebacken). - **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`). - **Healthcheck rot:** `docker compose logs app` – meist fehlende/falsche Supabase-Env-Variablen zur Laufzeit (`SUPABASE_SERVICE_ROLE_KEY`, Server-Komponenten).