- db-Volume auf /var/lib/postgresql (v18 legt PGDATA in .../18/docker; Mount auf .../data laesst v18 nicht starten -> db unhealthy -> App-Rollback) - Backup-Sidecar: Shell-Scheduler statt Alpine-crond (/etc/crontabs fehlt im Debian-postgres:18-Image -> Crash-Loop) Verifiziert auf TrueNAS: Custom-App gerbilmanager RUNNING, 2370 Tiere, Port 8090. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
154 lines
5.3 KiB
YAML
154 lines
5.3 KiB
YAML
# GerbilManager — TrueNAS Custom Application
|
|
# ==============================================
|
|
# Vor dem ersten Start:
|
|
# 1. Kopiere deploy/truenas/.env.example -> deploy/truenas/.env und setze die Werte.
|
|
# 2. Lege die Dataset-Pfade auf der NAS an (pgdata, photos, backups).
|
|
# 3. docker compose -f deploy/truenas/compose.yaml up -d
|
|
#
|
|
# Zugriff: http://<NAS-IP>:${PORT:-80}
|
|
# API-Doku: http://<NAS-IP>:${PORT:-80}/scalar
|
|
|
|
services:
|
|
|
|
# --- PostgreSQL-Datenbank ---
|
|
# postgres:18 (Debian): Locale en_US.utf8 == Quell-DB (Aspire) -> sauberer Dump-Restore.
|
|
db:
|
|
image: postgres:18
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
|
POSTGRES_DB: gerbilmanager
|
|
POSTGRES_USER: postgres
|
|
volumes:
|
|
# postgres:18: Volume auf /var/lib/postgresql (PGDATA landet in .../18/docker).
|
|
# Mount auf .../data laesst v18 nicht starten -> db unhealthy.
|
|
- pgdata:/var/lib/postgresql
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U postgres -d gerbilmanager"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 30s
|
|
|
|
# --- .NET API (GerbilManagerWebAPI) ---
|
|
api:
|
|
image: "${REGISTRY:-git.rismer.de/gulum}/gerbilmanager-api:${TAG:-latest}"
|
|
build:
|
|
context: ../..
|
|
dockerfile: GerbilManagerWebAPI/Dockerfile
|
|
restart: unless-stopped
|
|
environment:
|
|
ASPNETCORE_ENVIRONMENT: Production
|
|
# Verbindung zur Postgres-DB im selben Compose-Netz
|
|
ConnectionStrings__gerbilmanager: "Host=db;Port=5432;Database=gerbilmanager;Username=postgres;Password=${POSTGRES_PASSWORD}"
|
|
# Speicherort der hochgeladenen Fotos (NAS-Dataset gemounted unter /data/photos)
|
|
Photos__RootPath: /data/photos
|
|
# AR-3: Data Protection Key-Ring (persistiert Gmail-App-Passwort-Verschlüsselung über Redeployments)
|
|
DataProtection__KeyRingPath: /data/keys
|
|
# AR-4: KI-Funktionen (Verkaufstext + Posteingang-Entwurf, Sektion AI; beliebiger OpenAI-kompatibler Anbieter)
|
|
# Anbieter-Optionen und Schlüssel-Beispiele: docs/ai-provider.md
|
|
# Leer lassen = KI deaktiviert (503 AiKeyMissing statt Fehler)
|
|
AI__BaseUrl: "${AI__BaseUrl:-}"
|
|
AI__ApiKey: "${AI__ApiKey:-}"
|
|
AI__Model: "${AI__Model:-gemini-flash-latest}"
|
|
# WEB-2: Pfad wo POST /api/publish die oeffentliche Seite hinschreibt
|
|
PublicSite__RootPath: /data/publicsite
|
|
volumes:
|
|
- photos:/data/photos
|
|
- keys:/data/keys
|
|
- publicsite:/data/publicsite
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "curl -fsS http://localhost:8080/health || exit 1"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 90s
|
|
|
|
# --- nginx Frontend (React SPA + API-Proxy) ---
|
|
frontend:
|
|
image: "${REGISTRY:-git.rismer.de/gulum}/gerbilmanager-frontend:${TAG:-latest}"
|
|
build:
|
|
context: ../..
|
|
dockerfile: gerbil-manager-web/Dockerfile
|
|
restart: unless-stopped
|
|
ports:
|
|
# NAS-Port 80 ist durch den nginx-Reverse-Proxy belegt -> Default 8090.
|
|
- "${PORT:-8090}:80"
|
|
depends_on:
|
|
api:
|
|
condition: service_healthy
|
|
|
|
# --- nginx Public Site (WEB-2) ---
|
|
# Serviert NUR die statische oeffentliche Seite (live/ aus dem publicsite-Volume).
|
|
# SICHERHEIT: Kein Proxy auf api/frontend — nur statisches HTML nach aussen.
|
|
# Julian's externer nginx-Proxy leitet <DOMAIN> auf Port 8081 weiter.
|
|
publicsite:
|
|
image: nginx:alpine
|
|
restart: unless-stopped
|
|
ports:
|
|
- "${PUBLICSITE_PORT:-8081}:80"
|
|
volumes:
|
|
- publicsite:/usr/share/nginx/html:ro
|
|
- ./nginx/publicsite.conf:/etc/nginx/conf.d/default.conf:ro
|
|
depends_on:
|
|
- api
|
|
|
|
# --- Backup-Sidecar (taeglicher pg_dump + Foto-Archiv + Rotation) ---
|
|
backup:
|
|
image: postgres:18
|
|
restart: unless-stopped
|
|
environment:
|
|
PGPASSWORD: "${POSTGRES_PASSWORD}"
|
|
POSTGRES_HOST: db
|
|
POSTGRES_USER: postgres
|
|
POSTGRES_DB: gerbilmanager
|
|
BACKUP_KEEP_DAYS: "${BACKUP_KEEP_DAYS:-7}"
|
|
volumes:
|
|
- photos:/data/photos:ro
|
|
- backups:/backups
|
|
- ./scripts:/scripts:ro
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
entrypoint: ["/bin/sh", "/scripts/entrypoint.sh"]
|
|
|
|
volumes:
|
|
# NAS-Datasets als Bind-Mounts (Pfade in .env konfigurieren).
|
|
# TrueNAS Goldeye: /mnt/JailStorage/DockerVolumes/gerbilmanager/<name>
|
|
pgdata:
|
|
driver: local
|
|
driver_opts:
|
|
type: none
|
|
o: bind
|
|
device: "${PGDATA_PATH:-/mnt/JailStorage/DockerVolumes/gerbilmanager/pgdata}"
|
|
photos:
|
|
driver: local
|
|
driver_opts:
|
|
type: none
|
|
o: bind
|
|
device: "${PHOTOS_PATH:-/mnt/JailStorage/DockerVolumes/gerbilmanager/photos}"
|
|
# AR-3: Data Protection key ring — persistiert Gmail-App-Passwort-Verschlüsselung.
|
|
# Muss ein persistentes NAS-Dataset sein (nicht dasselbe wie photos).
|
|
keys:
|
|
driver: local
|
|
driver_opts:
|
|
type: none
|
|
o: bind
|
|
device: "${KEYS_PATH:-/mnt/JailStorage/DockerVolumes/gerbilmanager/keys}"
|
|
backups:
|
|
driver: local
|
|
driver_opts:
|
|
type: none
|
|
o: bind
|
|
device: "${BACKUPS_PATH:-/mnt/JailStorage/DockerVolumes/gerbilmanager/backups}"
|
|
# WEB-2: gemeinsames Volume fuer api (rw) und publicsite-nginx (ro).
|
|
publicsite:
|
|
driver: local
|
|
driver_opts:
|
|
type: none
|
|
o: bind
|
|
device: "${PUBLICSITE_PATH:-/mnt/JailStorage/DockerVolumes/gerbilmanager/publicsite}"
|