Files
homelab-infra/HOMELAB_ARCHITECTURE_MASTER_V2.md
T

27 KiB
Raw Blame History

HOMELAB_ARCHITECTURE — MASTER v2

Single Source of Truth für Docker-Netzwerkarchitektur, Sicherheitsregeln, Zielbild und Migration des Kallilabcore-Homelabs. Arbeitsregel für KI-Assistenten: Dieses Dokument immer zuerst lesen, bevor Fragen zu Containern, Netzwerken, Traefik, Tailscale, Migration oder Security beantwortet werden.

Stand: 2026-03-28 | Aktueller Sprint: 5 (Compose-Migration Dockerman-Container) — Sprints 14 abgeschlossen


Inhaltsverzeichnis

  1. Systemüberblick
  2. Architektur-Prinzipien
  3. Finales Netzwerk-Zielbild
  4. Zugangsmodell: Traefik vs. Tailscale
  5. Globale Sicherheitsregeln
  6. Einordnungsschema für neue Container
  7. Container-Zielbild (vollständig)
  8. Traefik-Label-Standard
  9. Migrationsstrategie (Blöcke AF)
  10. Bekannte Ausnahmen und Begründungen
  11. Projektorganisation und Arbeitsmodus
  12. Nutzung mit KI / Kontext-Regel
  13. Betriebserfahrungen und Entscheidungs-Log

1. Systemüberblick

Eigenschaft Wert
Host-OS Unraid
Hostname Kallilabcore
Reverse Proxy Traefik v3 (100% Docker-Labels, kein File-Provider)
VPN / Remote-Zugang Tailscale (Tailscale-Docker, host-Netz)
DNS-Stack AdGuard Home (host) → Unbound (dns_net)
Basis-Domain kaleschke.info
TLS Let's Encrypt via Cloudflare DNS Challenge
Certresolver le
Compose-Standard Komodo (GitOps, Stack aus Git)
Legacy Portainer CE (in Ablösung durch Komodo, Sprint 5)
Homelab-Compose-Pfad /mnt/user/services/homelab/
Secrets-Pfad /mnt/user/appdata/secrets/
Grundsatz Keine neuen Dockerman-Einzelcontainer

2. Architektur-Prinzipien

P1 — Traefik ist der einzige öffentliche HTTP(S)-Einstiegspunkt

Kein Webdienst veröffentlicht finale direkte Host-Ports außer traefik selbst. Begründete Ausnahmen: gitea-SSH (Port 222), Plex-Media-Server, binhex-official-pihole, Tailscale-Docker.

P2 — Das Setup bleibt bewusst einfach: frontend_net + backend_net + app-interne Netze

Die Quellenlage für Homelabs mit Traefik spricht für ein simples 2-Netz-Modell:

  • frontend_net = Proxy-/Web-Netz
  • backend_net = intern für DB/Cache/App-Kommunikation
  • zusätzliche Netze nur app-intern, wenn technisch nötig (mealie_mealie_internal, immich_default, dns_net)

Es gibt keine künstlichen globalen Zusatznetze wie admin_net, monitoring_net oder media_net.

P3 — Datenbanken gehören nie ins frontend_net

Postgres, Redis und ähnliche Dienste laufen ausschließlich in backend_net oder einem eigenen internen Compose-Netz.

P4 — Admin-UIs sind nicht öffentlich

Komodo, Dozzle, filebrowser, scrutiny, UptimeKuma, dashdot, code-server, luckyBackup, Traefik-Dashboard, netdata, Glances und netalertx sind standardmäßig Tailscale-only oder hinter Traefik mit zentraler Middleware abgesichert.

P5 — Compose-first

Alle produktiven Container werden als Compose verwaltet. Bestehende Dockerman-/Ad-hoc-Container werden schrittweise migriert.

P6 — Secrets nie im Klartext

Passwörter, Tokens und API-Keys gehören in Secret-Dateien unter /mnt/user/appdata/secrets/ oder als Komodo/Portainer Environment Variables mit ${VARIABLE} in der Compose. Ziel: kein sensibles Secret mehr sichtbar in docker inspect.

P7 — restart: unless-stopped ist Pflichtstandard

Jeder produktive Container nutzt restart: unless-stopped, außer eine Ausnahme ist dokumentiert.

