From 2ba78acc7809deb3412daf859c5aa717c7fa77e9 Mon Sep 17 00:00:00 2001 From: Micha Date: Thu, 26 Mar 2026 16:07:11 +0000 Subject: [PATCH] HOMELAB_ARCHITECTURE_MASTER_V2.md aktualisiert --- HOMELAB_ARCHITECTURE_MASTER_V2.md | 481 ++++++++++++++++-------------- 1 file changed, 265 insertions(+), 216 deletions(-) diff --git a/HOMELAB_ARCHITECTURE_MASTER_V2.md b/HOMELAB_ARCHITECTURE_MASTER_V2.md index f4c51ae..b4d74c2 100644 --- a/HOMELAB_ARCHITECTURE_MASTER_V2.md +++ b/HOMELAB_ARCHITECTURE_MASTER_V2.md @@ -3,6 +3,8 @@ > **Single Source of Truth** für Docker-Netzwerkarchitektur, Sicherheitsregeln, Zielbild und Migration des Kallilabcore-Homelabs. > **Arbeitsregel für KI-Assistenten:** Dieses Dokument immer zuerst lesen, bevor Fragen zu Containern, Netzwerken, Traefik, Tailscale, Migration oder Security beantwortet werden. +**Stand:** 2026-03-26 | **Aktueller Sprint:** 4 (Frontend-Stack) — Sprints 1–3 abgeschlossen + --- ## Inhaltsverzeichnis @@ -18,6 +20,7 @@ 10. [Bekannte Ausnahmen und Begründungen](#10-bekannte-ausnahmen-und-begründungen) 11. [Projektorganisation und Arbeitsmodus](#11-projektorganisation-und-arbeitsmodus) 12. [Nutzung mit KI / Kontext-Regel](#12-nutzung-mit-ki--kontext-regel) +13. [Betriebserfahrungen und Entscheidungs-Log](#13-betriebserfahrungen-und-entscheidungs-log) --- @@ -33,7 +36,7 @@ | Basis-Domain | `kaleschke.info` | | TLS | Let's Encrypt via Cloudflare DNS Challenge | | Certresolver | `le` | -| Compose-Standard | Unraid Compose Manager | +| Compose-Standard | Portainer Stacks (Web-Editor oder Git) | | Zusatz-Tool | Portainer als Verwaltungs-UI | | Homelab-Compose-Pfad | `/mnt/user/services/homelab/` | | Secrets-Pfad | `/mnt/user/appdata/secrets/` | @@ -44,14 +47,14 @@ ## 2. Architektur-Prinzipien ### P1 — Traefik ist der einzige öffentliche HTTP(S)-Einstiegspunkt -Kein Webdienst veröffentlicht final direkte Host-Ports außer `traefik` selbst. -Begründete Ausnahmen: `gitea`-SSH, `Plex-Media-Server`, `binhex-official-pihole`, `Tailscale-Docker`. +Kein Webdienst veröffentlicht finale direkte Host-Ports außer `traefik` selbst. +Begründete Ausnahmen: `gitea`-SSH (Port 222), `Plex-Media-Server`, `binhex-official-pihole`, `Tailscale-Docker`. ### P2 — Das Setup bleibt bewusst einfach: `frontend_net` + `backend_net` + app-interne Netze Die Quellenlage für Homelabs mit Traefik spricht für ein simples 2-Netz-Modell: - `frontend_net` = Proxy-/Web-Netz - `backend_net` = intern für DB/Cache/App-Kommunikation -- zusätzliche Netze nur app-intern, wenn technisch nötig (`mealie_internal`, `immich_default`, `scanopy_scanopy`, `dns_net`) +- zusätzliche Netze nur app-intern, wenn technisch nötig (`mealie_mealie_internal`, `immich_default`, `dns_net`) Es gibt **keine künstlichen globalen Zusatznetze** wie `admin_net`, `monitoring_net` oder `media_net`. @@ -66,7 +69,7 @@ Alle produktiven Container werden als Compose verwaltet. Bestehende Dockerman-/Ad-hoc-Container werden schrittweise migriert. ### P6 — Secrets nie im Klartext -Passwörter, Tokens und API-Keys gehören in Secret-Dateien unter `/mnt/user/appdata/secrets/` oder in `_FILE`-Variablen. +Passwörter, Tokens und API-Keys gehören in Secret-Dateien unter `/mnt/user/appdata/secrets/` oder als Portainer Environment Variables mit `${VARIABLE}` in der Compose. Ziel: kein sensibles Secret mehr sichtbar in `docker inspect`. ### P7 — `restart: unless-stopped` ist Pflichtstandard @@ -89,10 +92,8 @@ Jeder produktive Container nutzt `restart: unless-stopped`, außer eine Ausnahme | `frontend_net` | bridge, external | einziges Traefik-/Web-Netz | Standard | | `backend_net` | bridge, `internal: true` | interne App-/DB-/Cache-Kommunikation | Standard | | `dns_net` | bridge | Resolver-Schicht für `unbound` | bleibt | -| `mealie_internal` | bridge, `internal: true` | internes Netz nur für `mealie` + `mealie-postgres` | Ziel | -| `immich_default` | Compose-intern | internes Immich-Netz | bleibt | -| `scanopy_scanopy` | Compose-intern | internes Scanopy-Netz | bleibt | -| `diun_default` | Compose-intern | Compose-Netz für diun | bleibt, zusätzlich Join zu `frontend_net` | +| `mealie_mealie_internal` | bridge, `internal: true` | internes Netz nur für `mealie` + `mealie-postgres` | ✅ umgesetzt | +| `immich_default` | Compose-intern | internes Immich-Netz | ⏳ `internal: true` noch setzen | | `host` | host | nur für echte Sonderfälle | begründet | ### 3.2 Finales Diagramm (vereinfacht) @@ -104,20 +105,19 @@ Internet traefik (80/443) │ └── frontend_net - ├── öffentliche Apps - ├── Admin-UIs mit Middleware - └── interne Web-UIs für Tailscale-only + ├── öffentliche Apps (vaultwarden, mealie, paperless, immich, gitea, gotify) + ├── Admin-UIs mit Middleware (portainer, dozzle, uptime-kuma, dashdot, filebrowser, scrutiny, code-server) + └── Hybrid-Dienste mit Internetbedarf (mail-archiver, ddns-updater) backend_net (internal: true) ├── postgresql17 ├── Redis ├── mail-archiver - └── paperless / andere Backends + └── paperless-ngx App-interne Netze - ├── mealie_internal - ├── immich_default - └── scanopy_scanopy + ├── mealie_mealie_internal (internal: true) ✅ + └── immich_default (internal: true ausstehend) ⏳ Host-Sonderfälle ├── Tailscale-Docker @@ -153,9 +153,8 @@ Diese Dienste dürfen final über echte `*.kaleschke.info`-Domains erreichbar se - `gotify` - `gitea` (Web) - `immich_server` -- optional `mail-archiver` -- optional `backrest` -- optional `Stash` (nur wenn bewusst gewünscht) +- `mail-archiver` +- `backrest` ### 4.2 Nicht öffentlich / nur Tailscale oder Traefik + Middleware Diese Dienste sind **keine Public Apps**: @@ -168,7 +167,6 @@ Diese Dienste sind **keine Public Apps**: - `scrutiny` - `luckyBackup` - `code-server` -- `scanopy-server` - `Traefik-Dashboard` - `netdata` - `Glances` @@ -189,7 +187,7 @@ Admin-Dienste dürfen im `frontend_net` liegen, wenn: 1. Keine produktiven Dienste im Docker-Default-`bridge` 2. Keine direkten Host-Ports für Web-UIs außer dokumentierte Ausnahmen 3. `restart: unless-stopped` als Standard -4. Secrets als Datei / `_FILE` +4. Secrets als Datei / `_FILE` oder Portainer Environment Variables mit `${VAR}` 5. `no-new-privileges:true` ergänzen, wo praktikabel 6. `traefik.docker.network=frontend_net` immer explizit setzen 7. Admin-Dienste immer mit `dashboard-auth@file,secure-headers@file` @@ -207,29 +205,34 @@ Admin-Dienste dürfen im `frontend_net` liegen, wenn: - **Ja** → `frontend_net` - **Nein** → weiter zu Schritt 2 -### Schritt 2 — Braucht der Dienst eine DB / Redis / interne Backends? +### Schritt 2 — Braucht der Dienst externe Internetverbindungen? +- **Ja** → `frontend_net` (auch ohne Web-UI — z.B. `ddns-updater`, `mail-archiver`) +- **Nein** → weiter zu Schritt 3 + +### Schritt 3 — Braucht der Dienst eine DB / Redis / interne Backends? - **Ja** → zusätzlich `backend_net` oder eigenes app-internes Netz - **Nein** → nur das funktional nötige Netz -### Schritt 3 — Ist es eine Datenbank oder ein Cache? +### Schritt 4 — Ist es eine Datenbank oder ein Cache? - **Ja** → niemals `frontend_net`, nur `backend_net` oder internes Compose-Netz -### Schritt 4 — Ist es ein Admin-/Monitoring-Dienst? +### Schritt 5 — Ist es ein Admin-/Monitoring-Dienst? - **Ja** → wenn Web-UI vorhanden trotzdem `frontend_net`, aber nur mit: - Middleware - keiner direkten Portfreigabe - Tailscale-only-Charakter -### Schritt 5 — Braucht der Dienst Host-/Discovery-/L2-Sicht? +### Schritt 6 — Braucht der Dienst Host-/Discovery-/L2-Sicht? - **Ja** → `host` nur mit dokumentierter Begründung - **Nein** → kein `host` -### Schritt 6 — Braucht die App ein eigenes internes App-Netz? +### Schritt 7 — Braucht die App ein eigenes internes App-Netz? - **Ja** → Compose-internes Netz mit `internal: true` - **Nein** → kein weiteres Netz anlegen ### Kurzregel - **UI** → `frontend_net` +- **Externer Internetzugriff nötig** → `frontend_net` (auch ohne UI) - **DB/Cache** → `backend_net` - **Spezialfall** → app-internes Netz - **Host-Zugriff** → nur dokumentierte Ausnahme @@ -239,7 +242,7 @@ Admin-Dienste dürfen im `frontend_net` liegen, wenn: ## 7. Container-Zielbild (vollständig) Legende Status: -- `✅` = bereits weitgehend passend +- `✅` = umgesetzt - `⏳` = noch zu migrieren / zu korrigieren ### 7.1 Infrastruktur / Core @@ -248,66 +251,70 @@ Legende Status: |---|---|---|---|---|---| | `traefik` | ✅ | `frontend_net`, `backend_net` | öffentlich | zentraler Ingress, 80/443 direkt | Middleware-File-Provider sauber pflegen | | `homepage` | ✅ | `frontend_net` | Traefik | öffentliche Startseite | — | -| `ddns-updater` | ⏳ | `backend_net` | intern | kein Grund für `frontend_net` | aus `frontend_net` raus | -| `Tailscale-Docker` | ⏳ | `host` | VPN-Zugang | bleibt Host-Sonderfall | `restart` fixen; `TS_USERSPACE`/`privileged` später prüfen, nicht als Quick Win | -| `binhex-official-pihole` | ⏳ | `host` | LAN DNS / intern | bleibt Host-Sonderfall | `restart` fixen | +| `ddns-updater` | ✅ | `frontend_net` | intern | bleibt in `frontend_net` — braucht Internetzugang für Cloudflare DNS | Dokumentierte Ausnahme, kein `backend_net` wegen `internal: true` | +| `Tailscale-Docker` | ✅ | `host` | VPN-Zugang | Host-Sonderfall, `restart: unless-stopped` gesetzt | `TS_USERSPACE`/`privileged` später prüfen | +| `binhex-official-pihole` | ✅ | `host` | LAN DNS / intern | Host-Sonderfall, `restart: unless-stopped` gesetzt | — | | `unbound` | ✅ | `dns_net` | intern | Resolver bleibt isoliert | — | -| `backrest` | ⏳ | `frontend_net`, `backend_net` | Traefik oder intern | UI via Traefik, Repo intern | `traefik.docker.network` auf `frontend_net`; DNS-Hardcoding entfernen; Mounts straffen | -| `diun` | ⏳ | `diun_default`, `frontend_net` | intern | braucht `frontend_net`, um `gotify` zu erreichen | Gotify-Endpoint per Container-Name | -| `theme-park` | ⏳ | `frontend_net` oder rein intern | intern oder Traefik | nur veröffentlichen, wenn wirklich genutzt | Placeholder-Labels bereinigen | -| `scanopy-daemon` | ⏳ | `host`, `scanopy_scanopy` falls nötig | intern | Host-naher Sonderfall | Privileged nur dokumentiert belassen oder später testen | +| `backrest` | ✅ | `frontend_net`, `backend_net` | Traefik | `traefik.docker.network=frontend_net` korrigiert | Breite Mounts straffen (Block F) | +| `diun` | ✅ | `frontend_net` | intern | in `frontend_net`, gotify via Container-Name erreichbar | — | ### 7.2 Sicherheit / Identity | Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte | |---|---|---|---|---|---| -| `vaultwarden` | ✅ | `frontend_net` | Traefik | keine Host-Ports, kein `bridge`, Secret-Datei | `ADMIN_TOKEN`-Bug fixen; Port 4743 entfernen; Compose-Migration | +| `vaultwarden` | ✅ | `frontend_net` | Traefik | kein Host-Port, Secret-Datei (`ADMIN_TOKEN_FILE`) | — | ### 7.3 Datenbanken / Caches | Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte | |---|---|---|---|---|---| -| `postgresql17` | ✅ | `backend_net` | intern | keine Host-Ports, kein `bridge` | Port 5432 entfernen; Secret-Datei; Compose-Migration | +| `postgresql17` | ✅ | `backend_net` | intern | kein Host-Port, `POSTGRES_PASSWORD_FILE` | — | | `Redis` | ✅ | `backend_net` | intern | intern-only Cache | optional named volume statt anonym | -| `mealie-postgres` | ✅ | `mealie_internal` | intern | nur intern, nie `frontend_net` | aus `frontend_net` raus; Secret-Datei | -| `immich_postgres` | ⏳ | `immich_default` | intern | intern-only | Passwort rotieren; named volume prüfen | -| `immich_redis` | ⏳ | `immich_default` | intern | intern-only | anonymes Volume perspektivisch bereinigen | -| `scanopy-postgres` | ⏳ | `scanopy_scanopy` | intern | intern-only | Passwort rotieren (aktuell Wiederverwendung) | +| `mealie-postgres` | ✅ | `mealie_mealie_internal` | intern | isoliert, nie `frontend_net` | — | +| `immich_postgres` | ✅ | `immich_default` | intern | intern-only | `immich_default` → `internal: true` ausstehend | +| `immich_redis` | ⏳ | `immich_default` | intern | intern-only | anonymes Volume → named volume | ### 7.4 Öffentliche Apps | Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte | |---|---|---|---|---|---| -| `paperless-ngx` | ⏳ | `frontend_net`, `backend_net` | Traefik | vorbereitete Labels final aktivieren | `traefik.enable=true`; echte Domain; Port entfernen; Secret-Datei | -| `Paperless-AI` | ⏳ | `frontend_net` | Traefik oder intern | Port nicht mehr direkt offen | echte Domain; Labels aktivieren | -| `mealie` | ✅ | `frontend_net`, `mealie_internal` | Traefik | sauber getrennte App/DB-Struktur | Port entfernen; Secret-Datei | -| `gotify` | ⏳ | `frontend_net` | Traefik oder intern | intern per Container-Name für `diun` erreichbar | Passwort rotieren; Port entfernen | -| `gitea` | ⏳ | `frontend_net` | Traefik + SSH | Web via Traefik, SSH-Port bleibt | HTTP-Labels sauberziehen | -| `immich_server` | ⏳ | `immich_default`, `frontend_net` | Traefik | internes Immich-Netz bleibt; Web via Traefik | Port 2283 entfernen; Secret-Datei; `frontend_net` ergänzen | +| `paperless-ngx` | ✅ | `frontend_net`, `backend_net` | Traefik | aktiv via `paperless.kaleschke.info` | — | +| `Paperless-AI` | ✅ | `frontend_net` | Traefik | aktiv | — | +| `mealie` | ✅ | `frontend_net`, `mealie_mealie_internal` | Traefik | sauber getrennte App/DB-Struktur | — | +| `gotify` | ✅ | `frontend_net` | Traefik | aktiv via `gotify.kaleschke.info`, kein Host-Port | — | +| `gitea` | ✅ | `frontend_net` | Traefik + SSH | Web via Traefik, SSH-Port 222 bleibt | — | +| `immich_server` | ✅ | `immich_default`, `frontend_net` | Traefik | aktiv via `immich.kaleschke.info` | `immich_default` → `internal: true` ausstehend | | `immich_machine_learning` | ✅ | `immich_default` | intern | bleibt intern | — | -| `Stash` | ⏳ | `frontend_net` | intern oder Traefik | aus `bridge` raus; nur veröffentlichen wenn bewusst gewünscht | Port entfernen; ggf. Traefik-Labels | ### 7.5 Admin / Operations | Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte | |---|---|---|---|---|---| -| `code-server` | ⏳ | `frontend_net` | Traefik + Middleware / Tailscale-only | nicht öffentlich offen | `PASSWORD` → `HASHED_PASSWORD`; Secret-Datei | -| `PortainerCE` | ⏳ | `frontend_net` | Traefik + Middleware / Tailscale-only | keine Host-Ports | echte Domain; Labels aktivieren; direkte Ports entfernen; **Docker-Socket nicht blind auf `:ro`** | -| `Dozzle` | ⏳ | `frontend_net` | Traefik + Middleware / Tailscale-only | keine Host-Ports | Labels aktivieren; direkte Ports entfernen | -| `filebrowser` | ⏳ | `frontend_net` | Traefik + Middleware / Tailscale-only | aus `bridge`; breite FS-Mounts prüfen | Port entfernen; Mounts einschränken | -| `scanopy-server` | ⏳ | `scanopy_scanopy`, `frontend_net` | Traefik + Middleware / Tailscale-only | kein direkter Host-Port | `frontend_net` ergänzen; Port entfernen | -| `luckyBackup` | ⏳ | `frontend_net` | Traefik + Middleware / Tailscale-only | aus `bridge`; nur intern | Port entfernen; breite Mounts prüfen | +| `code-server` | ✅ | `frontend_net` | Traefik + Middleware | `PASSWORD_FILE` aktiv | — | +| `PortainerCE` | ✅ | `frontend_net` | Traefik + Middleware | keine Host-Ports | — | +| `Dozzle` | ✅ | `frontend_net` | Traefik + Middleware | keine Host-Ports | — | +| `filebrowser` | ✅ | `frontend_net` | Traefik + Middleware | aktiv via `files.kaleschke.info` | Breite Mounts einschränken (Block F) | +| `luckyBackup` | ⏳ | `frontend_net` | Traefik + Middleware | noch in `bridge` | aus `bridge`; Port entfernen | +| `mail-archiver` | ✅ | `frontend_net`, `backend_net` | Traefik | aktiv via `mail.kaleschke.info`, kein Host-Port | — | ### 7.6 Monitoring / Status | Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte | |---|---|---|---|---|---| -| `UptimeKuma` | ⏳ | `frontend_net` | Traefik + Middleware / Tailscale-only | keine direkten Ports | Labels sauberziehen; Port entfernen | -| `dashdot` | ⏳ | `frontend_net` | Traefik + Middleware / Tailscale-only | keine direkten Ports | Placeholder-Domain raus; Labels aktivieren | -| `Glances` | ⏳ | `host` | Tailscale-only | Host-Metriken | optional später hinter Traefik, aber nicht nötig | -| `netdata` | ⏳ | `host` | Tailscale-only | Host-Metriken | `restart` fixen; leere CLAIM-Vars aufräumen | -| `scrutiny` | ⏳ | `frontend_net` | Traefik + Middleware / Tailscale-only | aus `bridge`; echtes Image beibehalten | Ports entfernen; später prüfen, ob `privileged` reduziert werden kann | -| `netalertx` | ✅ | `host` | Tailscale-only | Host-/Netzsicht bleibt | optional später hinter Traefik, aber kein Muss | +| `UptimeKuma` | ✅ | `frontend_net` | Traefik + Middleware | aktiv via `uptime.kaleschke.info`, kein Host-Port | — | +| `dashdot` | ✅ | `frontend_net` | Traefik + Middleware | aktiv, kein Host-Port | — | +| `Glances` | ✅ | `host` | Tailscale-only | Host-Metriken | — | +| `netdata` | ✅ | `host` | Tailscale-only | Host-Metriken | leere CLAIM-Vars entfernen | +| `scrutiny` | ✅ | `frontend_net` | Traefik + Middleware | aktiv via `scrutiny.kaleschke.info`, Git-Stack | `privileged` später prüfen ob reduzierbar | +| `netalertx` | ✅ | `host` | Tailscale-only | Host-/Netzsicht | — | + +### 7.7 Entfernte Container + +| Container | Entfernt am | Begründung | +|---|---|---| +| `scanopy-server` | 2026-03-26 | nicht aktiv genutzt, durch paperless-ngx ersetzt | +| `scanopy-postgres` | 2026-03-26 | zusammen mit scanopy entfernt | +| `scanopy-daemon` | 2026-03-26 | zusammen mit scanopy entfernt | --- @@ -332,9 +339,10 @@ labels: ``` ### Regeln -- `traefik.docker.network` immer explizit auf `frontend_net` +- `traefik.docker.network` immer explizit auf `frontend_net` — nie auf `backend_net` oder anderen Netzen! - keine `yourdomain.tld`-Platzhalter - certresolver immer `le` +- `tls=true` immer explizit setzen — ohne diese Zeile kein TLS - wenn Traefik aktiv ist, werden direkte Host-Ports entfernt - Admin-Dienste niemals ohne Middleware veröffentlichen @@ -342,96 +350,125 @@ labels: ## 9. Migrationsstrategie (Blöcke A–F) -**Letzte Aktualisierung:** 2026-03-25 +**Letzte Aktualisierung:** 2026-03-26 -### Block A — Quick Wins (geringes Risiko, sofort) +### Block A — Quick Wins ✅ ABGESCHLOSSEN ```text -[x] restart: unless-stopped für: - - Tailscale-Docker - - binhex-official-pihole - - postgresql17 - - vaultwarden - - mail-archiver - - scrutiny - - netdata - - Stash - - Plex-Media-Server - -[x] vaultwarden ADMIN_TOKEN-Doppelpräfix korrigieren -[x] backrest DNS-Hardcoding entfernen -[x] diun ↔ gotify Erreichbarkeit prüfen / herstellen -[x] leere Netzwerke prüfen und entfernen: br0, immich_net, kopia_default, netbox_default -[x] ungenutzte anonyme Volumes prüfen +[x] restart: unless-stopped für alle Container gesetzt +[x] vaultwarden ADMIN_TOKEN-Doppelpräfix korrigiert +[x] backrest DNS-Hardcoding entfernt +[x] diun ↔ gotify Erreichbarkeit hergestellt +[x] leere Netzwerke entfernt: br0, immich_net, kopia_default, netbox_default, diun_default +[x] anonyme/verwaiste Volumes bereinigt (9 anonyme + immich_model-cache alt) +[x] scanopy komplett entfernt (3 Container + 2 Volumes + Netz) ``` -### Block B — Kritische Kernmigrationen (höchste Priorität) +### Block B — Kritische Kernmigrationen ✅ ABGESCHLOSSEN ```text [x] vaultwarden - - von bridge weg - - Host-Port weg - - Secret-Datei - - Traefik sauber aktiv + - aus bridge, in frontend_net + - Host-Port entfernt + - ADMIN_TOKEN_FILE aktiv + - Traefik aktiv [x] postgresql17 - - Port 5432 entfernen + - Port 5432 entfernt - nur backend_net - - Secret-Datei - - aus bridge raus + - POSTGRES_PASSWORD_FILE aktiv [x] diun - - Join zu frontend_net - - gotify via Container-Name + - frontend_net beigetreten + - gotify via Container-Name (http://gotify:80) erreichbar + - Token via Portainer ENV [x] mealie-postgres - aus frontend_net raus - - mealie_internal + - nur mealie_mealie_internal ``` ### Block C — Frontend-Stack finalisieren ```text -[ ] paperless-ngx +[x] gotify + - traefik.enable=true + - gotify.kaleschke.info + - Port entfernt + +[x] paperless-ngx - traefik.enable=true - paperless.kaleschke.info + - Port entfernt + - tls=true ergänzt + +[x] Paperless-AI + - traefik.enable=true + - aktiv + +[x] PortainerCE + - traefik.enable=true + - Middleware aktiv + - direkte Ports entfernt + +[x] Dozzle + - traefik.enable=true + - Middleware aktiv + - direkte Ports entfernt + +[x] dashdot + - traefik.enable=true + - Middleware aktiv + - direkte Ports entfernt + +[x] UptimeKuma + - traefik.enable=true + - uptime.kaleschke.info + - Port entfernt + - Middleware aktiv + +[x] filebrowser + - aus bridge → frontend_net + - traefik.enable=true + - files.kaleschke.info + - Port entfernt + - Middleware aktiv + +[x] scrutiny + - aus bridge → frontend_net + - traefik.enable=true + - scrutiny.kaleschke.info + - Ports entfernt + - Middleware aktiv + - als Git-Stack migriert + +[x] gitea + - traefik.enable=true + - git.kaleschke.info + - SSH-Port 222 bleibt (Ausnahme dokumentiert) + +[x] backrest + - traefik.docker.network=frontend_net korrigiert (war backend_net — Routing-Bug) + +[ ] luckyBackup + - aus bridge → frontend_net + - traefik.enable=true + - Middleware - Port entfernen -[ ] Paperless-AI - - traefik.enable=true - - paperless-ai.kaleschke.info - - Port entfernen - -[ ] PortainerCE - - traefik.enable=true - - portainer.kaleschke.info - - Middleware - - direkte Ports entfernen - -[ ] Dozzle - - traefik.enable=true - - dozzle.kaleschke.info - - Middleware - - direkte Ports entfernen - -[ ] dashdot - - traefik.enable=true - - dash.kaleschke.info - - Middleware - - direkte Ports entfernen - [ ] theme-park - nur wenn wirklich benötigt veröffentlichen + - direkte Ports 80/443 entfernen ``` ### Block D — Bridge-/Dockerman-Container in Compose ```text -[ ] vaultwarden -[ ] postgresql17 -[ ] mail-archiver -[ ] scrutiny -[ ] filebrowser +[x] vaultwarden ✅ +[x] postgresql17 ✅ +[x] mail-archiver ✅ +[x] scrutiny ✅ (Git-Stack) +[x] filebrowser ✅ [ ] luckyBackup [ ] Stash [ ] Tailscale-Docker @@ -443,31 +480,42 @@ labels: ### Block E — Secrets-Migration ```text -[ ] vaultwarden → ADMIN_TOKEN_FILE -[ ] postgresql17 → POSTGRES_PASSWORD_FILE -[ ] mail-archiver → Authentication__Password_FILE -[ ] mealie → POSTGRES_PASSWORD_FILE -[ ] mealie-postgres → POSTGRES_PASSWORD_FILE -[ ] gotify → Passwort rotieren + Secret-Datei -[ ] diun → GOTIFY token als Datei -[ ] paperless-ngx → PAPERLESS_DBPASS_FILE -[ ] code-server → HASHED_PASSWORD / Secret -[ ] immich_server → DB_PASSWORD rotieren + Datei -[ ] immich_postgres → POSTGRES_PASSWORD_FILE -[ ] scanopy-postgres → Passwort rotieren +[x] vaultwarden → ADMIN_TOKEN_FILE ✅ +[x] postgresql17 → POSTGRES_PASSWORD_FILE ✅ +[x] mail-archiver → Portainer ENV (${MAILARCHIVER_AUTH_PASSWORD}) ✅ +[x] mealie → Portainer ENV (kein _FILE-Support) ✅ +[x] mealie-postgres → Portainer ENV (kein _FILE-Support) ✅ +[x] gotify → GOTIFY_DEFAULTUSER_PASS_FILE ✅ +[x] diun → GOTIFY_TOKEN via Portainer ENV ✅ +[x] paperless-ngx → Portainer ENV (${PAPERLESS_DBPASS}) ✅ +[x] code-server → PASSWORD_FILE ✅ +[x] immich_server → Portainer ENV (${IMMICH_DB_PASSWORD}) ✅ +[x] immich_postgres → POSTGRES_PASSWORD_FILE ✅ +[ ] immich_redis → anonymes Volume → named volume +[ ] luckyBackup → Secrets prüfen ``` ### Block F — Feinschliff / Hardening ```text -[ ] backrest - - traefik.docker.network → frontend_net - - Mounts straffen - -[ ] Redis - - optional named volume +[ ] immich_default + - internal: true setzen (kurzer Downtime nötig) [ ] immich_redis + - anonymes Volume → named volume in Compose + +[ ] immich_server + - anonymes Volume prüfen und benennen + +[ ] backrest + - /mnt/user doppelt gemountet (einmal ro, einmal rw) + - rw-Mount auf konkrete Pfade einschränken + +[ ] filebrowser + - /mnt/user:/srv ist sehr breit + - auf /mnt/user/documents:/srv einschränken wenn möglich + +[ ] Redis - optional named volume [ ] netdata @@ -490,14 +538,17 @@ labels: | Container | Ausnahme | Begründung | |---|---|---| | `traefik` | Host-Ports 80/443 | zentraler Reverse Proxy | -| `Tailscale-Docker` | `host`, aktuell `privileged`, `TS_USERSPACE=true` | bestehender VPN-Zugang; Umstellung nur kontrolliert | +| `Tailscale-Docker` | `host`, aktuell `privileged` | bestehender VPN-Zugang; Umstellung nur kontrolliert | | `binhex-official-pihole` | `host` | DNS-/DHCP-naher Sonderfall | | `Plex-Media-Server` | `host` | Discovery / mDNS / Plex GDM | | `netdata` | `host` + zusätzliche Rechte | Host-Metriken | | `Glances` | `host` | Host-Metriken | | `netalertx` | `host` + Netzwerksicht | ARP / Netzwerkscan | -| `scanopy-daemon` | `host`, aktuell privilegiert | hardware-/systemnaher Sonderfall | +| `scrutiny` | `privileged: true` | SMART-Datenzugriff auf Laufwerke | | `PortainerCE` | Docker-Socket nicht dogmatisch `:ro` | Management-UI; Schreiboperationen können nötig sein | +| `gitea` | SSH-Port 222 direkt gebunden | Git-SSH-Zugang; kein HTTP-Proxy für SSH möglich | +| `ddns-updater` | bleibt in `frontend_net` statt `backend_net` | braucht Internetzugang für Cloudflare-API; `backend_net` ist `internal: true` | +| `mail-archiver` | `frontend_net` + `backend_net` | braucht Internetzugang für IMAP-Abruf (GMX, Gmail) und DB-Zugang | --- @@ -507,12 +558,15 @@ labels: Dieses Projekt wird **blockweise** umgesetzt, nicht wild containerweise. ### 11.2 Reihenfolge der Umsetzung -1. **Sprint 1:** Quick Wins + `vaultwarden` -2. **Sprint 2:** `postgresql17` + `diun/gotify` -3. **Sprint 3:** `mealie` / `mealie-postgres` + `mail-archiver` -4. **Sprint 4:** Frontend-Stack (`paperless`, `Portainer`, `Dozzle`, `dashdot`, etc.) -5. **Sprint 5:** Compose-Migration der Dockerman-Container -6. **Sprint 6:** Hardening / Secrets / Volumes / Sonderfälle + +| Sprint | Inhalt | Status | +|---|---|---| +| Sprint 1 | Quick Wins + `vaultwarden` | ✅ Abgeschlossen | +| Sprint 2 | `postgresql17` + `diun/gotify` | ✅ Abgeschlossen | +| Sprint 3 | `mealie` / `mealie-postgres` + `mail-archiver` | ✅ Abgeschlossen | +| Sprint 4 | Frontend-Stack (`paperless`, `Portainer`, `Dozzle`, `dashdot`, `scrutiny`, `filebrowser`, `gitea`, `UptimeKuma`) | 🔄 In Bearbeitung | +| Sprint 5 | Compose-Migration der Dockerman-Container (`luckyBackup`, `Stash`, `Tailscale`, `netdata`, `Plex`, `Pi-hole`) | ⏳ Offen | +| Sprint 6 | Hardening / Secrets / Volumes / Sonderfälle (`immich_default`, Volumes, Mounts) | ⏳ Offen | ### 11.3 Regel für jede Änderung Jeder Sprint folgt demselben Schema: @@ -528,7 +582,7 @@ Jeder Sprint folgt demselben Schema: ### 11.4 Source-of-Truth-Hierarchie 1. **Dieses Dokument** 2. Compose-Dateien -3. operative Checklisten / Excel +3. operative Checklisten 4. ad-hoc Notizen / Chat --- @@ -537,7 +591,7 @@ Jeder Sprint folgt demselben Schema: Wenn mit einer KI gearbeitet wird, gilt immer: -> **„Lies zuerst `HOMELAB_ARCHITECTURE_MASTER_V2.md`, dann beantworte meine Frage.“** +> **„Lies zuerst `HOMELAB_ARCHITECTURE_MASTER_V2.md`, dann beantworte meine Frage."** Damit ist sofort klar: - welche Netze Standard sind @@ -548,66 +602,84 @@ Damit ist sofort klar: - welche Ausnahmen bewusst dokumentiert sind --- -## ⚠️ Erweiterte Learnings (Portainer + GitOps) -### Secrets in Portainer Git-Stacks +## 13. Betriebserfahrungen und Entscheidungs-Log -Bei Deployments über Portainer mit Git-Repositories gilt: +### Secrets in Portainer Stacks -* Host-Pfade in `env_file` (z. B. `/mnt/...`) sind **nicht verfügbar** -* Portainer-Container hat keinen Zugriff auf diese Pfade +Bei Deployments über Portainer gilt: -#### Konsequenz +- Host-Pfade in `env_file` (z.B. `/mnt/...`) sind nicht verfügbar — Portainer-Container hat keinen Zugriff +- `env_file` ist für Secrets ungeeignet in diesem Setup -* `env_file` ist für Secrets ungeeignet in diesem Setup +**Standardlösung:** Portainer Environment Variables + `${VARIABLE_NAME}` in der Compose -#### Standardlösung +```yaml +POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} +``` -* Verwendung von **Portainer Environment Variables** -* Compose nutzt Variablen: - - * `POSTGRES_PASSWORD: ${VARIABLE_NAME}` - -👉 Ergebnis: - -* keine Secrets im Git -* funktionierender Git-Deployment-Flow +→ kein Secret im Git, funktionierender Deployment-Flow --- ### Umgang mit `_FILE` Variablen -Nicht alle Container unterstützen `_FILE`-basierte Secrets. +Nicht alle Container unterstützen `_FILE`-basierte Secrets: -#### Übersicht +| Container | `_FILE` Support | +|---|---| +| Vaultwarden | ✅ ja | +| PostgreSQL | ✅ ja | +| Gotify | ✅ ja (`GOTIFY_DEFAULTUSER_PASS_FILE`) | +| code-server | ✅ ja (`PASSWORD_FILE`) | +| Mealie | ❌ nein → Portainer ENV | +| diun | ❌ nein für Token → Portainer ENV | +| paperless-ngx | ❌ nein für DB-Pass → Portainer ENV | -* Vaultwarden → unterstützt `_FILE` -* PostgreSQL → unterstützt `_FILE` -* Mealie → unterstützt `_FILE` **nicht** +**Regel:** Wenn `_FILE` nicht unterstützt wird → Portainer Environment Variable -#### Regel +--- -* Wenn `_FILE` nicht unterstützt wird: - → Nutzung von Environment Variables über Portainer +### diun — Korrekte Konfiguration (v4.x) + +Wichtige Learnings aus der Migration: + +- `DIUN_NOTIFY_GOTIFY=true` existiert **nicht** — diese Variable gibt es in v4 nicht, führt zu `field not found` Fehler +- `DIUN_NOTIF_GOTIFY_TOKEN_FILE` wird **nicht** unterstützt — Token muss direkt gesetzt werden +- Gotify ist automatisch aktiv wenn `DIUN_NOTIF_GOTIFY_ENDPOINT` und `DIUN_NOTIF_GOTIFY_TOKEN` gesetzt sind +- `DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT=true` nötig damit alle Container überwacht werden (sonst nur Container mit `diun.enable=true` Label) + +**Korrekte Minimal-Konfiguration:** +```yaml +environment: + - DIUN_PROVIDERS_DOCKER=true + - DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT=true + - DIUN_NOTIF_GOTIFY_ENDPOINT=http://gotify:80 + - DIUN_NOTIF_GOTIFY_TOKEN=${GOTIFY_TOKEN} # via Portainer ENV +``` + +--- + +### ddns-updater — Netz-Ausnahme + +`ddns-updater` hat keine Web-UI, würde laut Schema in `backend_net` gehören — bleibt aber bewusst in `frontend_net`. + +**Begründung:** `backend_net` ist `internal: true` → kein Internetzugang. ddns-updater muss die Cloudflare-API erreichen. `frontend_net` ist hier die einzig funktionierende Option. + +→ Dokumentierte Ausnahme in Kapitel 10. --- ### Netzwerk-Standard für Apps mit Datenbanken -#### Architektur-Regel +- App → `frontend_net` + internes Netzwerk +- Datenbank → nur internes Netzwerk (`internal: true`) -* App → `frontend_net` + internes Netzwerk -* Datenbank → nur internes Netzwerk (`internal: true`) +**Beispiel (Mealie):** +- `mealie` → `frontend_net` + `mealie_mealie_internal` +- `mealie-postgres` → nur `mealie_mealie_internal` -#### Beispiel (Mealie) - -* `mealie` → `frontend_net` + `mealie_internal` -* `mealie-postgres` → nur `mealie_internal` - -👉 Vorteil: - -* Datenbank vollständig isoliert -* keine externe Erreichbarkeit möglich +→ Datenbank vollständig isoliert, keine externe Erreichbarkeit möglich --- @@ -615,54 +687,31 @@ Nicht alle Container unterstützen `_FILE`-basierte Secrets. Bei Änderungen an Datenbanken oder Core-Services: -#### Vorgehen - 1. Backup erstellen (`pg_dumpall`) 2. aktuellen Zustand sichern (`docker inspect`) -3. neue Compose in Git definieren +3. neue Compose in Git/Portainer definieren 4. Container stoppen & entfernen 5. neuen Stack deployen 6. Funktion prüfen (Logs + abhängige Services) -#### Grundsatz +→ Daten liegen im Volume und bleiben erhalten — Container sind austauschbar -* Daten liegen im Volume → bleiben erhalten -* Container sind austauschbar +--- -👉 Ziel: +### mail-archiver — Hybrid-Dienst -* risikoarme Migration ohne Datenverlust +`mail-archiver` benötigt beide Netze: -## Mail-Archiver Netzwerk-Design +- `backend_net` → Verbindung zu `postgresql17` +- `frontend_net` → IMAP-Abruf von externen Mailservern (GMX, Gmail) + DNS-Auflösung -Der mail-archiver Service benötigt Zugriff auf zwei unterschiedliche Netzwerkbereiche: - -### backend_net -- Verbindung zur PostgreSQL-Datenbank (postgresql17) -- rein interne Kommunikation - -### frontend_net -- erforderlich für externe Verbindungen: - - IMAP (z. B. GMX, Gmail) - - DNS-Auflösung - - Internetzugriff allgemein - -### Begründung -Ein ausschließlich internes Netzwerk (backend_net) ist für mail-archiver nicht ausreichend, -da der Service aktiv E-Mails von externen Mailservern abruft. - -Durch die zusätzliche Anbindung an frontend_net wird: -- externer Mail-Zugriff ermöglicht -- gleichzeitig bleibt die Datenbank isoliert im backend_net - -### Fazit -mail-archiver ist kein reiner Backend-Service, sondern ein hybrider Dienst: -→ benötigt sowohl interne als auch externe Netzwerkzugriffe +→ Kein reiner Backend-Dienst, sondern Hybrid mit externem Internetzugriff +--- ## Schlussformel Dieses Dokument ist keine lose Notiz, sondern das **operative Masterdokument** für die Docker- und Zugriffsarchitektur des Homelabs. **Zielbild in einem Satz:** -`frontend_net` für alle Web-UIs, `backend_net` für interne Backends, app-interne Netze nur wenn technisch nötig, Tailscale für Remote-Admin-Zugriff, Traefik als einziger Web-Einstieg, keine produktiven `bridge`-Container mehr. +`frontend_net` für alle Web-UIs und Dienste mit Internetbedarf, `backend_net` für interne Backends, app-interne Netze nur wenn technisch nötig, Tailscale für Remote-Admin-Zugriff, Traefik als einziger Web-Einstieg, keine produktiven `bridge`-Container mehr. \ No newline at end of file