HOMELAB_ARCHITECTURE_MASTER_V2.md aktualisiert

This commit is contained in:
2026-03-28 16:28:07 +00:00
parent d46ab8d631
commit e3e9e87aef
+124 -240
View File
@@ -1,9 +1,9 @@
# HOMELAB_ARCHITECTURE — MASTER v2 # HOMELAB_ARCHITECTURE — MASTER v2
> **Single Source of Truth** für Docker-Netzwerkarchitektur, Sicherheitsregeln, Zielbild und Migration des Kallilabcore-Homelabs. > **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. > **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 13 abgeschlossen **Stand:** 2026-03-28 | **Aktueller Sprint:** 5 (Compose-Migration Dockerman-Container) — Sprints 14 abgeschlossen
--- ---
@@ -30,14 +30,14 @@
|---|---| |---|---|
| Host-OS | Unraid | | Host-OS | Unraid |
| Hostname | Kallilabcore | | Hostname | Kallilabcore |
| Reverse Proxy | Traefik v3 | | Reverse Proxy | Traefik v3 (100% Docker-Labels, kein File-Provider) |
| VPN / Remote-Zugang | Tailscale (`Tailscale-Docker`, host-Netz) | | VPN / Remote-Zugang | Tailscale (`Tailscale-Docker`, host-Netz) |
| DNS-Stack | Pi-hole (host) → Unbound (`dns_net`) | | DNS-Stack | AdGuard Home (host) → Unbound (`dns_net`) |
| Basis-Domain | `kaleschke.info` | | Basis-Domain | `kaleschke.info` |
| TLS | Let's Encrypt via Cloudflare DNS Challenge | | TLS | Let's Encrypt via Cloudflare DNS Challenge |
| Certresolver | `le` | | Certresolver | `le` |
| Compose-Standard | Portainer Stacks (Web-Editor oder Git) | | Compose-Standard | Komodo (GitOps, Stack aus Git) |
| Zusatz-Tool | Portainer als Verwaltungs-UI | | Legacy | Portainer CE (in Ablösung durch Komodo, Sprint 5) |
| Homelab-Compose-Pfad | `/mnt/user/services/homelab/` | | Homelab-Compose-Pfad | `/mnt/user/services/homelab/` |
| Secrets-Pfad | `/mnt/user/appdata/secrets/` | | Secrets-Pfad | `/mnt/user/appdata/secrets/` |
| Grundsatz | Keine neuen Dockerman-Einzelcontainer | | Grundsatz | Keine neuen Dockerman-Einzelcontainer |
@@ -47,8 +47,7 @@
## 2. Architektur-Prinzipien ## 2. Architektur-Prinzipien
### P1 — Traefik ist der einzige öffentliche HTTP(S)-Einstiegspunkt ### P1 — Traefik ist der einzige öffentliche HTTP(S)-Einstiegspunkt
Kein Webdienst veröffentlicht finale direkte Host-Ports außer `traefik` selbst. 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`.
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 ### 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: Die Quellenlage für Homelabs mit Traefik spricht für ein simples 2-Netz-Modell:
@@ -62,14 +61,14 @@ Es gibt **keine künstlichen globalen Zusatznetze** wie `admin_net`, `monitoring
Postgres, Redis und ähnliche Dienste laufen ausschließlich in `backend_net` oder einem eigenen internen Compose-Netz. Postgres, Redis und ähnliche Dienste laufen ausschließlich in `backend_net` oder einem eigenen internen Compose-Netz.
### P4 — Admin-UIs sind nicht öffentlich ### P4 — Admin-UIs sind nicht öffentlich
Portainer, 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. 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 ### P5 — Compose-first
Alle produktiven Container werden als Compose verwaltet. Alle produktiven Container werden als Compose verwaltet.
Bestehende Dockerman-/Ad-hoc-Container werden schrittweise migriert. Bestehende Dockerman-/Ad-hoc-Container werden schrittweise migriert.
### P6 — Secrets nie im Klartext ### P6 — Secrets nie im Klartext
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. 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`. Ziel: kein sensibles Secret mehr sichtbar in `docker inspect`.
### P7 — `restart: unless-stopped` ist Pflichtstandard ### P7 — `restart: unless-stopped` ist Pflichtstandard
@@ -79,7 +78,7 @@ Jeder produktive Container nutzt `restart: unless-stopped`, außer eine Ausnahme
- `security_opt: ["no-new-privileges:true"]` standardmäßig ergänzen - `security_opt: ["no-new-privileges:true"]` standardmäßig ergänzen
- `privileged: true` nur mit dokumentierter Begründung - `privileged: true` nur mit dokumentierter Begründung
- `read_only: true` und Non-Root nur nach getesteter Image-Kompatibilität - `read_only: true` und Non-Root nur nach getesteter Image-Kompatibilität
- Docker-Socket standardmäßig vorsichtig behandeln; **PortainerCE ist eine dokumentierte Ausnahme** - Docker-Socket standardmäßig vorsichtig behandeln; **Komodo/PortainerCE sind dokumentierte Ausnahmen**
--- ---
@@ -100,38 +99,36 @@ Jeder produktive Container nutzt `restart: unless-stopped`, außer eine Ausnahme
```text ```text
Internet Internet
traefik (80/443) traefik (80/443)
└── frontend_net └── frontend_net
├── öffentliche Apps (vaultwarden, mealie, paperless, immich, gitea, gotify) ├── öffentliche Apps (vaultwarden, mealie, paperless, immich, gitea, gotify)
├── Admin-UIs mit Middleware (portainer, dozzle, uptime-kuma, dashdot, filebrowser, scrutiny, code-server) ├── Admin-UIs mit Middleware (komodo, dozzle, uptime-kuma, dashdot, filebrowser, scrutiny, code-server)
└── Hybrid-Dienste mit Internetbedarf (mail-archiver, ddns-updater) └── Hybrid-Dienste mit Internetbedarf (mail-archiver, ddns-updater)
backend_net (internal: true) backend_net (internal: true)
├── postgresql17 ├── postgresql17
├── Redis ├── Redis
├── mail-archiver ├── mail-archiver
└── paperless-ngx └── paperless-ngx
App-interne Netze App-interne Netze
├── mealie_mealie_internal (internal: true) ✅ ├── mealie_mealie_internal (internal: true) ✅
└── immich_default (internal: true ausstehend) ⏳ └── immich_default (internal: true ausstehend) ⏳
Host-Sonderfälle Host-Sonderfälle
├── Tailscale-Docker ├── Tailscale-Docker
├── binhex-official-pihole ├── binhex-official-pihole
├── Plex-Media-Server ├── Plex-Media-Server
├── netdata ├── netdata
├── Glances ├── Glances
└── netalertx └── netalertx
``` ```
### 3.3 Architekturentscheidung (final) ### 3.3 Architekturentscheidung (final)
**Wir bleiben final bei wenigen Netzen.** **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:
Das ist bewusst näher an aktuellen Homelab-Traefik-Setups als ein künstlich übersegmentiertes Docker-Netzmodell.
Trennung erfolgt primär über:
- Traefik - Traefik
- Auth-/Security-Middlewares - Auth-/Security-Middlewares
- Tailscale - Tailscale
@@ -145,7 +142,6 @@ Trennung erfolgt primär über:
### 4.1 Öffentlich über Traefik ### 4.1 Öffentlich über Traefik
Diese Dienste dürfen final über echte `*.kaleschke.info`-Domains erreichbar sein: Diese Dienste dürfen final über echte `*.kaleschke.info`-Domains erreichbar sein:
- `homepage` - `homepage`
- `vaultwarden` - `vaultwarden`
- `mealie` - `mealie`
@@ -158,8 +154,7 @@ Diese Dienste dürfen final über echte `*.kaleschke.info`-Domains erreichbar se
### 4.2 Nicht öffentlich / nur Tailscale oder Traefik + Middleware ### 4.2 Nicht öffentlich / nur Tailscale oder Traefik + Middleware
Diese Dienste sind **keine Public Apps**: Diese Dienste sind **keine Public Apps**:
- `Komodo` (Stack-Manager)
- `PortainerCE`
- `Dozzle` - `Dozzle`
- `UptimeKuma` - `UptimeKuma`
- `dashdot` - `dashdot`
@@ -173,8 +168,7 @@ Diese Dienste sind **keine Public Apps**:
- `netalertx` - `netalertx`
### 4.3 Regel ### 4.3 Regel
Wenn ein Dienst im `frontend_net` hängt, heißt das **nicht automatisch öffentlich**. Wenn ein Dienst im `frontend_net` hängt, heißt das **nicht automatisch öffentlich**. Admin-Dienste dürfen im `frontend_net` liegen, wenn:
Admin-Dienste dürfen im `frontend_net` liegen, wenn:
- Traefik sie routet - Traefik sie routet
- zentrale Middleware aktiv ist - zentrale Middleware aktiv ist
- keine direkten Host-Ports bestehen - keine direkten Host-Ports bestehen
@@ -187,7 +181,7 @@ Admin-Dienste dürfen im `frontend_net` liegen, wenn:
1. Keine produktiven Dienste im Docker-Default-`bridge` 1. Keine produktiven Dienste im Docker-Default-`bridge`
2. Keine direkten Host-Ports für Web-UIs außer dokumentierte Ausnahmen 2. Keine direkten Host-Ports für Web-UIs außer dokumentierte Ausnahmen
3. `restart: unless-stopped` als Standard 3. `restart: unless-stopped` als Standard
4. Secrets als Datei / `_FILE` oder Portainer Environment Variables mit `${VAR}` 4. Secrets als Datei / `_FILE` oder Komodo/Portainer Environment Variables mit `${VAR}`
5. `no-new-privileges:true` ergänzen, wo praktikabel 5. `no-new-privileges:true` ergänzen, wo praktikabel
6. `traefik.docker.network=frontend_net` immer explizit setzen 6. `traefik.docker.network=frontend_net` immer explizit setzen
7. Admin-Dienste immer mit `dashboard-auth@file,secure-headers@file` 7. Admin-Dienste immer mit `dashboard-auth@file,secure-headers@file`
@@ -244,19 +238,19 @@ Admin-Dienste dürfen im `frontend_net` liegen, wenn:
Legende Status: Legende Status:
- `✅` = umgesetzt - `✅` = umgesetzt
- `⏳` = noch zu migrieren / zu korrigieren - `⏳` = noch zu migrieren / zu korrigieren
- `❌` = entfernt
### 7.1 Infrastruktur / Core ### 7.1 Infrastruktur / Core
| Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte | | Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| `traefik` | ✅ | `frontend_net`, `backend_net` | öffentlich | zentraler Ingress, 80/443 direkt | Middleware-File-Provider sauber pflegen | | `traefik` | ✅ | `frontend_net`, `backend_net` | öffentlich | zentraler Ingress, 80/443 direkt, 100% Docker-Labels | — |
| `homepage` | ✅ | `frontend_net` | Traefik | öffentliche Startseite | — | | `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` | | `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 | | `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 | — | | `binhex-official-pihole` | ✅ | `host` | LAN DNS / intern | Host-Sonderfall, `restart: unless-stopped` gesetzt | — |
| `unbound` | ✅ | `dns_net` | intern | Resolver bleibt isoliert | — | | `unbound` | ✅ | `dns_net` | intern | Resolver bleibt isoliert | — |
| `backrest` | ✅ | `frontend_net`, `backend_net` | Traefik | `traefik.docker.network=frontend_net` korrigiert | Breite Mounts straffen (Block F) | | `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 ### 7.2 Sicherheit / Identity
@@ -290,11 +284,11 @@ Legende Status:
| Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte | | 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 | — | | `code-server` | ✅ | `frontend_net` | Traefik + Middleware | `PASSWORD_FILE` aktiv | — |
| `PortainerCE` | | `frontend_net` | Traefik + Middleware | keine Host-Ports | — | | `PortainerCE` | ⚠️ Legacy | `frontend_net` | Traefik + Middleware | wird durch Komodo abgelöst | abschalten in Sprint 5 |
| `Dozzle` | ✅ | `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) | | `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 | — | | `mail-archiver` | ✅ | `frontend_net`, `backend_net` | Traefik | aktiv via `mail.kaleschke.info`, kein Host-Port | — |
### 7.6 Monitoring / Status ### 7.6 Monitoring / Status
@@ -307,6 +301,7 @@ Legende Status:
| `netdata` | ✅ | `host` | Tailscale-only | Host-Metriken | leere CLAIM-Vars entfernen | | `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 | | `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 | — | | `netalertx` | ✅ | `host` | Tailscale-only | Host-/Netzsicht | — |
| `beszel` | ✅ | `frontend_net` | Traefik + Middleware | System-/Host-Monitoring | — |
### 7.7 Entfernte Container ### 7.7 Entfernte Container
@@ -315,6 +310,7 @@ Legende Status:
| `scanopy-server` | 2026-03-26 | nicht aktiv genutzt, durch paperless-ngx ersetzt | | `scanopy-server` | 2026-03-26 | nicht aktiv genutzt, durch paperless-ngx ersetzt |
| `scanopy-postgres` | 2026-03-26 | zusammen mit scanopy entfernt | | `scanopy-postgres` | 2026-03-26 | zusammen mit scanopy entfernt |
| `scanopy-daemon` | 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 |
--- ---
@@ -345,15 +341,15 @@ labels:
- `tls=true` immer explizit setzen — ohne diese Zeile kein TLS - `tls=true` immer explizit setzen — ohne diese Zeile kein TLS
- wenn Traefik aktiv ist, werden direkte Host-Ports entfernt - wenn Traefik aktiv ist, werden direkte Host-Ports entfernt
- Admin-Dienste niemals ohne Middleware veröffentlichen - 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) ## 9. Migrationsstrategie (Blöcke AF)
**Letzte Aktualisierung:** 2026-03-26 **Letzte Aktualisierung:** 2026-03-28
### Block A — Quick Wins ✅ ABGESCHLOSSEN ### Block A — Quick Wins ✅ ABGESCHLOSSEN
```text ```text
[x] restart: unless-stopped für alle Container gesetzt [x] restart: unless-stopped für alle Container gesetzt
[x] vaultwarden ADMIN_TOKEN-Doppelpräfix korrigiert [x] vaultwarden ADMIN_TOKEN-Doppelpräfix korrigiert
@@ -365,104 +361,33 @@ labels:
``` ```
### Block B — Kritische Kernmigrationen ✅ ABGESCHLOSSEN ### Block B — Kritische Kernmigrationen ✅ ABGESCHLOSSEN
```text ```text
[x] vaultwarden [x] vaultwarden - aus bridge, in frontend_net - Host-Port entfernt - ADMIN_TOKEN_FILE aktiv - Traefik aktiv
- aus bridge, in frontend_net [x] postgresql17 - Port 5432 entfernt - nur backend_net - POSTGRES_PASSWORD_FILE aktiv
- Host-Port entfernt [x] diun - frontend_net beigetreten - gotify via Container-Name (http://gotify:80) erreichbar - Token via Portainer ENV
- ADMIN_TOKEN_FILE aktiv [x] mealie-postgres - aus frontend_net raus - nur mealie_mealie_internal
- 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 ### Block C — Frontend-Stack finalisieren ✅ WEITGEHEND ABGESCHLOSSEN
```text ```text
[x] gotify [x] gotify - traefik.enable=true - gotify.kaleschke.info - Port entfernt
- traefik.enable=true [x] paperless-ngx - traefik.enable=true - paperless.kaleschke.info - Port entfernt - tls=true ergänzt
- gotify.kaleschke.info [x] Paperless-AI - traefik.enable=true - aktiv
- Port entfernt [x] PortainerCE - traefik.enable=true - Middleware aktiv - direkte Ports entfernt
[x] Dozzle - traefik.enable=true - Middleware aktiv - direkte Ports entfernt
[x] paperless-ngx [x] dashdot - traefik.enable=true - Middleware aktiv - direkte Ports entfernt
- traefik.enable=true [x] UptimeKuma - traefik.enable=true - uptime.kaleschke.info - Port entfernt - Middleware aktiv
- paperless.kaleschke.info [x] filebrowser - aus bridge → frontend_net - traefik.enable=true - files.kaleschke.info - Port entfernt - Middleware aktiv
- Port entfernt [x] scrutiny - aus bridge → frontend_net - traefik.enable=true - scrutiny.kaleschke.info - Ports entfernt - Middleware aktiv - als Git-Stack migriert
- tls=true ergänzt [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] Paperless-AI [x] Traefik File-Provider bereinigt - immich.yml, gitea.yml, mealie.yml, scrutiny.yml, vaultwarden.yml.bak gelöscht
- traefik.enable=true [x] immich Bad Gateway behoben - Traefik nutzt jetzt immich@docker statt immich@file
- aktiv [ ] 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
[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
[ ] theme-park
- nur wenn wirklich benötigt veröffentlichen
- direkte Ports 80/443 entfernen
``` ```
### Block D — Bridge-/Dockerman-Container in Compose ### Block D — Bridge-/Dockerman-Container in Compose
```text ```text
[x] vaultwarden ✅ [x] vaultwarden ✅
[x] postgresql17 ✅ [x] postgresql17 ✅
@@ -478,57 +403,35 @@ labels:
``` ```
### Block E — Secrets-Migration ### Block E — Secrets-Migration
```text ```text
[x] vaultwarden → ADMIN_TOKEN_FILE ✅ [x] vaultwarden → ADMIN_TOKEN_FILE ✅
[x] postgresql17 → POSTGRES_PASSWORD_FILE ✅ [x] postgresql17 → POSTGRES_PASSWORD_FILE ✅
[x] mail-archiver → Portainer ENV (${MAILARCHIVER_AUTH_PASSWORD}) ✅ [x] mail-archiver → Portainer ENV (${MAILARCHIVER_AUTH_PASSWORD}) ✅
[x] mealie → Portainer ENV (kein _FILE-Support) ✅ [x] mealie → Portainer ENV (kein _FILE-Support) ✅
[x] mealie-postgres → Portainer ENV (kein _FILE-Support) ✅ [x] mealie-postgres → Portainer ENV (kein _FILE-Support) ✅
[x] gotify → GOTIFY_DEFAULTUSER_PASS_FILE ✅ [x] gotify → GOTIFY_DEFAULTUSER_PASS_FILE ✅
[x] diun → GOTIFY_TOKEN via Portainer ENV ✅ [x] diun → GOTIFY_TOKEN via Portainer ENV ✅ (Container entfernt)
[x] paperless-ngx → Portainer ENV (${PAPERLESS_DBPASS}) ✅ [x] paperless-ngx → Portainer ENV (${PAPERLESS_DBPASS}) ✅
[x] code-server → PASSWORD_FILE ✅ [x] code-server → PASSWORD_FILE ✅
[x] immich_server → Portainer ENV (${IMMICH_DB_PASSWORD}) ✅ [x] immich_server → Portainer ENV (${IMMICH_DB_PASSWORD}) ✅
[x] immich_postgres → POSTGRES_PASSWORD_FILE ✅ [x] immich_postgres → POSTGRES_PASSWORD_FILE ✅
[ ] immich_redis → anonymes Volume → named volume [ ] immich_redis → anonymes Volume → named volume
[ ] luckyBackup → Secrets prüfen [ ] luckyBackup → Secrets prüfen
``` ```
### Block F — Feinschliff / Hardening ### Block F — Feinschliff / Hardening
```text ```text
[ ] immich_default [ ] immich_default - internal: true setzen (kurzer Downtime nötig)
- internal: true setzen (kurzer Downtime nötig) [ ] immich_redis - anonymes Volume → named volume in Compose
[ ] immich_server - anonymes Volume prüfen und benennen
[ ] immich_redis [ ] backrest - /mnt/user doppelt gemountet (einmal ro, einmal rw) - rw-Mount auf konkrete Pfade einschränken
- anonymes Volume → named volume in Compose [ ] filebrowser - /mnt/user:/srv ist sehr breit - auf /mnt/user/documents:/srv einschränken wenn möglich
[ ] Redis - optional named volume
[ ] immich_server [ ] netdata - leere CLAIM-Vars entfernen
- anonymes Volume prüfen und benennen [ ] scrutiny - später prüfen, ob privileged reduziert werden kann
[ ] Tailscale-Docker - später prüfen, ob TS_USERSPACE/privileged bereinigt werden kann
[ ] backrest [ ] Pi-hole - zuletzt konsolidieren, nicht als Erstprojekt
- /mnt/user doppelt gemountet (einmal ro, einmal rw) [ ] PortainerCE - abschalten nach vollständiger Komodo-Übernahme
- 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
``` ```
--- ---
@@ -545,7 +448,8 @@ labels:
| `Glances` | `host` | Host-Metriken | | `Glances` | `host` | Host-Metriken |
| `netalertx` | `host` + Netzwerksicht | ARP / Netzwerkscan | | `netalertx` | `host` + Netzwerksicht | ARP / Netzwerkscan |
| `scrutiny` | `privileged: true` | SMART-Datenzugriff auf Laufwerke | | `scrutiny` | `privileged: true` | SMART-Datenzugriff auf Laufwerke |
| `PortainerCE` | Docker-Socket nicht dogmatisch `:ro` | Management-UI; Schreiboperationen können nötig sein | | `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 | | `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` | | `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 | | `mail-archiver` | `frontend_net` + `backend_net` | braucht Internetzugang für IMAP-Abruf (GMX, Gmail) und DB-Zugang |
@@ -564,13 +468,12 @@ Dieses Projekt wird **blockweise** umgesetzt, nicht wild containerweise.
| Sprint 1 | Quick Wins + `vaultwarden` | ✅ Abgeschlossen | | Sprint 1 | Quick Wins + `vaultwarden` | ✅ Abgeschlossen |
| Sprint 2 | `postgresql17` + `diun/gotify` | ✅ Abgeschlossen | | Sprint 2 | `postgresql17` + `diun/gotify` | ✅ Abgeschlossen |
| Sprint 3 | `mealie` / `mealie-postgres` + `mail-archiver` | ✅ Abgeschlossen | | Sprint 3 | `mealie` / `mealie-postgres` + `mail-archiver` | ✅ Abgeschlossen |
| Sprint 4 | Frontend-Stack (`paperless`, `Portainer`, `Dozzle`, `dashdot`, `scrutiny`, `filebrowser`, `gitea`, `UptimeKuma`) | 🔄 In Bearbeitung | | Sprint 4 | Frontend-Stack (`paperless`, `Portainer`, `Dozzle`, `dashdot`, `scrutiny`, `filebrowser`, `gitea`, `UptimeKuma`) + Traefik File-Provider Bereinigung + Komodo Einführung | ✅ Abgeschlossen |
| Sprint 5 | Compose-Migration der Dockerman-Container (`luckyBackup`, `Stash`, `Tailscale`, `netdata`, `Plex`, `Pi-hole`) | ⏳ Offen | | 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 | | Sprint 6 | Hardening / Secrets / Volumes / Sonderfälle (`immich_default`, Volumes, Mounts) | ⏳ Offen |
### 11.3 Regel für jede Änderung ### 11.3 Regel für jede Änderung
Jeder Sprint folgt demselben Schema: Jeder Sprint folgt demselben Schema:
1. Zielbild in diesem Dokument prüfen 1. Zielbild in diesem Dokument prüfen
2. nur den aktuellen Block anfassen 2. nur den aktuellen Block anfassen
3. Compose-Datei ändern 3. Compose-Datei ändern
@@ -605,25 +508,36 @@ Damit ist sofort klar:
## 13. Betriebserfahrungen und Entscheidungs-Log ## 13. Betriebserfahrungen und Entscheidungs-Log
### Secrets in Portainer Stacks ### 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`
Bei Deployments über Portainer gilt: **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.
- Host-Pfade in `env_file` (z.B. `/mnt/...`) sind nicht verfügbar — Portainer-Container hat keinen Zugriff **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 - `env_file` ist für Secrets ungeeignet in diesem Setup
**Standardlösung:** Portainer Environment Variables + `${VARIABLE_NAME}` in der Compose **Standardlösung:** Stack Environment Variables + `${VARIABLE_NAME}` in der Compose
```yaml ```yaml
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
``` ```
→ kein Secret im Git, funktionierender Deployment-Flow → kein Secret im Git, funktionierender Deployment-Flow
---
### Umgang mit `_FILE` Variablen ### Umgang mit `_FILE` Variablen
Nicht alle Container unterstützen `_FILE`-basierte Secrets: Nicht alle Container unterstützen `_FILE`-basierte Secrets:
| Container | `_FILE` Support | | Container | `_FILE` Support |
@@ -632,79 +546,49 @@ Nicht alle Container unterstützen `_FILE`-basierte Secrets:
| PostgreSQL | ✅ ja | | PostgreSQL | ✅ ja |
| Gotify | ✅ ja (`GOTIFY_DEFAULTUSER_PASS_FILE`) | | Gotify | ✅ ja (`GOTIFY_DEFAULTUSER_PASS_FILE`) |
| code-server | ✅ ja (`PASSWORD_FILE`) | | code-server | ✅ ja (`PASSWORD_FILE`) |
| Mealie | ❌ nein → Portainer ENV | | Mealie | ❌ nein → Stack ENV |
| diun | ❌ nein für Token → Portainer ENV | | diun | ❌ nein für Token → Stack ENV (Container entfernt) |
| paperless-ngx | ❌ nein für DB-Pass → Portainer ENV | | paperless-ngx | ❌ nein für DB-Pass → Stack ENV |
**Regel:** Wenn `_FILE` nicht unterstützt wird → Portainer Environment Variable **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
### diun — Korrekte Konfiguration (v4.x) **Hintergrund:** Update-Monitoring kann über Komodo's eingebaute Update-Notifications abgedeckt werden.
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 — Netz-Ausnahme
`ddns-updater` hat keine Web-UI, würde laut Schema in `backend_net` gehören — bleibt aber bewusst in `frontend_net`. `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. **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. → Dokumentierte Ausnahme in Kapitel 10.
---
### Netzwerk-Standard für Apps mit Datenbanken ### Netzwerk-Standard für Apps mit Datenbanken
- App → `frontend_net` + internes Netzwerk - App → `frontend_net` + internes Netzwerk
- Datenbank → nur internes Netzwerk (`internal: true`) - Datenbank → nur internes Netzwerk (`internal: true`)
**Beispiel (Mealie):** **Beispiel (Mealie):**
- `mealie``frontend_net` + `mealie_mealie_internal` - `mealie``frontend_net` + `mealie_mealie_internal`
- `mealie-postgres` → nur `mealie_mealie_internal` - `mealie-postgres` → nur `mealie_mealie_internal`
→ Datenbank vollständig isoliert, keine externe Erreichbarkeit möglich → Datenbank vollständig isoliert, keine externe Erreichbarkeit möglich
---
### Migrations-Standard für kritische Services ### Migrations-Standard für kritische Services
Bei Änderungen an Datenbanken oder Core-Services: Bei Änderungen an Datenbanken oder Core-Services:
1. Backup erstellen (`pg_dumpall`) 1. Backup erstellen (`pg_dumpall`)
2. aktuellen Zustand sichern (`docker inspect`) 2. aktuellen Zustand sichern (`docker inspect`)
3. neue Compose in Git/Portainer definieren 3. neue Compose in Git/Komodo definieren
4. Container stoppen & entfernen 4. Container stoppen & entfernen
5. neuen Stack deployen 5. neuen Stack deployen
6. Funktion prüfen (Logs + abhängige Services) 6. Funktion prüfen (Logs + abhängige Services)
→ Daten liegen im Volume und bleiben erhalten — Container sind austauschbar → Daten liegen im Volume und bleiben erhalten — Container sind austauschbar
---
### mail-archiver — Hybrid-Dienst ### mail-archiver — Hybrid-Dienst
`mail-archiver` benötigt beide Netze: `mail-archiver` benötigt beide Netze:
- `backend_net` → Verbindung zu `postgresql17` - `backend_net` → Verbindung zu `postgresql17`
- `frontend_net` → IMAP-Abruf von externen Mailservern (GMX, Gmail) + DNS-Auflösung - `frontend_net` → IMAP-Abruf von externen Mailservern (GMX, Gmail) + DNS-Auflösung
→ Kein reiner Backend-Dienst, sondern Hybrid mit externem Internetzugriff → Kein reiner Backend-Dienst, sondern Hybrid mit externem Internetzugriff
--- ---
@@ -713,5 +597,5 @@ Bei Änderungen an Datenbanken oder Core-Services:
Dieses Dokument ist keine lose Notiz, sondern das **operative Masterdokument** für die Docker- und Zugriffsarchitektur des Homelabs. Dieses Dokument ist keine lose Notiz, sondern das **operative Masterdokument** für die Docker- und Zugriffsarchitektur des Homelabs.
**Zielbild in einem Satz:** **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, 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 (100% Docker-Labels), Komodo als GitOps-Stack-Manager, keine produktiven `bridge`-Container mehr.