P8 — Least Privilege

  • security_opt: ["no-new-privileges:true"] standardmäßig ergänzen
  • privileged: true nur mit dokumentierter Begründung
  • read_only: true und Non-Root nur nach getesteter Image-Kompatibilität
  • Docker-Socket standardmäßig vorsichtig behandeln; Komodo/PortainerCE sind dokumentierte Ausnahmen

3. Finales Netzwerk-Zielbild

3.1 Netz-Logik

Netzwerk Typ Zweck Status
frontend_net bridge, external einziges Traefik-/Web-Netz Standard
backend_net bridge, internal: true interne App-/DB-/Cache-Kommunikation Standard
dns_net bridge Resolver-Schicht für unbound bleibt
mealie_mealie_internal bridge, internal: true internes Netz nur für mealie + mealie-postgres umgesetzt
immich_default Compose-intern internes Immich-Netz internal: true noch setzen
host host nur für echte Sonderfälle begründet

3.2 Finales Diagramm (vereinfacht)

Internet
│
▼
traefik (80/443)
│
└── frontend_net
    ├── öffentliche Apps (vaultwarden, mealie, paperless, immich, gitea, gotify)
    ├── Admin-UIs mit Middleware (komodo, dozzle, uptime-kuma, dashdot, filebrowser, scrutiny, code-server)
    └── Hybrid-Dienste mit Internetbedarf (mail-archiver, ddns-updater)

backend_net (internal: true)
├── postgresql17
├── Redis
├── mail-archiver
└── paperless-ngx

App-interne Netze
├── mealie_mealie_internal (internal: true) ✅
└── immich_default (internal: true ausstehend) ⏳

Host-Sonderfälle
├── Tailscale-Docker
├── binhex-official-pihole
├── Plex-Media-Server
├── netdata
├── Glances
└── netalertx

3.3 Architekturentscheidung (final)

Wir bleiben final bei wenigen Netzen. Das ist bewusst näher an aktuellen Homelab-Traefik-Setups als ein künstlich übersegmentiertes Docker-Netzmodell. Trennung erfolgt primär über:

  • Traefik
  • Auth-/Security-Middlewares
  • Tailscale
  • kein direktes Port-Publishing
  • app-interne Netze
  • Host-Sonderfälle nur mit Begründung

4. Zugangsmodell: Traefik vs. Tailscale

4.1 Öffentlich über Traefik

Diese Dienste dürfen final über echte *.kaleschke.info-Domains erreichbar sein:

  • homepage
  • vaultwarden
  • mealie
  • paperless-ngx
  • gotify
  • gitea (Web)
  • immich_server
  • mail-archiver
  • backrest

4.2 Nicht öffentlich / nur Tailscale oder Traefik + Middleware

Diese Dienste sind keine Public Apps:

  • Komodo (Stack-Manager)
  • Dozzle
  • UptimeKuma
  • dashdot
  • filebrowser
  • scrutiny
  • luckyBackup
  • code-server
  • Traefik-Dashboard
  • netdata
  • Glances
  • netalertx

4.3 Regel

Wenn ein Dienst im frontend_net hängt, heißt das nicht automatisch öffentlich. Admin-Dienste dürfen im frontend_net liegen, wenn:

  • Traefik sie routet
  • zentrale Middleware aktiv ist
  • keine direkten Host-Ports bestehen
  • Zugriff zusätzlich durch Tailscale bzw. Auth begrenzt ist

5. Globale Sicherheitsregeln

  1. Keine produktiven Dienste im Docker-Default-bridge
  2. Keine direkten Host-Ports für Web-UIs außer dokumentierte Ausnahmen
  3. restart: unless-stopped als Standard
  4. Secrets als Datei / _FILE oder Komodo/Portainer Environment Variables mit ${VAR}
  5. no-new-privileges:true ergänzen, wo praktikabel
  6. traefik.docker.network=frontend_net immer explizit setzen
  7. Admin-Dienste immer mit dashboard-auth@file,secure-headers@file
  8. Placeholder-Domains (yourdomain.tld) sind verboten
  9. privileged: true nur mit Begründung
  10. Volume-Mounts so klein und so read-only wie möglich
  11. Neue Dienste nur via Compose Manager / YAML
  12. Änderungen immer gegen dieses Dokument prüfen

6. Einordnungsschema für neue Container

