diff --git a/HOMELAB_ARCHITECTURE_MASTER_V2.md b/HOMELAB_ARCHITECTURE_MASTER_V2.md index 29024e5..1a3dfca 100644 --- a/HOMELAB_ARCHITECTURE_MASTER_V2.md +++ b/HOMELAB_ARCHITECTURE_MASTER_V2.md @@ -16,7 +16,7 @@ 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 A–F)](#9-migrationsstrategie-blöcke-a-f) +9. [Migrationsstrategie (Blöcke A–F)](#9-migrationsstrategie-blöcke-af) 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) @@ -31,12 +31,12 @@ | 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`) | +| VPN / Remote-Zugang | Tailscale (`tailscale`, host-Netz, Git-Stack) | +| DNS-Stack | AdGuard Home (`dns_net` + `frontend_net`) → Unbound (`dns_net`) | | Basis-Domain | `kaleschke.info` | | TLS | Let's Encrypt via Cloudflare DNS Challenge | | Certresolver | `le` | -| Compose-Standard | Komodo (GitOps, Stack aus Git) | +| Compose-Standard | Komodo (GitOps, Stack aus Gitea) | | Legacy | Portainer CE (in Ablösung durch Komodo, Sprint 5) | | Homelab-Compose-Pfad | `/mnt/user/services/homelab/` | | Secrets-Pfad | `/mnt/user/appdata/secrets/` | @@ -47,10 +47,9 @@ ## 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`. +Kein Webdienst veröffentlicht finale direkte Host-Ports außer `traefik` selbst. Begründete Ausnahmen: `gitea`-SSH (Port 222), `AdGuard Home` (Port 53/DNS + 3000/Admin), `Tailscale`, `Plex-Media-Server`. ### 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`) @@ -61,15 +60,13 @@ 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. ### 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. +Komodo, Dozzle, filebrowser, scrutiny, UptimeKuma, dashdot, code-server, luckyBackup, Traefik-Dashboard, netdata, Glances, netalertx und beszel 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. +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. @@ -77,7 +74,6 @@ Jeder produktive Container nutzt `restart: unless-stopped`, außer eine Ausnahme ### 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** --- @@ -90,7 +86,7 @@ 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 | +| `dns_net` | bridge | Resolver-Schicht: AdGuard Home + 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 | @@ -104,8 +100,8 @@ 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) + ├── öffentliche Apps (vaultwarden, mealie, paperless, immich, gitea, gotify, ntfy, homepage) + ├── Admin-UIs mit Middleware (komodo, dozzle, uptime-kuma, dashdot, filebrowser, scrutiny, code-server, beszel) └── Hybrid-Dienste mit Internetbedarf (mail-archiver, ddns-updater) backend_net (internal: true) @@ -114,65 +110,64 @@ backend_net (internal: true) ├── mail-archiver └── paperless-ngx +dns_net +├── AdGuard Home (+ frontend_net, feste IP 172.23.0.3) +└── unbound + App-interne Netze ├── mealie_mealie_internal (internal: true) ✅ └── immich_default (internal: true ausstehend) ⏳ Host-Sonderfälle -├── Tailscale-Docker -├── binhex-official-pihole +├── tailscale ├── Plex-Media-Server ├── netdata ├── Glances -└── netalertx +├── netalertx +└── beszel-agent ``` -### 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` +Diese Dienste sind über echte `*.kaleschke.info`-Domains erreichbar: + +- `vaultwarden` — vault.kaleschke.info +- `mealie` — mealie.kaleschke.info +- `paperless-ngx` — paperless.kaleschke.info +- `gotify` — gotify.kaleschke.info +- `ntfy` — ntfy.kaleschke.info +- `gitea` (Web) — git.kaleschke.info +- `immich_server` — immich.kaleschke.info +- `homepage` — homepage.kaleschke.info ### 4.2 Nicht öffentlich / nur Tailscale oder Traefik + Middleware Diese Dienste sind **keine Public Apps**: -- `Komodo` (Stack-Manager) + +- `Komodo` — komodo.kaleschke.info (Middleware) - `Dozzle` -- `UptimeKuma` +- `UptimeKuma` — uptime.kaleschke.info (Middleware) - `dashdot` -- `filebrowser` -- `scrutiny` +- `filebrowser` — files.kaleschke.info (Middleware) +- `scrutiny` — scrutiny.kaleschke.info (Middleware) - `luckyBackup` - `code-server` +- `beszel` — beszel.kaleschke.info (Middleware ausstehend) +- `backrest` - `Traefik-Dashboard` - `netdata` - `Glances` - `netalertx` +- `AdGuard Home` — Port 3000 direkt (kein Traefik, nur LAN-Zugang) ### 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 +- Zugriff durch Tailscale bzw. Auth begrenzt ist --- @@ -188,7 +183,7 @@ Wenn ein Dienst im `frontend_net` hängt, heißt das **nicht automatisch öffent 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 +11. Neue Dienste nur via Compose / Git-Stack 12. Änderungen immer gegen dieses Dokument prüfen --- @@ -200,7 +195,7 @@ Wenn ein Dienst im `frontend_net` hängt, heißt das **nicht automatisch öffent - **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`) +- **Ja** → `frontend_net` (auch ohne Web-UI) - **Nein** → weiter zu Schritt 3 ### Schritt 3 — Braucht der Dienst eine DB / Redis / interne Backends? @@ -211,59 +206,49 @@ Wenn ein Dienst im `frontend_net` hängt, heißt das **nicht automatisch öffent - **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 +- **Ja** → wenn Web-UI vorhanden trotzdem `frontend_net`, aber nur mit Middleware und ohne direkte Portfreigabe ### 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 +- `✅` = umgesetzt und in Git-Stack +- `✅ (Dockerman)` = Traefik/Netz korrekt konfiguriert, noch kein Git-Stack - `⏳` = noch zu migrieren / zu korrigieren +- `⚠️ Legacy` = läuft, wird abgelöst - `❌` = 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) | +| `traefik` | ✅ | `frontend_net`, `backend_net` | öffentlich 80/443 | zentraler Ingress, 100% Docker-Labels | — | +| `AdGuard Home` | ✅ | `dns_net` (172.23.0.3), `frontend_net` | Port 53 DNS direkt, Port 3000 Admin (LAN) | DNS-Server + Upstream zu unbound; kein Traefik (DNS-Sonderfall) | Admin-Port per Traefik + Middleware absichern (Block F) | +| `unbound` | ✅ | `dns_net` | intern | Upstream-Resolver für AdGuard, isoliert | — | +| `ddns-updater` | ✅ | `frontend_net` | intern | Cloudflare DNS API; bleibt in `frontend_net` | Dokumentierte Ausnahme | +| `tailscale` | ✅ | `host` | VPN-Zugang | Git-Stack (`host-services/tailscale/`) | `TS_USERSPACE`/`privileged` später prüfen | +| `backrest` | ✅ | `frontend_net`, `backend_net` | Traefik + Middleware | `traefik.docker.network=frontend_net` korrigiert | Breite Mounts straffen (Block F) | +| `homepage` | ✅ | `frontend_net` | Traefik | öffentliche Startseite via `homepage.kaleschke.info` | — | ### 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`) | — | +| `vaultwarden` | ✅ | `frontend_net` | Traefik | kein Host-Port, `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 | +| `Redis` | ✅ | `backend_net` | intern | intern-only Cache | optional named volume | | `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 | @@ -275,8 +260,9 @@ Legende Status: | `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 | — | +| `gotify` | ✅ (Dockerman) | `frontend_net` | Traefik | aktiv via `gotify.kaleschke.info`, kein Host-Port | noch kein Git-Stack (Sprint 5) | +| `ntfy` | ✅ | `frontend_net` | Traefik | aktiv via `ntfy.kaleschke.info`, Git-Stack | — | +| `gitea` | ✅ | `frontend_net` | Traefik + SSH-Port 222 | Web via Traefik, SSH direkt gebunden | — | | `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 | — | @@ -286,31 +272,49 @@ Legende Status: |---|---|---|---|---|---| | `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 | — | +| `PortainerCE` | ⚠️ Legacy | `frontend_net` | Traefik + Middleware | wird durch Komodo abgelöst | abschalten Sprint 5 | +| `Dozzle` | ✅ (Dockerman) | `frontend_net` | Traefik + Middleware | keine Host-Ports | Git-Stack Sprint 5 | +| `dashdot` | ✅ (Dockerman) | `frontend_net` | Traefik + Middleware | aktiv, kein Host-Port | Git-Stack Sprint 5 | +| `filebrowser` | ✅ | `frontend_net` | Traefik + Middleware | aktiv via `files.kaleschke.info` | Mounts einschränken (Block F) | +| `mail-archiver` | ✅ | `frontend_net`, `backend_net` | intern | IMAP-Abruf + DB-Zugang, kein öffentlicher Zugang | — | +| `luckyBackup` | ⏳ | `frontend_net` | Traefik + Middleware | Sprint 5 | aus bridge → frontend_net, Traefik, Port entfernen | ### 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 | — | +| `UptimeKuma` | ✅ | `frontend_net` | Traefik + Middleware | aktiv via `uptime.kaleschke.info` | — | +| `scrutiny` | ✅ | `frontend_net` | Traefik + Middleware | aktiv via `scrutiny.kaleschke.info`, Git-Stack | `privileged` später prüfen | +| `beszel` | ✅ | `frontend_net` | Traefik | aktiv via `beszel.kaleschke.info`, Git-Stack | Admin-Middleware ergänzen (Block F) | +| `beszel-agent` | ✅ | `host` | intern | System-Monitoring, Socket-Zugriff auf Host | — | +| `Glances` | ✅ (Dockerman) | `host` | Tailscale-only | Host-Metriken | Git-Stack Sprint 5 | +| `netdata` | ✅ (Dockerman) | `host` | Tailscale-only | Host-Metriken | leere CLAIM-Vars entfernen, Git-Stack Sprint 5 | +| `netalertx` | ✅ (Dockerman) | `host` | Tailscale-only | Host-/Netzsicht | Git-Stack Sprint 5 | -### 7.7 Entfernte Container +### 7.7 Sprint 5 — noch zu migrieren (Dockerman → Git-Stack) + +| Container | Status | Ziel | +|---|---|---| +| `gotify` | ✅ (Dockerman) | Git-Stack | +| `Dozzle` | ✅ (Dockerman) | Git-Stack | +| `dashdot` | ✅ (Dockerman) | Git-Stack | +| `Stash` | ⏳ | Compose-Migration | +| `netdata` | ✅ (Dockerman) | Git-Stack, CLAIM-Vars entfernen | +| `Glances` | ✅ (Dockerman) | Git-Stack | +| `netalertx` | ✅ (Dockerman) | Git-Stack | +| `Plex-Media-Server` | ⏳ | Compose-Migration, `host`-Netz bleibt (Discovery) | +| `luckyBackup` | ⏳ | Compose-Migration + Traefik + Port entfernen | +| `PortainerCE` | ⚠️ Legacy | abschalten nach vollständiger Komodo-Übernahme | + +### 7.8 Entfernte Container | Container | Entfernt am | Begründung | |---|---|---| -| `scanopy-server` | 2026-03-26 | nicht aktiv genutzt, durch paperless-ngx ersetzt | +| `scanopy-server` | 2026-03-26 | nicht 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 | +| `diun` | 2026-03-28 | Update-Monitoring via Komodo; Stack + Netz `diun_diun_default` + Repo-Eintrag entfernt | +| `binhex-official-pihole` | 2026-03-28 | ersetzt durch AdGuard Home + Unbound | --- @@ -335,13 +339,13 @@ labels: ``` ### Regeln -- `traefik.docker.network` immer explizit auf `frontend_net` — nie auf `backend_net` oder anderen Netzen! +- `traefik.docker.network` immer explizit auf `frontend_net` - keine `yourdomain.tld`-Platzhalter - certresolver immer `le` -- `tls=true` immer explizit setzen — ohne diese Zeile kein TLS +- `tls=true` immer explizit setzen - 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 +- **File-Provider nur noch für:** `middlewares.yml`, `tls.yml`, `dashboards.yml` — keine Service-Routen mehr via File-Provider --- @@ -354,66 +358,75 @@ labels: [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] anonyme/verwaiste Volumes bereinigt [x] scanopy komplett entfernt (3 Container + 2 Volumes + Netz) +[x] binhex-official-pihole entfernt → ersetzt durch AdGuard Home + Unbound ``` ### 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 +[x] vaultwarden - frontend_net, Host-Port entfernt, ADMIN_TOKEN_FILE, Traefik aktiv +[x] postgresql17 - Port 5432 entfernt, nur backend_net, POSTGRES_PASSWORD_FILE +[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] ntfy - Git-Stack - ntfy.kaleschke.info - Traefik aktiv +[x] paperless-ngx - traefik.enable=true - paperless.kaleschke.info - Port entfernt - tls=true [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] filebrowser - frontend_net - traefik.enable=true - files.kaleschke.info - Port entfernt - Middleware aktiv +[x] scrutiny - frontend_net - traefik.enable=true - scrutiny.kaleschke.info - Git-Stack [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 +[x] AdGuard Home - Git-Stack - dns_net + frontend_net - Port 53 (DNS) + 3000 (Admin) +[x] beszel - Git-Stack - frontend_net - beszel.kaleschke.info - Traefik 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 +[ ] beszel - Admin-Middleware (dashboard-auth@file) ergänzen ``` -### Block D — Bridge-/Dockerman-Container in Compose +### Block D — Dockerman-Container in Git-Stacks ```text [x] vaultwarden ✅ [x] postgresql17 ✅ [x] mail-archiver ✅ -[x] scrutiny ✅ (Git-Stack) +[x] scrutiny ✅ [x] filebrowser ✅ +[x] tailscale ✅ +[x] AdGuard Home ✅ +[x] beszel ✅ +[x] ntfy ✅ +[x] homepage ✅ +[ ] gotify +[ ] Dozzle +[ ] dashdot [ ] luckyBackup [ ] Stash -[ ] Tailscale-Docker [ ] netdata +[ ] Glances +[ ] netalertx [ ] 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] mail-archiver → Stack ENV (${MAILARCHIVER_AUTH_PASSWORD}) ✅ +[x] mealie → Stack ENV (kein _FILE-Support) ✅ +[x] mealie-postgres → Stack 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] paperless-ngx → Stack ENV (${PAPERLESS_DBPASS}) ✅ [x] code-server → PASSWORD_FILE ✅ -[x] immich_server → Portainer ENV (${IMMICH_DB_PASSWORD}) ✅ +[x] immich_server → Stack ENV (${IMMICH_DB_PASSWORD}) ✅ [x] immich_postgres → POSTGRES_PASSWORD_FILE ✅ [ ] immich_redis → anonymes Volume → named volume [ ] luckyBackup → Secrets prüfen @@ -424,13 +437,14 @@ labels: [ ] 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 +[ ] backrest - /mnt/user doppelt gemountet (ro + 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 +[ ] tailscale - TS_USERSPACE/privileged bereinigen wenn möglich +[ ] beszel - Admin-Middleware (dashboard-auth@file) ergänzen +[ ] AdGuard Home - Admin-Port 3000 per Traefik + Middleware absichern (aktuell direkter Port) [ ] PortainerCE - abschalten nach vollständiger Komodo-Übernahme ``` @@ -441,17 +455,18 @@ labels: | 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 | +| `tailscale` | `host` | VPN-Zugang; Umstellung nur kontrolliert möglich | +| `AdGuard Home` | Port 53 (TCP/UDP) direkt + Port 3000 Admin | DNS benötigt direkten Port 53; kein HTTP-Proxy für DNS möglich | | `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 | +| `beszel-agent` | `host` | direkter Host-Zugriff für System-Monitoring nötig | +| `Komodo` | Docker-Socket Zugriff | Stack-Deployments benötigen Socket | +| `PortainerCE` | Docker-Socket | 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` | +| `ddns-updater` | bleibt in `frontend_net` statt `backend_net` | braucht Cloudflare-API-Zugang; `backend_net` ist `internal: true` | | `mail-archiver` | `frontend_net` + `backend_net` | braucht Internetzugang für IMAP-Abruf (GMX, Gmail) und DB-Zugang | --- @@ -468,12 +483,11 @@ Dieses Projekt wird **blockweise** umgesetzt, nicht wild containerweise. | 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 | +| Sprint 4 | Frontend-Stack (`paperless`, `PortainerCE`, `Dozzle`, `dashdot`, `scrutiny`, `filebrowser`, `gitea`, `UptimeKuma`, `ntfy`, `beszel`) + Traefik File-Provider Bereinigung + Komodo Einführung + AdGuard Home Migration + Pi-hole Ablösung | ✅ Abgeschlossen | +| Sprint 5 | Git-Stack-Migration Dockerman-Container (`gotify`, `Dozzle`, `dashdot`, `luckyBackup`, `Stash`, `netdata`, `Glances`, `netalertx`, `Plex`) + PortainerCE abschalten | 🔄 In Bearbeitung | +| Sprint 6 | Hardening / Secrets / Volumes / Sonderfälle (`immich_default`, Volumes, Mounts, AdGuard Traefik) | ⏳ 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 @@ -484,7 +498,7 @@ Jeder Sprint folgt demselben Schema: ### 11.4 Source-of-Truth-Hierarchie 1. **Dieses Dokument** -2. Compose-Dateien +2. Compose-Dateien im Git-Repo 3. operative Checklisten 4. ad-hoc Notizen / Chat @@ -511,9 +525,9 @@ Damit ist sofort klar: ### 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` +- **Verbleibend (notwendig):** `middlewares.yml`, `tls.yml`, `dashboards.yml` -**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. +**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 (Bad Gateway). Nach Löschung läuft Traefik ausschließlich auf Docker-Labels. **Regel:** Neue Dienste ausschließlich via Docker Compose Labels konfigurieren. Keine neuen `.yml`-Dateien im `dynamic/`-Verzeichnis für Service-Routen anlegen. @@ -521,24 +535,37 @@ Die statischen File-Provider-Konfigurationen in `/mnt/user/appdata/traefik/dynam 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 +- Stacks werden via 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 +### AdGuard Home — Ablösung von Pi-hole (2026-03-28) +`binhex-official-pihole` wurde entfernt und durch `AdGuard Home` + `unbound` ersetzt: +- AdGuard läuft als Git-Stack (`host-services/Adguard/docker-compose.yml`) +- Netzwerke: `dns_net` (feste IP 172.23.0.3) + `frontend_net` +- Port 53 (DNS) direkt gebunden — dokumentierte Ausnahme +- Port 3000 (Admin-UI) direkt gebunden — Traefik-Absicherung ausstehend (Block F) +- `unbound` läuft weiterhin als Upstream-Resolver in `dns_net` -**Standardlösung:** Stack Environment Variables + `${VARIABLE_NAME}` in der Compose -```yaml -POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} -``` -→ kein Secret im Git, funktionierender Deployment-Flow +### 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 -### Umgang mit `_FILE` Variablen -Nicht alle Container unterstützen `_FILE`-basierte Secrets: +Update-Monitoring kann über Komodo's eingebaute Update-Notifications abgedeckt werden. + +### ntfy — Push-Notifications (Git-Stack) +`ntfy` läuft als Git-Stack (`apps/ntfy/docker-compose.yml`) parallel zu `gotify`: +- `ntfy.kaleschke.info` via Traefik +- `NTFY_UPSTREAM_BASE_URL: https://ntfy.sh` für mobile Push-Notifications +- `NTFY_BEHIND_PROXY: true` korrekt gesetzt + +### Secrets in Komodo / Portainer Stacks +Host-Pfade in `env_file` (z.B. `/mnt/...`) sind in Git-Stacks nicht verfügbar. Standardlösung: Stack Environment Variables + `${VARIABLE_NAME}` in der Compose. + +**Regel:** Wenn `_FILE` nicht unterstützt wird → Stack Environment Variable. Kein Secret im Git. | Container | `_FILE` Support | |---|---| @@ -547,49 +574,19 @@ Nicht alle Container unterstützen `_FILE`-basierte Secrets: | 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`. +Bleibt bewusst in `frontend_net` statt `backend_net`, weil `backend_net` `internal: true` ist und ddns-updater die Cloudflare-API erreichen muss. -**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. +### mail-archiver — Hybrid-Dienst +Benötigt `backend_net` (PostgreSQL) + `frontend_net` (IMAP-Abruf von GMX/Gmail). Kein reiner Backend-Dienst. Kein öffentlicher Traefik-Zugang. ### 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 +Beispiel (Mealie): `mealie` → `frontend_net` + `mealie_mealie_internal`, `mealie-postgres` → nur `mealie_mealie_internal`. --- @@ -598,4 +595,4 @@ 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. **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. +`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, AdGuard Home + Unbound für DNS, keine produktiven `bridge`-Container mehr.