Files
homelab-infra/HOMELAB_ARCHITECTURE_MASTER_V2.md
T

602 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HOMELAB_ARCHITECTURE — MASTER v2
> **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-28 | **Aktueller Sprint:** 5 (Compose-Migration Dockerman-Container) — Sprints 14 abgeschlossen
---
## Inhaltsverzeichnis
1. [Systemüberblick](#1-systemüberblick)
2. [Architektur-Prinzipien](#2-architektur-prinzipien)
3. [Finales Netzwerk-Zielbild](#3-finales-netzwerk-zielbild)
4. [Zugangsmodell: Traefik vs. Tailscale](#4-zugangsmodell-traefik-vs-tailscale)
5. [Globale Sicherheitsregeln](#5-globale-sicherheitsregeln)
6. [Einordnungsschema für neue Container](#6-einordnungsschema-für-neue-container)
7. [Container-Zielbild (vollständig)](#7-container-zielbild-vollständig)
8. [Traefik-Label-Standard](#8-traefik-label-standard)
9. [Migrationsstrategie (Blöcke AF)](#9-migrationsstrategie-blöcke-a-f)
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)
---
## 1. Systemüberblick
| Eigenschaft | Wert |
|---|---|
| Host-OS | Unraid |
| Hostname | Kallilabcore |
| Reverse Proxy | Traefik v3 (100% Docker-Labels, kein File-Provider) |
| VPN / Remote-Zugang | Tailscale (`Tailscale-Docker`, host-Netz) |
| DNS-Stack | AdGuard Home (host) → Unbound (`dns_net`) |
| Basis-Domain | `kaleschke.info` |
| TLS | Let's Encrypt via Cloudflare DNS Challenge |
| Certresolver | `le` |
| Compose-Standard | Komodo (GitOps, Stack aus Git) |
| Legacy | Portainer CE (in Ablösung durch Komodo, Sprint 5) |
| Homelab-Compose-Pfad | `/mnt/user/services/homelab/` |
| Secrets-Pfad | `/mnt/user/appdata/secrets/` |
| Grundsatz | Keine neuen Dockerman-Einzelcontainer |
---
## 2. Architektur-Prinzipien
### P1 — Traefik ist der einzige öffentliche HTTP(S)-Einstiegspunkt
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_mealie_internal`, `immich_default`, `dns_net`)
Es gibt **keine künstlichen globalen Zusatznetze** wie `admin_net`, `monitoring_net` oder `media_net`.
### P3 — Datenbanken gehören nie ins `frontend_net`
Postgres, Redis und ähnliche Dienste laufen ausschließlich in `backend_net` oder einem eigenen internen Compose-Netz.
### P4 — Admin-UIs sind nicht öffentlich
Komodo, Dozzle, filebrowser, scrutiny, UptimeKuma, dashdot, code-server, luckyBackup, Traefik-Dashboard, netdata, Glances und netalertx sind standardmäßig **Tailscale-only** oder hinter Traefik **mit zentraler Middleware** abgesichert.
### P5 — Compose-first
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 als Komodo/Portainer Environment Variables mit `${VARIABLE}` in der Compose.
Ziel: kein sensibles Secret mehr sichtbar in `docker inspect`.
### P7 — `restart: unless-stopped` ist Pflichtstandard
Jeder produktive Container nutzt `restart: unless-stopped`, außer eine Ausnahme ist dokumentiert.
### P8 — Least Privilege
- `security_opt: ["no-new-privileges:true"]` standardmäßig ergänzen
- `privileged: true` nur mit dokumentierter Begründung
- `read_only: true` und Non-Root nur nach getesteter Image-Kompatibilität
- Docker-Socket standardmäßig vorsichtig behandeln; **Komodo/PortainerCE sind dokumentierte Ausnahmen**
---
## 3. Finales Netzwerk-Zielbild
### 3.1 Netz-Logik
| Netzwerk | Typ | Zweck | Status |
|---|---|---|---|
| `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_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)
```text
Internet
traefik (80/443)
└── frontend_net
├── öffentliche Apps (vaultwarden, mealie, paperless, immich, gitea, gotify)
├── Admin-UIs mit Middleware (komodo, 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-ngx
App-interne Netze
├── mealie_mealie_internal (internal: true) ✅
└── immich_default (internal: true ausstehend) ⏳
Host-Sonderfälle
├── Tailscale-Docker
├── binhex-official-pihole
├── Plex-Media-Server
├── netdata
├── Glances
└── netalertx
```
### 3.3 Architekturentscheidung (final)
**Wir bleiben final bei wenigen Netzen.** Das ist bewusst näher an aktuellen Homelab-Traefik-Setups als ein künstlich übersegmentiertes Docker-Netzmodell. Trennung erfolgt primär über:
- Traefik
- Auth-/Security-Middlewares
- Tailscale
- kein direktes Port-Publishing
- app-interne Netze
- Host-Sonderfälle nur mit Begründung
---
## 4. Zugangsmodell: Traefik vs. Tailscale
### 4.1 Öffentlich über Traefik
Diese Dienste dürfen final über echte `*.kaleschke.info`-Domains erreichbar sein:
- `homepage`
- `vaultwarden`
- `mealie`
- `paperless-ngx`
- `gotify`
- `gitea` (Web)
- `immich_server`
- `mail-archiver`
- `backrest`
### 4.2 Nicht öffentlich / nur Tailscale oder Traefik + Middleware
Diese Dienste sind **keine Public Apps**:
- `Komodo` (Stack-Manager)
- `Dozzle`
- `UptimeKuma`
- `dashdot`
- `filebrowser`
- `scrutiny`
- `luckyBackup`
- `code-server`
- `Traefik-Dashboard`
- `netdata`
- `Glances`
- `netalertx`
### 4.3 Regel
Wenn ein Dienst im `frontend_net` hängt, heißt das **nicht automatisch öffentlich**. Admin-Dienste dürfen im `frontend_net` liegen, wenn:
- Traefik sie routet
- zentrale Middleware aktiv ist
- keine direkten Host-Ports bestehen
- Zugriff zusätzlich durch Tailscale bzw. Auth begrenzt ist
---
## 5. Globale Sicherheitsregeln
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` oder Komodo/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`
8. Placeholder-Domains (`yourdomain.tld`) sind verboten
9. `privileged: true` nur mit Begründung
10. Volume-Mounts so klein und so read-only wie möglich
11. Neue Dienste nur via Compose Manager / YAML
12. Änderungen immer gegen dieses Dokument prüfen
---
## 6. Einordnungsschema für neue Container
### Schritt 1 — Hat der Dienst eine Web-UI?
- **Ja** → `frontend_net`
- **Nein** → weiter zu Schritt 2
### 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 4 — Ist es eine Datenbank oder ein Cache?
- **Ja** → niemals `frontend_net`, nur `backend_net` oder internes Compose-Netz
### 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 6 — Braucht der Dienst Host-/Discovery-/L2-Sicht?
- **Ja** → `host` nur mit dokumentierter Begründung
- **Nein** → kein `host`
### 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
---
## 7. Container-Zielbild (vollständig)
Legende Status:
- `✅` = umgesetzt
- `⏳` = noch zu migrieren / zu korrigieren
- `❌` = entfernt
### 7.1 Infrastruktur / Core
| Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte |
|---|---|---|---|---|---|
| `traefik` | ✅ | `frontend_net`, `backend_net` | öffentlich | zentraler Ingress, 80/443 direkt, 100% Docker-Labels | — |
| `homepage` | ✅ | `frontend_net` | Traefik | öffentliche Startseite | — |
| `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 | `traefik.docker.network=frontend_net` korrigiert | Breite Mounts straffen (Block F) |
### 7.2 Sicherheit / Identity
| Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte |
|---|---|---|---|---|---|
| `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 | kein Host-Port, `POSTGRES_PASSWORD_FILE` | — |
| `Redis` | ✅ | `backend_net` | intern | intern-only Cache | optional named volume statt anonym |
| `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 | 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 | — |
### 7.5 Admin / Operations
| Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte |
|---|---|---|---|---|---|
| `komodo` | ✅ | `frontend_net` | Traefik + Middleware | primärer GitOps-Stack-Manager | — |
| `code-server` | ✅ | `frontend_net` | Traefik + Middleware | `PASSWORD_FILE` aktiv | — |
| `PortainerCE` | ⚠️ Legacy | `frontend_net` | Traefik + Middleware | wird durch Komodo abgelöst | abschalten in Sprint 5 |
| `Dozzle` | ✅ | `frontend_net` | Traefik + Middleware | keine Host-Ports | — |
| `filebrowser` | ✅ | `frontend_net` | Traefik + Middleware | aktiv via `files.kaleschke.info` | Breite Mounts einschränken (Block F) |
| `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 | 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 | — |
| `beszel` | ✅ | `frontend_net` | Traefik + Middleware | System-/Host-Monitoring | — |
### 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 |
| `diun` | 2026-03-28 | Image-Update-Monitoring nicht mehr via diun; Stack deinstalliert, Netz bereinigt |
---
## 8. Traefik-Label-Standard
Jeder Dienst mit Traefik-Routing nutzt dieses Muster:
```yaml
labels:
- traefik.enable=true
- traefik.docker.network=frontend_net
- traefik.http.routers.<name>.rule=Host(`<subdomain>.kaleschke.info`)
- traefik.http.routers.<name>.entrypoints=websecure
- traefik.http.routers.<name>.tls=true
- traefik.http.routers.<name>.tls.certresolver=le
- traefik.http.services.<name>.loadbalancer.server.port=<interner-port>
```
### Zusatz für Admin-Dienste
```yaml
- traefik.http.routers.<name>.middlewares=dashboard-auth@file,secure-headers@file
```
### Regeln
- `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
- **File-Provider nur noch für:** `middlewares.yml` (Auth + Headers) — keine Service-Routen mehr via File-Provider
---
## 9. Migrationsstrategie (Blöcke AF)
**Letzte Aktualisierung:** 2026-03-28
### Block A — Quick Wins ✅ ABGESCHLOSSEN
```text
[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 ✅ ABGESCHLOSSEN
```text
[x] vaultwarden - aus bridge, in frontend_net - Host-Port entfernt - ADMIN_TOKEN_FILE aktiv - Traefik aktiv
[x] postgresql17 - Port 5432 entfernt - nur backend_net - POSTGRES_PASSWORD_FILE aktiv
[x] diun - frontend_net beigetreten - gotify via Container-Name (http://gotify:80) erreichbar - Token via Portainer ENV
[x] mealie-postgres - aus frontend_net raus - nur mealie_mealie_internal
```
### Block C — Frontend-Stack finalisieren ✅ WEITGEHEND ABGESCHLOSSEN
```text
[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)
[x] Traefik File-Provider bereinigt - immich.yml, gitea.yml, mealie.yml, scrutiny.yml, vaultwarden.yml.bak gelöscht
[x] immich Bad Gateway behoben - Traefik nutzt jetzt immich@docker statt immich@file
[ ] luckyBackup - aus bridge → frontend_net - traefik.enable=true - Middleware - Port entfernen
[ ] theme-park - nur wenn wirklich benötigt veröffentlichen - direkte Ports 80/443 entfernen
```
### Block D — Bridge-/Dockerman-Container in Compose
```text
[x] vaultwarden ✅
[x] postgresql17 ✅
[x] mail-archiver ✅
[x] scrutiny ✅ (Git-Stack)
[x] filebrowser ✅
[ ] luckyBackup
[ ] Stash
[ ] Tailscale-Docker
[ ] netdata
[ ] Plex-Media-Server
[ ] binhex-official-pihole
```
### Block E — Secrets-Migration
```text
[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 ✅ (Container entfernt)
[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
[ ] 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 - leere CLAIM-Vars entfernen
[ ] scrutiny - später prüfen, ob privileged reduziert werden kann
[ ] Tailscale-Docker - später prüfen, ob TS_USERSPACE/privileged bereinigt werden kann
[ ] Pi-hole - zuletzt konsolidieren, nicht als Erstprojekt
[ ] PortainerCE - abschalten nach vollständiger Komodo-Übernahme
```
---
## 10. Bekannte Ausnahmen und Begründungen
| Container | Ausnahme | Begründung |
|---|---|---|
| `traefik` | Host-Ports 80/443 | zentraler Reverse Proxy |
| `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 |
| `scrutiny` | `privileged: true` | SMART-Datenzugriff auf Laufwerke |
| `Komodo` | Docker-Socket Zugriff | Management-UI; Stack-Deployments benötigen Socket |
| `PortainerCE` | Docker-Socket nicht dogmatisch `:ro` | Legacy-UI; wird durch Komodo abgelöst |
| `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 |
---
## 11. Projektorganisation und Arbeitsmodus
### 11.1 Unser Arbeitsprinzip
Dieses Projekt wird **blockweise** umgesetzt, nicht wild containerweise.
### 11.2 Reihenfolge der Umsetzung
| 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`) + Traefik File-Provider Bereinigung + Komodo Einführung | ✅ Abgeschlossen |
| Sprint 5 | Compose-Migration Dockerman-Container (`luckyBackup`, `Stash`, `Tailscale`, `netdata`, `Plex`, `Pi-hole`) + Portainer abschalten | 🔄 In Bearbeitung |
| Sprint 6 | Hardening / Secrets / Volumes / Sonderfälle (`immich_default`, Volumes, Mounts) | ⏳ Offen |
### 11.3 Regel für jede Änderung
Jeder Sprint folgt demselben Schema:
1. Zielbild in diesem Dokument prüfen
2. nur den aktuellen Block anfassen
3. Compose-Datei ändern
4. deployen
5. testen
6. dokumentieren / abhaken
7. erst dann nächster Schritt
### 11.4 Source-of-Truth-Hierarchie
1. **Dieses Dokument**
2. Compose-Dateien
3. operative Checklisten
4. ad-hoc Notizen / Chat
---
## 12. Nutzung mit KI / Kontext-Regel
Wenn mit einer KI gearbeitet wird, gilt immer:
> **„Lies zuerst `HOMELAB_ARCHITECTURE_MASTER_V2.md`, dann beantworte meine Frage."**
Damit ist sofort klar:
- welche Netze Standard sind
- welche Container wohin gehören
- welche Dienste öffentlich sein dürfen
- welche Dienste nur intern/VPN-only sind
- welche Migrationen noch offen sind
- welche Ausnahmen bewusst dokumentiert sind
---
## 13. Betriebserfahrungen und Entscheidungs-Log
### Traefik — Wechsel zu reinen Docker-Labels (2026-03-28)
Die statischen File-Provider-Konfigurationen in `/mnt/user/appdata/traefik/dynamic/` wurden vollständig bereinigt:
- **Gelöscht:** `immich.yml`, `gitea.yml`, `mealie.yml`, `scrutiny.yml`, `vaultwarden.yml.bak`
- **Verbleibend (notwendig):** `middlewares.yml`, `tls.yml`, `usersfile`
**Hintergrund:** Die alten File-Provider-Configs haben `@file`-Routen mit `@docker`-Routen konkurrieren lassen. In Traefik v3 gewinnt der File-Provider und hat z.B. Immich auf die falsche IP geroutet (`192.168.178.58:2283`, Port nicht am Host exponiert → Bad Gateway). Nach Löschung der statischen Configs läuft Traefik ausschließlich auf Docker-Labels. Immich, Gitea, Mealie und Scrutiny wurden damit sofort korrekt über ihre Docker-Labels geroutet.
**Regel:** Neue Dienste ausschließlich via Docker Compose Labels konfigurieren. Keine neuen `.yml`-Dateien im `dynamic/`-Verzeichnis für Service-Routen anlegen.
### Komodo — Ablösung von Portainer als Stack-Manager (2026-03-28)
Komodo ist nun der primäre GitOps-Stack-Manager:
- **Komodo Core** läuft als Docker-Stack (`ops/komodo/docker-compose.yml`)
- **Komodo Periphery** läuft auf dem Unraid-Host für direktes Server-Management
- Stacks werden via Git (Gitea) synchronisiert und über Komodo deployed
- Portainer CE läuft noch als Legacy-UI und wird in Sprint 5 abgeschaltet
**Vorteil gegenüber Portainer:** Sauberer GitOps-Flow ohne Web-Editor; alle Stack-Änderungen laufen über Git.
### Secrets in Portainer / Komodo Stacks
Bei Deployments über Komodo/Portainer gilt:
- Host-Pfade in `env_file` (z.B. `/mnt/...`) sind nicht verfügbar — Container hat keinen Zugriff
- `env_file` ist für Secrets ungeeignet in diesem Setup
**Standardlösung:** Stack Environment Variables + `${VARIABLE_NAME}` in der Compose
```yaml
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
```
→ kein Secret im Git, funktionierender Deployment-Flow
### Umgang mit `_FILE` Variablen
Nicht alle Container unterstützen `_FILE`-basierte Secrets:
| Container | `_FILE` Support |
|---|---|
| Vaultwarden | ✅ ja |
| PostgreSQL | ✅ ja |
| Gotify | ✅ ja (`GOTIFY_DEFAULTUSER_PASS_FILE`) |
| code-server | ✅ ja (`PASSWORD_FILE`) |
| Mealie | ❌ nein → Stack ENV |
| diun | ❌ nein für Token → Stack ENV (Container entfernt) |
| paperless-ngx | ❌ nein für DB-Pass → Stack ENV |
**Regel:** Wenn `_FILE` nicht unterstützt wird → Stack Environment Variable
### diun — Entfernung (2026-03-28)
`diun` (Docker Image Update Notifier) wurde deinstalliert:
- Stack gelöscht
- Orphan-Netzwerk `diun_diun_default` bereinigt
- Repo-Eintrag `infra/diun/` aus Git entfernt
**Hintergrund:** Update-Monitoring kann über Komodo's eingebaute Update-Notifications abgedeckt werden.
### 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
- 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`
→ Datenbank vollständig isoliert, keine externe Erreichbarkeit möglich
### Migrations-Standard für kritische Services
Bei Änderungen an Datenbanken oder Core-Services:
1. Backup erstellen (`pg_dumpall`)
2. aktuellen Zustand sichern (`docker inspect`)
3. neue Compose in Git/Komodo definieren
4. Container stoppen & entfernen
5. neuen Stack deployen
6. Funktion prüfen (Logs + abhängige Services)
→ Daten liegen im Volume und bleiben erhalten — Container sind austauschbar
### mail-archiver — Hybrid-Dienst
`mail-archiver` benötigt beide Netze:
- `backend_net` → Verbindung zu `postgresql17`
- `frontend_net` → IMAP-Abruf von externen Mailservern (GMX, Gmail) + DNS-Auflösung
→ 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 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 (100% Docker-Labels), Komodo als GitOps-Stack-Manager, keine produktiven `bridge`-Container mehr.