Schritt 1 — Hat der Dienst eine Web-UI?

  • Jafrontend_net
  • Nein → weiter zu Schritt 2

Schritt 2 — Braucht der Dienst externe Internetverbindungen?

  • Jafrontend_net (auch ohne Web-UI — z.B. ddns-updater, mail-archiver)
  • Nein → weiter zu Schritt 3

Schritt 3 — Braucht der Dienst eine DB / Redis / interne Backends?

  • Ja → zusätzlich backend_net oder eigenes app-internes Netz
  • Nein → nur das funktional nötige Netz

Schritt 4 — Ist es eine Datenbank oder ein Cache?

  • Ja → niemals frontend_net, nur backend_net oder internes Compose-Netz

Schritt 5 — Ist es ein Admin-/Monitoring-Dienst?

  • Ja → wenn Web-UI vorhanden trotzdem frontend_net, aber nur mit:
    • Middleware
    • keiner direkten Portfreigabe
    • Tailscale-only-Charakter

Schritt 6 — Braucht der Dienst Host-/Discovery-/L2-Sicht?

  • Jahost 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

  • UIfrontend_net
  • Externer Internetzugriff nötigfrontend_net (auch ohne UI)
  • DB/Cachebackend_net
  • Spezialfall → app-internes Netz
  • Host-Zugriff → nur dokumentierte Ausnahme

7. Container-Zielbild (vollständig)

Legende Status:

  • = umgesetzt
  • = noch zu migrieren / zu korrigieren
  • = entfernt

7.1 Infrastruktur / Core

Container Status Soll-Netz(e) Finaler Zugang Finaler Sollzustand Offene Punkte
traefik frontend_net, backend_net öffentlich zentraler Ingress, 80/443 direkt, 100% Docker-Labels
homepage frontend_net Traefik öffentliche Startseite
ddns-updater frontend_net intern bleibt in frontend_net — braucht Internetzugang für Cloudflare DNS Dokumentierte Ausnahme, kein backend_net wegen internal: true
Tailscale-Docker host VPN-Zugang Host-Sonderfall, restart: unless-stopped gesetzt TS_USERSPACE/privileged später prüfen
binhex-official-pihole host LAN DNS / intern Host-Sonderfall, restart: unless-stopped gesetzt
unbound dns_net intern Resolver bleibt isoliert
backrest frontend_net, backend_net Traefik traefik.docker.network=frontend_net korrigiert Breite Mounts straffen (Block F)

7.2 Sicherheit / Identity

Container Status Soll-Netz(e) Finaler Zugang Finaler Sollzustand Offene Punkte
vaultwarden frontend_net Traefik kein Host-Port, Secret-Datei (ADMIN_TOKEN_FILE)

7.3 Datenbanken / Caches

Container Status Soll-Netz(e) Finaler Zugang Finaler Sollzustand Offene Punkte
postgresql17 backend_net intern kein Host-Port, POSTGRES_PASSWORD_FILE
Redis backend_net intern intern-only Cache optional named volume statt anonym
mealie-postgres mealie_mealie_internal intern isoliert, nie frontend_net
immich_postgres immich_default intern intern-only immich_defaultinternal: true ausstehend
immich_redis immich_default intern intern-only anonymes Volume → named volume

7.4 Öffentliche Apps

Container Status Soll-Netz(e) Finaler Zugang Finaler Sollzustand Offene Punkte
paperless-ngx frontend_net, backend_net Traefik aktiv via paperless.kaleschke.info
Paperless-AI frontend_net Traefik aktiv
mealie frontend_net, mealie_mealie_internal Traefik sauber getrennte App/DB-Struktur
gotify frontend_net Traefik aktiv via gotify.kaleschke.info, kein Host-Port
gitea frontend_net Traefik + SSH Web via Traefik, SSH-Port 222 bleibt
immich_server immich_default, frontend_net Traefik aktiv via immich.kaleschke.info immich_defaultinternal: true ausstehend
immich_machine_learning immich_default intern bleibt intern

7.5 Admin / Operations

