26 KiB
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-26 | Aktueller Sprint: 4 (Frontend-Stack) — Sprints 1–3 abgeschlossen
Inhaltsverzeichnis
- Systemüberblick
- Architektur-Prinzipien
- Finales Netzwerk-Zielbild
- Zugangsmodell: Traefik vs. Tailscale
- Globale Sicherheitsregeln
- Einordnungsschema für neue Container
- Container-Zielbild (vollständig)
- Traefik-Label-Standard
- Migrationsstrategie (Blöcke A–F)
- Bekannte Ausnahmen und Begründungen
- Projektorganisation und Arbeitsmodus
- Nutzung mit KI / Kontext-Regel
- Betriebserfahrungen und Entscheidungs-Log
1. Systemüberblick
| Eigenschaft | Wert |
|---|---|
| Host-OS | Unraid |
| Hostname | Kallilabcore |
| Reverse Proxy | Traefik v3 |
| VPN / Remote-Zugang | Tailscale (Tailscale-Docker, host-Netz) |
| DNS-Stack | Pi-hole (host) → Unbound (dns_net) |
| Basis-Domain | kaleschke.info |
| TLS | Let's Encrypt via Cloudflare DNS Challenge |
| Certresolver | le |
| Compose-Standard | Portainer Stacks (Web-Editor oder Git) |
| Zusatz-Tool | Portainer als Verwaltungs-UI |
| 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-Netzbackend_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
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.
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 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änzenprivileged: truenur mit dokumentierter Begründungread_only: trueund Non-Root nur nach getesteter Image-Kompatibilität- Docker-Socket standardmäßig vorsichtig behandeln; PortainerCE ist eine 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 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 (portainer, 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:
homepagevaultwardenmealiepaperless-ngxgotifygitea(Web)immich_servermail-archiverbackrest
4.2 Nicht öffentlich / nur Tailscale oder Traefik + Middleware
Diese Dienste sind keine Public Apps:
PortainerCEDozzleUptimeKumadashdotfilebrowserscrutinyluckyBackupcode-serverTraefik-DashboardnetdataGlancesnetalertx
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
- Keine produktiven Dienste im Docker-Default-
bridge - Keine direkten Host-Ports für Web-UIs außer dokumentierte Ausnahmen
restart: unless-stoppedals Standard- Secrets als Datei /
_FILEoder Portainer Environment Variables mit${VAR} no-new-privileges:trueergänzen, wo praktikabeltraefik.docker.network=frontend_netimmer explizit setzen- Admin-Dienste immer mit
dashboard-auth@file,secure-headers@file - Placeholder-Domains (
yourdomain.tld) sind verboten privileged: truenur mit Begründung- Volume-Mounts so klein und so read-only wie möglich
- Neue Dienste nur via Compose Manager / YAML
- Änderungen immer gegen dieses Dokument prüfen
6. Einordnungsschema für neue Container
Schritt 1 — Hat der Dienst eine Web-UI?
- Ja →
frontend_net - Nein → weiter zu Schritt 2
Schritt 2 — Braucht der Dienst externe Internetverbindungen?
- Ja →
frontend_net(auch ohne Web-UI — z.B.ddns-updater,mail-archiver) - Nein → weiter zu Schritt 3
Schritt 3 — Braucht der Dienst eine DB / Redis / interne Backends?
- Ja → zusätzlich
backend_netoder eigenes app-internes Netz - Nein → nur das funktional nötige Netz
Schritt 4 — Ist es eine Datenbank oder ein Cache?
- Ja → niemals
frontend_net, nurbackend_netoder internes Compose-Netz
Schritt 5 — Ist es ein Admin-/Monitoring-Dienst?
- Ja → wenn Web-UI vorhanden trotzdem
frontend_net, aber nur mit:- Middleware
- keiner direkten Portfreigabe
- Tailscale-only-Charakter
Schritt 6 — Braucht der Dienst Host-/Discovery-/L2-Sicht?
- Ja →
hostnur mit dokumentierter Begründung - Nein → kein
host
Schritt 7 — Braucht die App ein eigenes internes App-Netz?
- Ja → Compose-internes Netz mit
internal: true - Nein → kein weiteres Netz anlegen
Kurzregel
- UI →
frontend_net - Externer Internetzugriff nötig →
frontend_net(auch ohne UI) - DB/Cache →
backend_net - Spezialfall → app-internes Netz
- Host-Zugriff → nur dokumentierte Ausnahme
7. Container-Zielbild (vollständig)
Legende Status:
✅= umgesetzt⏳= noch zu migrieren / zu korrigieren
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 | Middleware-File-Provider sauber pflegen |
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) |
diun |
✅ | frontend_net |
intern | in frontend_net, gotify via Container-Name erreichbar |
— |
7.2 Sicherheit / Identity
| Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte |
|---|---|---|---|---|---|
vaultwarden |
✅ | frontend_net |
Traefik | kein Host-Port, Secret-Datei (ADMIN_TOKEN_FILE) |
— |
7.3 Datenbanken / Caches
| Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte |
|---|---|---|---|---|---|
postgresql17 |
✅ | backend_net |
intern | kein Host-Port, POSTGRES_PASSWORD_FILE |
— |
Redis |
✅ | backend_net |
intern | intern-only Cache | optional named volume statt anonym |
mealie-postgres |
✅ | mealie_mealie_internal |
intern | isoliert, nie frontend_net |
— |
immich_postgres |
✅ | immich_default |
intern | intern-only | immich_default → internal: true ausstehend |
immich_redis |
⏳ | immich_default |
intern | intern-only | anonymes Volume → named volume |
7.4 Öffentliche Apps
| Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte |
|---|---|---|---|---|---|
paperless-ngx |
✅ | frontend_net, backend_net |
Traefik | aktiv via paperless.kaleschke.info |
— |
Paperless-AI |
✅ | frontend_net |
Traefik | aktiv | — |
mealie |
✅ | frontend_net, mealie_mealie_internal |
Traefik | sauber getrennte App/DB-Struktur | — |
gotify |
✅ | frontend_net |
Traefik | aktiv via gotify.kaleschke.info, kein Host-Port |
— |
gitea |
✅ | frontend_net |
Traefik + SSH | Web via Traefik, SSH-Port 222 bleibt | — |
immich_server |
✅ | immich_default, frontend_net |
Traefik | aktiv via immich.kaleschke.info |
immich_default → internal: true ausstehend |
immich_machine_learning |
✅ | immich_default |
intern | bleibt intern | — |
7.5 Admin / Operations
| Container | Status | Soll-Netz(e) | Finaler Zugang | Finaler Sollzustand | Offene Punkte |
|---|---|---|---|---|---|
code-server |
✅ | frontend_net |
Traefik + Middleware | PASSWORD_FILE aktiv |
— |
PortainerCE |
✅ | 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) |
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 |
— |
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 | — |
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 |
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.networkimmer explizit auffrontend_net— nie aufbackend_netoder anderen Netzen!- keine
yourdomain.tld-Platzhalter - certresolver immer
le tls=trueimmer explizit setzen — ohne diese Zeile kein TLS- wenn Traefik aktiv ist, werden direkte Host-Ports entfernt
- Admin-Dienste niemals ohne Middleware veröffentlichen
9. Migrationsstrategie (Blöcke A–F)
Letzte Aktualisierung: 2026-03-26
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
[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)
[ ] 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 ✅
[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
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 |
PortainerCE |
Docker-Socket nicht dogmatisch :ro |
Management-UI; Schreiboperationen können nötig sein |
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) |
🔄 In Bearbeitung |
| Sprint 5 | Compose-Migration der Dockerman-Container (luckyBackup, Stash, Tailscale, netdata, Plex, Pi-hole) |
⏳ Offen |
| Sprint 6 | Hardening / Secrets / Volumes / Sonderfälle (immich_default, Volumes, Mounts) |
⏳ Offen |
11.3 Regel für jede Änderung
Jeder Sprint folgt demselben Schema:
- Zielbild in diesem Dokument prüfen
- nur den aktuellen Block anfassen
- Compose-Datei ändern
- deployen
- testen
- dokumentieren / abhaken
- erst dann nächster Schritt
11.4 Source-of-Truth-Hierarchie
- Dieses Dokument
- Compose-Dateien
- operative Checklisten
- 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
Secrets in Portainer Stacks
Bei Deployments über Portainer gilt:
- Host-Pfade in
env_file(z.B./mnt/...) sind nicht verfügbar — Portainer-Container hat keinen Zugriff env_fileist für Secrets ungeeignet in diesem Setup
Standardlösung: Portainer 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 → Portainer ENV |
| diun | ❌ nein für Token → Portainer ENV |
| paperless-ngx | ❌ nein für DB-Pass → Portainer ENV |
Regel: Wenn _FILE nicht unterstützt wird → Portainer Environment Variable
diun — Korrekte Konfiguration (v4.x)
Wichtige Learnings aus der Migration:
DIUN_NOTIFY_GOTIFY=trueexistiert nicht — diese Variable gibt es in v4 nicht, führt zufield not foundFehlerDIUN_NOTIF_GOTIFY_TOKEN_FILEwird nicht unterstützt — Token muss direkt gesetzt werden- Gotify ist automatisch aktiv wenn
DIUN_NOTIF_GOTIFY_ENDPOINTundDIUN_NOTIF_GOTIFY_TOKENgesetzt sind DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT=truenötig damit alle Container überwacht werden (sonst nur Container mitdiun.enable=trueLabel)
Korrekte Minimal-Konfiguration:
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 hat keine Web-UI, würde laut Schema in backend_net gehören — bleibt aber bewusst in frontend_net.
Begründung: backend_net ist internal: true → kein Internetzugang. ddns-updater muss die Cloudflare-API erreichen. frontend_net ist hier die einzig funktionierende Option.
→ Dokumentierte Ausnahme in Kapitel 10.
Netzwerk-Standard für Apps mit Datenbanken
- App →
frontend_net+ internes Netzwerk - Datenbank → nur internes Netzwerk (
internal: true)
Beispiel (Mealie):
mealie→frontend_net+mealie_mealie_internalmealie-postgres→ nurmealie_mealie_internal
→ Datenbank vollständig isoliert, keine externe Erreichbarkeit möglich
Migrations-Standard für kritische Services
Bei Änderungen an Datenbanken oder Core-Services:
- Backup erstellen (
pg_dumpall) - aktuellen Zustand sichern (
docker inspect) - neue Compose in Git/Portainer definieren
- Container stoppen & entfernen
- neuen Stack deployen
- 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 zupostgresql17frontend_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, keine produktiven bridge-Container mehr.