Files
Florian-netz/docker-compose.yml
Claude f71cf51eb4 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>
2026-06-10 12:39:42 +02:00

155 lines
5.3 KiB
YAML

# 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
# die separat betriebene Traefik-Instanz am Netz "${TRAEFIK_NETWORK}" (Default:
# frontend). Dieses Netz muss bereits existieren:
# docker network create frontend
#
# 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: docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
services:
app:
build:
context: .
dockerfile: Dockerfile
depends_on:
postgres:
condition: service_healthy
environment:
NODE_ENV: production
DATABASE_URL: postgres://${POSTGRES_USER:-floriannetz}:${POSTGRES_PASSWORD:-floriannetz}@postgres:5432/${POSTGRES_DB:-floriannetz}
# Forwarded-Header + sichere Cookies hinter Traefik.
AUTH_TRUST_HOST: "true"
AUTH_URL: https://${APP_HOST}
AUTH_SECRET: ${AUTH_SECRET}
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:
- frontend
- internal
healthcheck:
test:
- CMD-SHELL
- "wget -q -O - http://127.0.0.1:3000/api/health | grep -q ok"
interval: 30s
timeout: 5s
retries: 5
start_period: 40s
restart: unless-stopped
labels:
- "traefik.enable=true"
- "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"
- "traefik.http.middlewares.floriannetz-sechdrs.headers.stsSeconds=63072000"
- "traefik.http.middlewares.floriannetz-sechdrs.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.floriannetz-sechdrs.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.floriannetz-sechdrs.headers.frameDeny=true"
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${POSTGRES_USER:-floriannetz}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-floriannetz}
POSTGRES_DB: ${POSTGRES_DB:-floriannetz}
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- internal
healthcheck:
test:
- CMD-SHELL
- "pg_isready -U ${POSTGRES_USER:-floriannetz} -d ${POSTGRES_DB:-floriannetz}"
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
osrm:
build:
context: .
dockerfile: docker/osrm/Dockerfile
command: osrm-routed --algorithm mld /data/austria-latest.osrm
volumes:
- osrm-data:/data
networks:
- internal
expose:
- "5000"
healthcheck:
test:
- CMD-SHELL
- >-
wget -q -O - 'http://localhost:5000/table/v1/driving/15.6229,48.2079;16.3738,48.2082?sources=0'
| grep -q '"code":"Ok"'
interval: 30s
timeout: 5s
retries: 5
start_period: 60s
restart: unless-stopped
nominatim:
image: mediagis/nominatim:4.4
environment:
PBF_URL: https://download.geofabrik.de/europe/austria-latest.osm.pbf
REPLICATION_URL: https://download.geofabrik.de/europe/austria-updates/
IMPORT_STYLE: address
NOMINATIM_PASSWORD: ${NOMINATIM_PASSWORD:-nominatim}
volumes:
- nominatim-data:/var/lib/postgresql/14/main
shm_size: 1g
networks:
- internal
expose:
- "8080"
healthcheck:
test:
- CMD-SHELL
- "wget -q -O - 'http://localhost:8080/status' | grep -q OK"
interval: 30s
timeout: 5s
retries: 5
start_period: 120s
restart: unless-stopped
volumes:
postgres-data:
osrm-data:
nominatim-data:
networks:
# 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:-frontend}
# Internes Bridge-Netz: Postgres/Geo ohne veröffentlichte Ports (nicht
# öffentlich), zugleich Egress für den App-Container (Authentik-OIDC).
internal:
driver: bridge