Container Status Soll-Netz(e) Finaler Zugang Finaler Sollzustand Offene Punkte
komodo frontend_net Traefik + Middleware primärer GitOps-Stack-Manager
code-server frontend_net Traefik + Middleware PASSWORD_FILE aktiv
PortainerCE ⚠️ Legacy frontend_net Traefik + Middleware wird durch Komodo abgelöst abschalten in Sprint 5
Dozzle frontend_net Traefik + Middleware keine Host-Ports
filebrowser frontend_net Traefik + Middleware aktiv via files.kaleschke.info Breite Mounts einschränken (Block F)
mail-archiver frontend_net, backend_net Traefik aktiv via mail.kaleschke.info, kein Host-Port

7.6 Monitoring / Status

Container Status Soll-Netz(e) Finaler Zugang Finaler Sollzustand Offene Punkte
UptimeKuma frontend_net Traefik + Middleware aktiv via uptime.kaleschke.info, kein Host-Port
dashdot frontend_net Traefik + Middleware aktiv, kein Host-Port
Glances host Tailscale-only Host-Metriken
netdata host Tailscale-only Host-Metriken leere CLAIM-Vars entfernen
scrutiny frontend_net Traefik + Middleware aktiv via scrutiny.kaleschke.info, Git-Stack privileged später prüfen ob reduzierbar
netalertx host Tailscale-only Host-/Netzsicht
beszel frontend_net Traefik + Middleware System-/Host-Monitoring

7.7 Entfernte Container

Container Entfernt am Begründung
scanopy-server 2026-03-26 nicht aktiv genutzt, durch paperless-ngx ersetzt
scanopy-postgres 2026-03-26 zusammen mit scanopy entfernt
scanopy-daemon 2026-03-26 zusammen mit scanopy entfernt
diun 2026-03-28 Image-Update-Monitoring nicht mehr via diun; Stack deinstalliert, Netz bereinigt

8. Traefik-Label-Standard

Jeder Dienst mit Traefik-Routing nutzt dieses Muster:

labels:
  - traefik.enable=true
  - traefik.docker.network=frontend_net
  - traefik.http.routers.<name>.rule=Host(`<subdomain>.kaleschke.info`)
  - traefik.http.routers.<name>.entrypoints=websecure
  - traefik.http.routers.<name>.tls=true
  - traefik.http.routers.<name>.tls.certresolver=le
  - traefik.http.services.<name>.loadbalancer.server.port=<interner-port>

Zusatz für Admin-Dienste

  - traefik.http.routers.<name>.middlewares=dashboard-auth@file,secure-headers@file

Regeln

  • traefik.docker.network immer explizit auf frontend_net — nie auf backend_net oder anderen Netzen!
  • keine yourdomain.tld-Platzhalter
  • certresolver immer le
  • tls=true immer explizit setzen — ohne diese Zeile kein TLS
  • wenn Traefik aktiv ist, werden direkte Host-Ports entfernt
  • Admin-Dienste niemals ohne Middleware veröffentlichen
  • File-Provider nur noch für: middlewares.yml (Auth + Headers) — keine Service-Routen mehr via File-Provider

9. Migrationsstrategie (Blöcke AF)

Letzte Aktualisierung: 2026-03-28

Block A — Quick Wins ABGESCHLOSSEN

[x] restart: unless-stopped für alle Container gesetzt
[x] vaultwarden ADMIN_TOKEN-Doppelpräfix korrigiert
[x] backrest DNS-Hardcoding entfernt
[x] diun ↔ gotify Erreichbarkeit hergestellt
[x] leere Netzwerke entfernt: br0, immich_net, kopia_default, netbox_default, diun_default
[x] anonyme/verwaiste Volumes bereinigt (9 anonyme + immich_model-cache alt)
[x] scanopy komplett entfernt (3 Container + 2 Volumes + Netz)

Block B — Kritische Kernmigrationen ABGESCHLOSSEN

