# 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-04-15 | **Aktueller Schwerpunkt:** GitOps / Borg / Workflow-Konsolidierung --- ## 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 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) 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`, 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 Gitea) | | Legacy | Portainer CE entfernt; Komodo ist alleiniger Stack-Manager | | 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), `AdGuard Home` (Port 53/DNS + 3000/Admin), `Tailscale`, `Plex-Media-Server`. ### P2 — Das Setup bleibt bewusst einfach: `frontend_net` + `backend_net` + app-interne Netze - `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, filebrowser, scrutiny, UptimeKuma, code-server, Traefik-Dashboard, backrest, borg-ui 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. ### P6 — Secrets nie im Klartext Passwörter, Tokens und API-Keys gehören in Secret-Dateien unter `/mnt/user/appdata/secrets/` oder als Komodo Stack Environment Variables mit `${VARIABLE}` in der Compose. ### 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 - Docker-Socket standardmäßig vorsichtig behandeln; **Komodo ist dokumentierte Ausnahme** --- ## 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: AdGuard Home + Unbound | bleibt | | `mealie_mealie_internal` | bridge, `internal: true` | internes Netz nur für `mealie` + `mealie-postgres` | ✅ umgesetzt | | `immich_default` | Compose-intern, `internal: true` | internes Immich-Netz | ✅ umgesetzt | | `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, ntfy, homepage) ├── Admin-UIs mit Middleware (komodo, uptime-kuma, filebrowser, scrutiny, code-server, backrest, borg-ui, beszel) └── Hybrid-Dienste mit Internetbedarf (mail-archiver, ddns-updater) backend_net (internal: true) ├── postgresql17 ├── Redis ├── 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) ✅ Host-Sonderfälle ├── tailscale ├── Plex-Media-Server └── beszel-agent ``` --- ## 4. Zugangsmodell: Traefik vs. Tailscale ### 4.1 Öffentlich über Traefik Diese Dienste sind über echte `*.kaleschke.info`-Domains erreichbar: - `vaultwarden` — vault.kaleschke.info - `mealie` — mealie.kaleschke.info - `paperless-ngx` — paperless.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` — komodo.kaleschke.info (Middleware) - `UptimeKuma` — uptime.kaleschke.info (Middleware) - `filebrowser` — files.kaleschke.info (Middleware) - `scrutiny` — scrutiny.kaleschke.info (Middleware) - `code-server` — Traefik + Middleware - `beszel` — beszel.kaleschke.info (Middleware ausstehend) - `backrest` — Traefik + Middleware - `borg-ui` — borg.kaleschke.info (Middleware) - `Traefik-Dashboard` - `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 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 Stack 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 / Git-Stack 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) - **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 und ohne direkte Portfreigabe ### Schritt 6 — Braucht der Dienst Host-/Discovery-/L2-Sicht? - **Ja** → `host` nur mit dokumentierter Begründung ### Schritt 7 — Braucht die App ein eigenes internes App-Netz? - **Ja** → Compose-internes Netz mit `internal: true` --- ## 7. Container-Zielbild (vollständig) Legende Status: - `✅` = 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 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, `ADMIN_TOKEN_FILE` | — | | `authelia` | 🔄 | `frontend_net`, `backend_net` | Traefik via `auth.kaleschke.info` | ForwardAuth-Provider, Secrets via `_FILE`, PostgreSQL + Redis Shared | NAS-seitige Einrichtung ausstehend (Secrets, DB, Users, DNS) | ### 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 | | `mealie-postgres` | ✅ | `mealie_mealie_internal` | intern | isoliert, nie `frontend_net` | — | | `immich_postgres` | ✅ | `immich_default` | intern | intern-only | — | | `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 | — | | `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_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` | ❌ entfernt | - | - | 2026-03-29 abgeschaltet | historisch; nicht mehr deployen | | `filebrowser` | ✅ | `frontend_net` | Traefik + Middleware | aktiv via `files.kaleschke.info` | Mounts einschränken (Block F) | | `borg-ui` | 🔄 | `frontend_net` | Traefik + Middleware | Git-Stack für Borg/BorgBase-Backups; Borg UI bündelt Borg-CLI im Container | BorgBase-SSH-Key hinterlegen, erstes Repo initialisieren, Quell-Mounts bei Bedarf gezielt erweitern | | `mail-archiver` | ✅ | `frontend_net`, `backend_net` | intern | IMAP-Abruf + DB-Zugang, kein öffentlicher Zugang | — | ### 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` | — | | `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 | — | ### 7.7 Sprint 5 — noch zu migrieren / abzuschalten | Container | Status | Ziel | |---|---|---| | `Plex-Media-Server` | ⏳ Dockerman | Compose-Migration, `host`-Netz bleibt (Discovery) | | `PortainerCE` | ✅ abgeschlossen | 2026-03-29 abgeschaltet | ### 7.8 Entfernte Container | Container | Entfernt am | Begründung | |---|---|---| | `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 | Update-Monitoring via Komodo; Stack + Netz `diun_diun_default` + Repo-Eintrag entfernt | | `binhex-official-pihole` | 2026-03-28 | ersetzt durch AdGuard Home + Unbound | | `gotify` | 2026-03-28 | nicht mehr aktiv; Push-Notifications via ntfy abgedeckt | | `Dozzle` | 2026-03-28 | nicht mehr aktiv; Log-Monitoring via Komodo/beszel | | `dashdot` | 2026-03-28 | nicht mehr aktiv; System-Monitoring via beszel | | `netdata` | 2026-03-28 | nicht mehr aktiv; System-Monitoring via beszel | | `Glances` | 2026-03-28 | nicht mehr aktiv | | `netalertx` | 2026-03-28 | nicht mehr aktiv | | `luckyBackup` | 2026-03-28 | nicht mehr aktiv; Backup via backrest | | `Stash` | 2026-03-28 | nicht mehr aktiv | | `PortainerCE` | 2026-03-29 | abgeschaltet; Komodo ist alleiniger Stack-Manager | --- ## 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..rule=Host(`.kaleschke.info`) - traefik.http.routers..entrypoints=websecure - traefik.http.routers..tls=true - traefik.http.routers..tls.certresolver=le - traefik.http.services..loadbalancer.server.port= ``` ### Zusatz für Admin-Dienste ```yaml - traefik.http.routers..middlewares=dashboard-auth@file,secure-headers@file ``` ### Regeln - `traefik.docker.network` immer explizit auf `frontend_net` - keine `yourdomain.tld`-Platzhalter - certresolver immer `le` - `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`, `tls.yml`, `dashboards.yml` — keine Service-Routen mehr via File-Provider --- ## 9. Migrationsstrategie (Blöcke A–F) **Letzte Aktualisierung:** 2026-03-29 ### 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] leere Netzwerke entfernt: br0, immich_net, kopia_default, netbox_default, diun_default [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 - 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 ✅ ABGESCHLOSSEN ```text [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] UptimeKuma - traefik.enable=true - uptime.kaleschke.info - Port entfernt - Middleware aktiv [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 [ ] beszel - Admin-Middleware (dashboard-auth@file) ergänzen ``` ### Block D — Dockerman-Container in Git-Stacks ```text [x] vaultwarden ✅ [x] postgresql17 ✅ [x] mail-archiver ✅ [x] scrutiny ✅ [x] filebrowser ✅ [x] tailscale ✅ [x] AdGuard Home ✅ [x] beszel ✅ [x] ntfy ✅ [x] homepage ✅ [ ] Plex-Media-Server (Sprint 5) ``` ### Block E — Secrets-Migration ```text [x] vaultwarden → ADMIN_TOKEN_FILE ✅ [x] postgresql17 → POSTGRES_PASSWORD_FILE ✅ [x] mail-archiver → Stack ENV (${MAILARCHIVER_AUTH_PASSWORD}) ✅ [x] mealie → Stack ENV (kein _FILE-Support) ✅ [x] mealie-postgres → Stack ENV (kein _FILE-Support) ✅ [x] paperless-ngx → Stack ENV (${PAPERLESS_DBPASS}) ✅ [x] code-server → PASSWORD_FILE ✅ [x] immich_server → Stack ENV (${IMMICH_DB_PASSWORD}) ✅ [x] immich_postgres → POSTGRES_PASSWORD_FILE ✅ [ ] immich_redis → anonymes Volume → named volume ``` ### Block F — Feinschliff / Hardening ```text [x] immich_default - internal: true gesetzt (2026-03-29) [x] PortainerCE - abgeschaltet (Sprint 5, 2026-03-29) [ ] immich_redis - anonymes Volume → named volume in Compose [ ] immich_server - anonymes Volume prüfen und benennen [ ] 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 [ ] scrutiny - später prüfen, ob privileged reduziert werden kann [ ] 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) ``` ### Block G — Authelia SSO/2FA (Sprint 7) ```text [x] security/authelia/docker-compose.yml im Repo (2026-03-29) [x] security/authelia/configuration.yml im Repo (2026-03-29) [x] traefik/dynamic/middlewares.yml - authelia ForwardAuth Middleware ergänzt (2026-03-29) [ ] NAS: Secrets anlegen (jwt_secret, session_secret, storage_encryption_key, postgres_password) [ ] NAS: Authelia PostgreSQL-User und -Datenbank anlegen [ ] NAS: /mnt/user/appdata/authelia/config/configuration.yml aus Repo übernehmen [ ] NAS: users_database.yml mit gehashten Passwörtern anlegen [ ] NAS: DNS-Eintrag auth.kaleschke.info in AdGuard setzen [ ] Komodo: Stack security/authelia deployen [ ] Services schrittweise mit authelia@docker Middleware absichern ``` --- ## 10. Bekannte Ausnahmen und Begründungen | Container | Ausnahme | Begründung | |---|---|---| | `traefik` | Host-Ports 80/443 | zentraler Reverse Proxy | | `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 | | `scrutiny` | `privileged: true` | SMART-Datenzugriff auf Laufwerke | | `beszel-agent` | `host` | direkter Host-Zugriff für System-Monitoring nötig | | `Komodo` | Docker-Socket Zugriff | Stack-Deployments benötigen Socket | | `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 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 | --- ## 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`, `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 | `Plex-Media-Server` Compose-Migration + `PortainerCE` abschalten | ✅ Abgeschlossen | | Sprint 6 | Hardening / Secrets / Volumes / Sonderfälle (`immich_default` ✅, Volumes, Mounts, AdGuard Traefik) | ✅ Abgeschlossen | | Sprint 7 | `Authelia` SSO/2FA: Compose + Config im Repo, Traefik ForwardAuth Middleware, NAS-seitige Einrichtung | 🔄 In Bearbeitung | | Sprint 8 | `borg-ui` Git-Stack + BorgBase Offsite-Backup-Workflow | 🔄 In Bearbeitung | ### 11.3 Regel für jede Änderung 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. **Gitea Online (origin/master)** 2. lokaler Clone / GitHub Desktop 3. Compose-Dateien im Git-Repo 4. Komodo als Deploy-Consumer 5. operative Checklisten und Notizen ### 11.5 Operativer Git-Workflow - Gitea Online ist der verbindliche Sollzustand. - Lokal wird standardmäßig über GitHub Desktop gearbeitet. - Komodo deployt aus Gitea und ist kein Bearbeitungsort. - Webhooks sind aktiv: Ein Push kann unmittelbar einen Komodo-Deploy auslösen. - Wenn online in Gitea editiert wurde, muss vor der nächsten lokalen Änderung zuerst Fetch origin und danach Pull origin erfolgen. --- ## 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`, `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 (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. ### 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 Gitea synchronisiert und über Komodo deployed - Portainer CE ist abgeschaltet; Komodo ist der alleinige aktive Stack-Manager **Betriebsregel:** Alle Stack-Änderungen laufen über Git; Komodo konsumiert nur den Stand aus Gitea. ### 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` ### 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 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`): - `ntfy.kaleschke.info` via Traefik - `NTFY_UPSTREAM_BASE_URL: https://ntfy.sh` für mobile Push-Notifications - `NTFY_BEHIND_PROXY: true` korrekt gesetzt ### immich_default — internal: true gesetzt (2026-03-29) `immich_default` wurde von `external: true` auf ein Compose-verwaltetes internes Netz umgestellt: - **Vorher:** `external: true` (manuell erstellt, falsche Labels `com.docker.compose.network=default`) - **Nachher:** Compose-managed, `internal: true`, `driver: bridge`, korrekte Labels - Durchgeführt via: manuelles `docker stop` der Containers → `docker network rm immich_default` → Komodo Redeploy - Ergebnis: alle Immich-Container (`immich_postgres`, `immich_redis`, `immich_machine_learning`) sind jetzt vom Internet isoliert; nur `immich_server` hat zusätzlich `frontend_net` für Traefik ### 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. ### Borg UI / BorgBase (2026-04-12) - `borg-ui` läuft als Admin-Dienst in `ops/borg-ui/docker-compose.yml` - nur `frontend_net`, weil Web-UI + externer SSH-Zugang zu BorgBase benötigt werden - keine direkten Host-Ports; Zugriff ausschließlich via Traefik + Middleware über `borg.kaleschke.info` - Mounts bewusst klein gehalten: `/mnt/user/appdata` read-only als erste Backup-Quelle, separates Restore-Ziel unter `/mnt/user/appdata/borg-ui/restore` - kein separater Borg-CLI-Container nötig, da Borg UI die Borg-CLI bereits im Container mitbringt | Container | `_FILE` Support | |---|---| | Vaultwarden | ✅ ja | | PostgreSQL | ✅ ja | | code-server | ✅ ja (`PASSWORD_FILE`) | | Immich Postgres | ✅ ja (`POSTGRES_PASSWORD_FILE`) | | Mealie | ❌ nein → Stack ENV | | paperless-ngx | ❌ nein für DB-Pass → Stack ENV | ### ddns-updater — Netz-Ausnahme Bleibt bewusst in `frontend_net` statt `backend_net`, weil `backend_net` `internal: true` ist und ddns-updater die Cloudflare-API erreichen muss. ### 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`. --- ## 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, AdGuard Home + Unbound für DNS, keine produktiven `bridge`-Container mehr.