deploy: Traefik-Setup an feuerwehr_dashboard angleichen
Abgeglichen mit ~/work/feuerwehr_dashboard/docker-compose.yml: - externes Traefik-Netz heißt 'frontend' (external: true), nicht 'traefik' - explizite Router->Service-Bindung (routers.floriannetz.service=floriannetz) - entrypoints=websecure, tls + certresolver=letsencrypt, port 3000 - traefik.docker.network -> frontend; AUTHENTIK_ADMIN_GROUP an App durchgereicht - internes Netz als Bridge (statt internal:true): Postgres/Geo ohne Host-Ports, aber App hat Egress für Authentik-OIDC - APP_HOST-Default florian.feuerwehr-rems.at; TRAEFIK_NETWORK-Default frontend - Doku (deployment-traefik.md) + Makefile-Kommentare angepasst Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -31,11 +31,13 @@ HAVERSINE_KMH=50
|
||||
# Deployment / externes Traefik
|
||||
# APP_HOST ist der öffentliche Hostname (Traefik-Routing + AUTH_URL-Basis).
|
||||
# In Produktion: AUTH_URL=https://${APP_HOST} und AUTH_TRUST_HOST=true setzen.
|
||||
APP_HOST=floriannetz.example.at
|
||||
APP_HOST=florian.feuerwehr-rems.at
|
||||
# Traefik-Zertifikatsauflöser (muss in der externen Traefik-Instanz definiert sein).
|
||||
TRAEFIK_CERTRESOLVER=letsencrypt
|
||||
# Name des externen, von Traefik verwalteten Docker-Netzes.
|
||||
TRAEFIK_NETWORK=traefik
|
||||
# Name des externen, von Traefik verwalteten Docker-Netzes
|
||||
# (im feuerwehr_dashboard heißt es "frontend"). Muss existieren:
|
||||
# docker network create frontend
|
||||
TRAEFIK_NETWORK=frontend
|
||||
# Optionaler Katalog-Seed beim Container-Start (idempotent).
|
||||
RUN_SEED=false
|
||||
# Postgres-Zugangsdaten für den Compose-Postgres-Service.
|
||||
|
||||
4
Makefile
4
Makefile
@@ -9,7 +9,7 @@
|
||||
# make build-app migrate
|
||||
#
|
||||
# Voll-Deploy hinter externem Traefik (Docker):
|
||||
# docker network create traefik # einmalig
|
||||
# docker network create frontend # einmalig (externes Traefik-Netz)
|
||||
# make deploy
|
||||
#
|
||||
# `make help` listet alle Ziele.
|
||||
@@ -116,7 +116,7 @@ setup: install env db-up db-wait migrate seed-all ## Komplettes lokales Setup vo
|
||||
@echo "✓ Setup fertig. Login-Admin via 'make seed-auth' angelegt. Weiter mit: make dev"
|
||||
|
||||
# --- Deployment (externes Traefik; braucht Docker) -----------------------
|
||||
# Externes Netz muss existieren: docker network create traefik
|
||||
# Externes Netz muss existieren: docker network create frontend
|
||||
.PHONY: build up down logs ps deploy migrate-stack data config
|
||||
build: ## App-Image bauen (Next.js standalone, non-root)
|
||||
$(COMPOSE) build app
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
# FlorianNetz — Basis-Compose hinter EXTERNEM Traefik.
|
||||
#
|
||||
# Ausgerichtet auf das bestehende Setup von feuerwehr_dashboard:
|
||||
# - externes, von Traefik verwaltetes Netz heißt "frontend" (external: true)
|
||||
# - Router: entrypoints=websecure, tls + certresolver=letsencrypt
|
||||
# - explizite Router->Service-Bindung, loadbalancer.server.port=3000
|
||||
# - traefik.docker.network = das externe "frontend"-Netz
|
||||
#
|
||||
# Es gibt bewusst KEINEN eigenen Proxy-/Traefik-Service: Routing/TLS übernimmt
|
||||
# eine separat betriebene Traefik-Instanz, die am externen Netz "${TRAEFIK_NETWORK}"
|
||||
# (Default: traefik) lauscht. Dieses Netz muss bereits existieren:
|
||||
# docker network create traefik
|
||||
# die separat betriebene Traefik-Instanz am Netz "${TRAEFIK_NETWORK}" (Default:
|
||||
# frontend). Dieses Netz muss bereits existieren:
|
||||
# docker network create frontend
|
||||
#
|
||||
# Geo-Dienste (osrm, nominatim) sind hier mit ihren Laufzeit-Verträgen definiert;
|
||||
# das schwergewichtige Daten-Preprocessing/Volume kommt aus docker-compose.geo.yml
|
||||
# (siehe scripts/prepare-osm-data.sh / infra/geo).
|
||||
# Postgres/Geo liegen am internen Bridge-Netz (keine veröffentlichten Ports,
|
||||
# also nicht öffentlich erreichbar) — der App-Container hat über dieses Netz
|
||||
# zugleich Egress (z. B. für den Authentik-OIDC-Token-Austausch).
|
||||
#
|
||||
# Start:
|
||||
# docker compose --env-file .env up -d
|
||||
# Lokal ohne Traefik/TLS:
|
||||
# docker compose -f docker-compose.yml -f docker-compose.override.yml up -d
|
||||
# Start: docker compose --env-file .env up -d
|
||||
# Lokal: docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
|
||||
|
||||
services:
|
||||
app:
|
||||
@@ -32,13 +36,14 @@ services:
|
||||
AUTHENTIK_ISSUER: ${AUTHENTIK_ISSUER}
|
||||
AUTHENTIK_CLIENT_ID: ${AUTHENTIK_CLIENT_ID}
|
||||
AUTHENTIK_CLIENT_SECRET: ${AUTHENTIK_CLIENT_SECRET}
|
||||
AUTHENTIK_ADMIN_GROUP: ${AUTHENTIK_ADMIN_GROUP:-floriannetz-admins}
|
||||
OSRM_URL: http://osrm:5000
|
||||
NOMINATIM_URL: http://nominatim:8080
|
||||
GEO_HTTP_TIMEOUT_MS: ${GEO_HTTP_TIMEOUT_MS:-4000}
|
||||
HAVERSINE_KMH: ${HAVERSINE_KMH:-50}
|
||||
RUN_SEED: ${RUN_SEED:-false}
|
||||
networks:
|
||||
- traefik
|
||||
- frontend
|
||||
- internal
|
||||
healthcheck:
|
||||
test:
|
||||
@@ -51,11 +56,12 @@ services:
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=${TRAEFIK_NETWORK:-traefik}"
|
||||
- "traefik.http.routers.floriannetz.rule=Host(`${APP_HOST}`)"
|
||||
- "traefik.docker.network=${TRAEFIK_NETWORK:-frontend}"
|
||||
- "traefik.http.routers.floriannetz.entrypoints=websecure"
|
||||
- "traefik.http.routers.floriannetz.rule=Host(`${APP_HOST}`)"
|
||||
- "traefik.http.routers.floriannetz.tls=true"
|
||||
- "traefik.http.routers.floriannetz.tls.certresolver=${TRAEFIK_CERTRESOLVER:-letsencrypt}"
|
||||
- "traefik.http.routers.floriannetz.service=floriannetz"
|
||||
- "traefik.http.services.floriannetz.loadbalancer.server.port=3000"
|
||||
# Security-Header-Middleware (zusätzlich zu next.config.ts; defense-in-depth).
|
||||
- "traefik.http.routers.floriannetz.middlewares=floriannetz-sechdrs"
|
||||
@@ -137,10 +143,12 @@ volumes:
|
||||
nominatim-data:
|
||||
|
||||
networks:
|
||||
# Externes, von der separaten Traefik-Instanz verwaltetes Netz.
|
||||
traefik:
|
||||
# Externes, von der separaten Traefik-Instanz verwaltetes Netz (wie im
|
||||
# feuerwehr_dashboard "frontend"). Muss existieren: docker network create frontend
|
||||
frontend:
|
||||
external: true
|
||||
name: ${TRAEFIK_NETWORK:-traefik}
|
||||
# Internes Netz: Postgres/Geo sind nur app-intern erreichbar, nicht öffentlich.
|
||||
name: ${TRAEFIK_NETWORK:-frontend}
|
||||
# Internes Bridge-Netz: Postgres/Geo ohne veröffentlichte Ports (nicht
|
||||
# öffentlich), zugleich Egress für den App-Container (Authentik-OIDC).
|
||||
internal:
|
||||
internal: true
|
||||
driver: bridge
|
||||
|
||||
@@ -18,17 +18,20 @@ Der Stack besteht aus genau vier Services (kein Proxy):
|
||||
|
||||
Netze:
|
||||
|
||||
- **`traefik`** — externes, von Traefik verwaltetes Netz (`external: true`,
|
||||
Name aus `TRAEFIK_NETWORK`, Default `traefik`). Nur `app` hängt daran.
|
||||
- **`internal`** — internes Netz (`internal: true`); Postgres und die Geo-Dienste
|
||||
sind ausschließlich für die App erreichbar, nie öffentlich.
|
||||
- **`frontend`** — externes, von Traefik verwaltetes Netz (`external: true`,
|
||||
Name aus `TRAEFIK_NETWORK`, Default `frontend` — wie im feuerwehr_dashboard).
|
||||
Nur `app` hängt daran (Proxy↔App).
|
||||
- **`internal`** — internes Bridge-Netz; Postgres und die Geo-Dienste haben
|
||||
**keine veröffentlichten Ports** (nicht öffentlich erreichbar). Über dieses
|
||||
Netz hat der App-Container zugleich **Egress** (z. B. für den
|
||||
Authentik-OIDC-Token-Austausch).
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
Das externe Traefik-Netz muss existieren, bevor der Stack startet:
|
||||
|
||||
```bash
|
||||
docker network create traefik
|
||||
docker network create frontend
|
||||
```
|
||||
|
||||
Die externe Traefik-Instanz muss:
|
||||
@@ -36,7 +39,7 @@ Die externe Traefik-Instanz muss:
|
||||
- einen Entrypoint `websecure` (Port 443) bereitstellen,
|
||||
- einen Zertifikatsauflöser anbieten, dessen Name `TRAEFIK_CERTRESOLVER`
|
||||
entspricht (Default `letsencrypt`),
|
||||
- am Netz `traefik` lauschen (`providers.docker` mit `exposedByDefault=false`).
|
||||
- am Netz `frontend` lauschen (`providers.docker` mit `exposedByDefault=false`).
|
||||
|
||||
Die App-Labels in `docker-compose.yml` setzen Router (`Host(\`${APP_HOST}\`)`,
|
||||
`entrypoints=websecure`, `tls.certresolver`), Service-Port `3000` und eine
|
||||
@@ -48,16 +51,17 @@ Vollständiger Vertrag in `.env.example`. Für den Betrieb hinter Traefik zwinge
|
||||
|
||||
| Variable | Beispiel / Hinweis |
|
||||
| ------------------------- | ---------------------------------------------------- |
|
||||
| `APP_HOST` | öffentlicher Hostname, z. B. `floriannetz.example.at` |
|
||||
| `APP_HOST` | öffentlicher Hostname, z. B. `florian.feuerwehr-rems.at` |
|
||||
| `AUTH_URL` | `https://${APP_HOST}` — Basis für Callback + Cookies |
|
||||
| `AUTH_TRUST_HOST` | `true` — Auth.js vertraut den Forwarded-Headern |
|
||||
| `AUTH_SECRET` | >= 32 Zeichen (`openssl rand -base64 32`) |
|
||||
| `AUTHENTIK_ISSUER` | OIDC-Issuer-URL der Authentik-Anwendung |
|
||||
| `AUTHENTIK_CLIENT_ID` | Client-ID der Authentik-Anwendung |
|
||||
| `AUTHENTIK_CLIENT_SECRET` | Client-Secret der Authentik-Anwendung |
|
||||
| `AUTHENTIK_ADMIN_GROUP` | Authentik-Gruppe → platform_admin (Default `floriannetz-admins`; s. authentik-setup.md) |
|
||||
| `DATABASE_URL` | wird in Compose aus `POSTGRES_*` zusammengesetzt |
|
||||
| `TRAEFIK_CERTRESOLVER` | Name des Traefik-Zertifikatsauflösers |
|
||||
| `TRAEFIK_NETWORK` | Name des externen Traefik-Netzes (Default `traefik`) |
|
||||
| `TRAEFIK_NETWORK` | Name des externen Traefik-Netzes (Default `frontend`) |
|
||||
|
||||
## Forwarded-Header & sichere Cookies
|
||||
|
||||
@@ -86,6 +90,10 @@ https://${APP_HOST}/api/auth/callback/authentik
|
||||
Der Pfad `callback/authentik` entspricht dem NextAuth-Provider-Namen. Bei lokaler
|
||||
Entwicklung zusätzlich `http://localhost:3000/api/auth/callback/authentik`.
|
||||
|
||||
**Admin-Zugang über Gruppe:** Dem Provider muss das `groups`-Scope-Mapping
|
||||
zugewiesen sein, und es muss die Gruppe aus `AUTHENTIK_ADMIN_GROUP` existieren —
|
||||
nur deren Mitglieder werden `platform_admin`. Details: `authentik-setup.md`.
|
||||
|
||||
## Health-Check & Middleware-Allowlist
|
||||
|
||||
`GET /api/health` ist **öffentlich** (anonym `200`, nur Liveness, keine
|
||||
@@ -107,7 +115,7 @@ App-Healthcheck pingen `http://127.0.0.1:3000/api/health`.
|
||||
|
||||
```bash
|
||||
cp .env.example .env # Werte setzen (APP_HOST, AUTH_*, AUTHENTIK_*, POSTGRES_*)
|
||||
docker network create traefik # einmalig, falls nicht vorhanden
|
||||
docker network create frontend # einmalig, falls nicht vorhanden
|
||||
make data # einmalig: OSRM-Geodaten vorbereiten (groß, dauert)
|
||||
make deploy # build + up
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user