[x] vaultwarden - aus bridge, in frontend_net - Host-Port entfernt - ADMIN_TOKEN_FILE aktiv - Traefik aktiv
[x] postgresql17 - Port 5432 entfernt - nur backend_net - POSTGRES_PASSWORD_FILE aktiv
[x] diun - frontend_net beigetreten - gotify via Container-Name (http://gotify:80) erreichbar - Token via Portainer ENV
[x] mealie-postgres - aus frontend_net raus - nur mealie_mealie_internal

Block C — Frontend-Stack finalisieren WEITGEHEND ABGESCHLOSSEN

[x] gotify - traefik.enable=true - gotify.kaleschke.info - Port entfernt
[x] paperless-ngx - traefik.enable=true - paperless.kaleschke.info - Port entfernt - tls=true ergänzt
[x] Paperless-AI - traefik.enable=true - aktiv
[x] PortainerCE - traefik.enable=true - Middleware aktiv - direkte Ports entfernt
[x] Dozzle - traefik.enable=true - Middleware aktiv - direkte Ports entfernt
[x] dashdot - traefik.enable=true - Middleware aktiv - direkte Ports entfernt
[x] UptimeKuma - traefik.enable=true - uptime.kaleschke.info - Port entfernt - Middleware aktiv
[x] filebrowser - aus bridge → frontend_net - traefik.enable=true - files.kaleschke.info - Port entfernt - Middleware aktiv
[x] scrutiny - aus bridge → frontend_net - traefik.enable=true - scrutiny.kaleschke.info - Ports entfernt - Middleware aktiv - als Git-Stack migriert
[x] gitea - traefik.enable=true - git.kaleschke.info - SSH-Port 222 bleibt (Ausnahme dokumentiert)
[x] backrest - traefik.docker.network=frontend_net korrigiert (war backend_net — Routing-Bug)
[x] Traefik File-Provider bereinigt - immich.yml, gitea.yml, mealie.yml, scrutiny.yml, vaultwarden.yml.bak gelöscht
[x] immich Bad Gateway behoben - Traefik nutzt jetzt immich@docker statt immich@file
[ ] luckyBackup - aus bridge → frontend_net - traefik.enable=true - Middleware - Port entfernen
[ ] theme-park - nur wenn wirklich benötigt veröffentlichen - direkte Ports 80/443 entfernen

Block D — Bridge-/Dockerman-Container in Compose

[x] vaultwarden ✅
[x] postgresql17 ✅
[x] mail-archiver ✅
[x] scrutiny ✅ (Git-Stack)
[x] filebrowser ✅
[ ] luckyBackup
[ ] Stash
[ ] Tailscale-Docker
[ ] netdata
[ ] Plex-Media-Server
[ ] binhex-official-pihole

Block E — Secrets-Migration

[x] vaultwarden → ADMIN_TOKEN_FILE ✅
[x] postgresql17 → POSTGRES_PASSWORD_FILE ✅
[x] mail-archiver → Portainer ENV (${MAILARCHIVER_AUTH_PASSWORD}) ✅
[x] mealie → Portainer ENV (kein _FILE-Support) ✅
[x] mealie-postgres → Portainer ENV (kein _FILE-Support) ✅
[x] gotify → GOTIFY_DEFAULTUSER_PASS_FILE ✅
[x] diun → GOTIFY_TOKEN via Portainer ENV ✅ (Container entfernt)
[x] paperless-ngx → Portainer ENV (${PAPERLESS_DBPASS}) ✅
[x] code-server → PASSWORD_FILE ✅
[x] immich_server → Portainer ENV (${IMMICH_DB_PASSWORD}) ✅
[x] immich_postgres → POSTGRES_PASSWORD_FILE ✅
[ ] immich_redis → anonymes Volume → named volume
[ ] luckyBackup → Secrets prüfen

Block F — Feinschliff / Hardening

[ ] immich_default - internal: true setzen (kurzer Downtime nötig)
[ ] immich_redis - anonymes Volume → named volume in Compose
[ ] immich_server - anonymes Volume prüfen und benennen
[ ] backrest - /mnt/user doppelt gemountet (einmal ro, einmal rw) - rw-Mount auf konkrete Pfade einschränken
[ ] filebrowser - /mnt/user:/srv ist sehr breit - auf /mnt/user/documents:/srv einschränken wenn möglich
[ ] Redis - optional named volume
[ ] netdata - leere CLAIM-Vars entfernen
[ ] scrutiny - später prüfen, ob privileged reduziert werden kann
[ ] Tailscale-Docker - später prüfen, ob TS_USERSPACE/privileged bereinigt werden kann
[ ] Pi-hole - zuletzt konsolidieren, nicht als Erstprojekt
[ ] PortainerCE - abschalten nach vollständiger Komodo-Übernahme

10. Bekannte Ausnahmen und Begründungen

Container Ausnahme Begründung
traefik Host-Ports 80/443 zentraler Reverse Proxy
Tailscale-Docker host, aktuell privileged bestehender VPN-Zugang; Umstellung nur kontrolliert
binhex-official-pihole host DNS-/DHCP-naher Sonderfall
Plex-Media-Server host Discovery / mDNS / Plex GDM
netdata host + zusätzliche Rechte Host-Metriken
Glances host Host-Metriken
netalertx host + Netzwerksicht ARP / Netzwerkscan
scrutiny privileged: true SMART-Datenzugriff auf Laufwerke
Komodo Docker-Socket Zugriff Management-UI; Stack-Deployments benötigen Socket
PortainerCE Docker-Socket nicht dogmatisch :ro Legacy-UI; wird durch Komodo abgelöst
gitea SSH-Port 222 direkt gebunden Git-SSH-Zugang; kein HTTP-Proxy für SSH möglich
ddns-updater bleibt in frontend_net statt backend_net braucht Internetzugang für Cloudflare-API; backend_net ist internal: true
mail-archiver frontend_net + backend_net braucht Internetzugang für IMAP-Abruf (GMX, Gmail) und DB-Zugang

11. Projektorganisation und Arbeitsmodus

11.1 Unser Arbeitsprinzip

Dieses Projekt wird blockweise umgesetzt, nicht wild containerweise.

11.2 Reihenfolge der Umsetzung

Sprint Inhalt Status
Sprint 1 Quick Wins + vaultwarden Abgeschlossen
Sprint 2 postgresql17 + diun/gotify Abgeschlossen
Sprint 3 mealie / mealie-postgres + mail-archiver Abgeschlossen
Sprint 4 Frontend-Stack (paperless, Portainer, Dozzle, dashdot, scrutiny, filebrowser, gitea, UptimeKuma) + Traefik File-Provider Bereinigung + Komodo Einführung Abgeschlossen
Sprint 5 Compose-Migration Dockerman-Container (luckyBackup, Stash, Tailscale, netdata, Plex, Pi-hole) + Portainer abschalten 🔄 In Bearbeitung
Sprint 6 Hardening / Secrets / Volumes / Sonderfälle (immich_default, Volumes, Mounts) Offen

11.3 Regel für jede Änderung

Jeder Sprint folgt demselben Schema:

  1. Zielbild in diesem Dokument prüfen
  2. nur den aktuellen Block anfassen
  3. Compose-Datei ändern
  4. deployen
  5. testen
  6. dokumentieren / abhaken
  7. erst dann nächster Schritt

11.4 Source-of-Truth-Hierarchie

  1. Dieses Dokument
  2. Compose-Dateien
  3. operative Checklisten
  4. ad-hoc Notizen / Chat

12. Nutzung mit KI / Kontext-Regel

Wenn mit einer KI gearbeitet wird, gilt immer:

„Lies zuerst HOMELAB_ARCHITECTURE_MASTER_V2.md, dann beantworte meine Frage."

Damit ist sofort klar:

  • welche Netze Standard sind
  • welche Container wohin gehören
  • welche Dienste öffentlich sein dürfen
  • welche Dienste nur intern/VPN-only sind
  • welche Migrationen noch offen sind
  • welche Ausnahmen bewusst dokumentiert sind

13. Betriebserfahrungen und Entscheidungs-Log

Traefik — Wechsel zu reinen Docker-Labels (2026-03-28)

Die statischen File-Provider-Konfigurationen in /mnt/user/appdata/traefik/dynamic/ wurden vollständig bereinigt:

  • Gelöscht: immich.yml, gitea.yml, mealie.yml, scrutiny.yml, vaultwarden.yml.bak
  • Verbleibend (notwendig): middlewares.yml, tls.yml, usersfile

Hintergrund: Die alten File-Provider-Configs haben @file-Routen mit @docker-Routen konkurrieren lassen. In Traefik v3 gewinnt der File-Provider und hat z.B. Immich auf die falsche IP geroutet (192.168.178.58:2283, Port nicht am Host exponiert → Bad Gateway). Nach Löschung der statischen Configs läuft Traefik ausschließlich auf Docker-Labels. Immich, Gitea, Mealie und Scrutiny wurden damit sofort korrekt über ihre Docker-Labels geroutet.

Regel: Neue Dienste ausschließlich via Docker Compose Labels konfigurieren. Keine neuen .yml-Dateien im dynamic/-Verzeichnis für Service-Routen anlegen.

Komodo — Ablösung von Portainer als Stack-Manager (2026-03-28)

Komodo ist nun der primäre GitOps-Stack-Manager:

  • Komodo Core läuft als Docker-Stack (ops/komodo/docker-compose.yml)
  • Komodo Periphery läuft auf dem Unraid-Host für direktes Server-Management
  • Stacks werden via Git (Gitea) synchronisiert und über Komodo deployed
  • Portainer CE läuft noch als Legacy-UI und wird in Sprint 5 abgeschaltet

Vorteil gegenüber Portainer: Sauberer GitOps-Flow ohne Web-Editor; alle Stack-Änderungen laufen über Git.

Secrets in Portainer / Komodo Stacks

Bei Deployments über Komodo/Portainer gilt:

  • Host-Pfade in env_file (z.B. /mnt/...) sind nicht verfügbar — Container hat keinen Zugriff
  • env_file ist für Secrets ungeeignet in diesem Setup

Standardlösung: Stack Environment Variables + ${VARIABLE_NAME} in der Compose

POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

→ kein Secret im Git, funktionierender Deployment-Flow

Umgang mit _FILE Variablen

Nicht alle Container unterstützen _FILE-basierte Secrets:

Container _FILE Support
Vaultwarden ja
PostgreSQL ja
Gotify ja (GOTIFY_DEFAULTUSER_PASS_FILE)
code-server ja (PASSWORD_FILE)
Mealie nein → Stack ENV
diun nein für Token → Stack ENV (Container entfernt)
paperless-ngx nein für DB-Pass → Stack ENV

Regel: Wenn _FILE nicht unterstützt wird → Stack Environment Variable

diun — Entfernung (2026-03-28)

diun (Docker Image Update Notifier) wurde deinstalliert:

  • Stack gelöscht
  • Orphan-Netzwerk diun_diun_default bereinigt
  • Repo-Eintrag infra/diun/ aus Git entfernt

Hintergrund: Update-Monitoring kann über Komodo's eingebaute Update-Notifications abgedeckt werden.

ddns-updater — Netz-Ausnahme

ddns-updater hat keine Web-UI, würde laut Schema in backend_net gehören — bleibt aber bewusst in frontend_net.

Begründung: backend_net ist internal: true → kein Internetzugang. ddns-updater muss die Cloudflare-API erreichen. frontend_net ist hier die einzig funktionierende Option. → Dokumentierte Ausnahme in Kapitel 10.

Netzwerk-Standard für Apps mit Datenbanken

  • App → frontend_net + internes Netzwerk
  • Datenbank → nur internes Netzwerk (internal: true)

Beispiel (Mealie):

  • mealiefrontend_net + mealie_mealie_internal
  • mealie-postgres → nur mealie_mealie_internal → Datenbank vollständig isoliert, keine externe Erreichbarkeit möglich

Migrations-Standard für kritische Services

Bei Änderungen an Datenbanken oder Core-Services:

  1. Backup erstellen (pg_dumpall)
  2. aktuellen Zustand sichern (docker inspect)
  3. neue Compose in Git/Komodo definieren
  4. Container stoppen & entfernen
  5. neuen Stack deployen
  6. Funktion prüfen (Logs + abhängige Services) → Daten liegen im Volume und bleiben erhalten — Container sind austauschbar

mail-archiver — Hybrid-Dienst

mail-archiver benötigt beide Netze:

  • backend_net → Verbindung zu postgresql17
  • frontend_net → IMAP-Abruf von externen Mailservern (GMX, Gmail) + DNS-Auflösung → Kein reiner Backend-Dienst, sondern Hybrid mit externem Internetzugriff

Schlussformel

Dieses Dokument ist keine lose Notiz, sondern das operative Masterdokument für die Docker- und Zugriffsarchitektur des Homelabs.

Zielbild in einem Satz: frontend_net für alle Web-UIs und Dienste mit Internetbedarf, backend_net für interne Backends, app-interne Netze nur wenn technisch nötig, Tailscale für Remote-Admin-Zugriff, Traefik als einziger Web-Einstieg (100% Docker-Labels), Komodo als GitOps-Stack-Manager, keine produktiven bridge-Container mehr.