build: Docker-Images, Compose-Setup und Gitea-Workflows

- Mehrstufige Dockerfiles je Dienst mit Stufen für Entwicklung und Betrieb,
  beide Images laufen als unprivilegierter Benutzer
- Backend-Entrypoint wartet auf die Datenbank, migriert und seedt nur bei
  leerem Kategoriebaum (neues Flag --if-empty)
- docker-compose.yml mit Netz-Trennung, Healthchecks und benannten Volumes;
  die Datenbank ist ausschließlich im internen Netz erreichbar
- nginx liefert das Bundle aus und reicht /api weiter; index.html ungecacht,
  gehashte Assets ein Jahr
- Gitea-Workflows: ci.yml für Lint, Tests und Bundle-Bau, build.yml für die
  Images nach linux/amd64 ohne Deploy-Schritt
- docs/runner.md und docs/deployment.md, .dockerignore je Dienst

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014e7t8UpmoVNMtWivY5LiSH
This commit is contained in:
moneyfy
2026-09-09 17:14:44 +02:00
co-authored by Claude Opus 5
parent 54c59c9f71
commit 87bee9b72a
17 changed files with 1184 additions and 10 deletions
+212
View File
@@ -0,0 +1,212 @@
# Betrieb auf docker-srv001
## Erstinstallation
```bash
mkdir -p /opt/moneyfy && cd /opt/moneyfy
# docker-compose.yml und .env.example aus dem Repository holen
curl -fsSLO https://git.menzel.center/menzeljonas/moneyfy/raw/branch/main/docker-compose.yml
curl -fsSLO https://git.menzel.center/menzeljonas/moneyfy/raw/branch/main/.env.example
cp .env.example .env
```
In `.env` mindestens setzen:
```bash
POSTGRES_PASSWORD=$(openssl rand -hex 24)
SECRET_KEY=$(openssl rand -hex 32)
MONEYFY_ADMIN_PASSWORD=<Startpasswort>
PUBLIC_BASE_URL=https://moneyfy.menzel.center
TAG=latest
```
Das externe Proxy-Netz muss existieren NPMplus bringt es üblicherweise mit:
```bash
docker network inspect proxy >/dev/null 2>&1 || docker network create proxy
```
An der Registry anmelden und starten:
```bash
docker login git.menzel.center
docker compose up -d
docker compose logs -f moneyfy-backend
```
Das Backend wartet auf die Datenbank, spielt die Migrationen ein und legt beim
allerersten Start den Kategoriebaum an. Danach ist die Oberfläche unter
`http://127.0.0.1:8087` erreichbar.
Beim ersten Anmelden gelten `MONEYFY_ADMIN_USER` und `MONEYFY_ADMIN_PASSWORD`;
die Anwendung verlangt sofort ein neues Passwort. Danach kann
`MONEYFY_ADMIN_PASSWORD` aus der `.env` entfernt werden.
## NPMplus konfigurieren
`moneyfy.menzel.center` als **Proxy Host** anlegen:
| Feld | Wert |
|---|---|
| Domain Names | `moneyfy.menzel.center` |
| Scheme | `http` |
| Forward Hostname / IP | `moneyfy-frontend` |
| Forward Port | `8080` |
| Cache Assets | aus der Container setzt die Cache-Kopfzeilen selbst |
| Block Common Exploits | an |
| Websockets Support | aus die Anwendung braucht keine |
Unter *SSL*: Zertifikat über Let's Encrypt anfordern, **Force SSL** und **HTTP/2**
einschalten, **HSTS** aktivieren.
Damit NPMplus den Container über seinen Namen erreicht, müssen beide im Netz
`proxy` liegen. Das erledigt `docker-compose.yml` bereits; prüfen mit:
```bash
docker network inspect proxy --format '{{range .Containers}}{{.Name}} {{end}}'
```
Unter *Advanced* diese Zusatzkonfiguration eintragen. Sie sorgt dafür, dass die
Anwendung das richtige Schema sieht und Exporte nicht in einen Timeout laufen:
```nginx
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
client_max_body_size 4m;
proxy_read_timeout 120s;
```
> Die Anmeldung setzt `Secure`-Cookies. Ohne HTTPS also ohne Zugriff über
> NPMplus kommt keine Sitzung zustande. Für einen Test über
> `http://127.0.0.1:8087` muss `COOKIE_SECURE=false` gesetzt werden.
## Aktualisieren
Das Ausrollen passiert bewusst von Hand; die CI baut nur die Images.
```bash
cd /opt/moneyfy
# Bei fester Version: TAG in .env auf die neue Version setzen.
docker compose pull
docker compose up -d
docker compose logs -f moneyfy-backend
```
Die Migrationen laufen beim Start des Backends automatisch. Ein Blick in die
Ausgabe zeigt, welche Revision eingespielt wurde.
Zurückrollen: `TAG` auf die vorherige Version setzen und erneut
`docker compose up -d`. **Achtung:** Migrationen werden dabei nicht rückgängig
gemacht. Vor einem Update mit Schemaänderung immer erst sichern.
## Sicherung
Zwei Dinge müssen gesichert werden: die Datenbank und das Volume mit den Logos.
### Datenbank
```bash
docker compose exec -T moneyfy-db \
pg_dump -U moneyfy -d moneyfy --format=custom \
> /var/backups/moneyfy/db-$(date +%F).dump
```
Wiederherstellen:
```bash
docker compose stop moneyfy-backend
docker compose exec -T moneyfy-db \
pg_restore -U moneyfy -d moneyfy --clean --if-exists < /var/backups/moneyfy/db-2026-03-01.dump
docker compose start moneyfy-backend
```
### Logo-Volume
```bash
docker run --rm \
-v moneyfy-data:/data:ro \
-v /var/backups/moneyfy:/backup \
alpine tar czf /backup/logos-$(date +%F).tar.gz -C /data .
```
Wiederherstellen:
```bash
docker run --rm \
-v moneyfy-data:/data \
-v /var/backups/moneyfy:/backup \
alpine sh -c "rm -rf /data/* && tar xzf /backup/logos-2026-03-01.tar.gz -C /data"
```
Die Logos lassen sich zur Not auch neu beschaffen die Datenbank nicht.
### Tägliche Sicherung per Cron
`/opt/moneyfy/backup.sh`:
```bash
#!/bin/sh
set -eu
ZIEL=/var/backups/moneyfy
TAG=$(date +%F)
mkdir -p "$ZIEL"
cd /opt/moneyfy
docker compose exec -T moneyfy-db \
pg_dump -U moneyfy -d moneyfy --format=custom > "$ZIEL/db-$TAG.dump"
docker run --rm \
-v moneyfy-data:/data:ro \
-v "$ZIEL:/backup" \
alpine tar czf "/backup/logos-$TAG.tar.gz" -C /data .
# Älteres als 30 Tage entfernen.
find "$ZIEL" -name 'db-*.dump' -mtime +30 -delete
find "$ZIEL" -name 'logos-*.tar.gz' -mtime +30 -delete
```
```cron
30 3 * * * /opt/moneyfy/backup.sh >> /var/log/moneyfy-backup.log 2>&1
```
Die Sicherungen anschließend auf ein anderes System spiegeln eine Kopie auf
demselben Host hilft bei einem Plattenausfall nicht.
## Fehlersuche
**Backend startet nicht, Log endet bei „Warte auf die Datenbank“**
Die Datenbank ist nicht gesund. `docker compose ps` und
`docker compose logs moneyfy-db` prüfen; meist stimmt `POSTGRES_PASSWORD` in der
`.env` nicht mit dem im Volume hinterlegten Passwort überein. Ein bereits
angelegtes Volume übernimmt kein neues Passwort.
**`SECRET_KEY ist nicht gesetzt`**
Die Anwendung verweigert in Produktion den Start mit dem Platzhalterwert.
`openssl rand -hex 32` erzeugt einen passenden Schlüssel. Wird er später
geändert, sind alle Sitzungen ungültig das ist beabsichtigt.
**Anmeldung schlägt fehl, obwohl das Passwort stimmt**
Meist fehlt HTTPS: Die Cookies sind `Secure` gesetzt und erreichen den Server
über eine reine HTTP-Verbindung nicht. Entweder über NPMplus zugreifen oder
`COOKIE_SECURE=false` setzen.
**Firmen bekommen kein Logo**
Ohne Netzzugang findet nur der lokale Markenkatalog etwas; unbekannte Firmen
erhalten einen erzeugten Avatar. Das ist so gewollt. Mit
`docker compose exec moneyfy-backend python -c "from app.services.logos import simple_icon_index; print(simple_icon_index().version)"`
lässt sich prüfen, ob der Katalog im Image liegt.
**Keine Benachrichtigungen**
`SCHEDULER_ENABLED` und `NOTIFICATIONS_ENABLED` prüfen, dann in der Oberfläche
unter *Einstellungen → Benachrichtigungen* den Einrichtungsstand ansehen und
eine Testnachricht schicken. Das Versandprotokoll steht auf derselben Seite.
**Speicherplatz**
```bash
docker system df
docker compose exec moneyfy-db psql -U moneyfy -d moneyfy -c "\l+"
docker run --rm -v moneyfy-data:/data:ro alpine du -sh /data
```