717 lines
26 KiB
Markdown
717 lines
26 KiB
Markdown
# 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
|
||
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-a-f)
|
||
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 |
|
||
| 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-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
|
||
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ä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; **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)
|
||
|
||
```text
|
||
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:
|
||
|
||
- `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**:
|
||
|
||
- `PortainerCE`
|
||
- `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 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?
|
||
- **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_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?
|
||
- **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
|
||
- `⏳` = 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:
|
||
|
||
```yaml
|
||
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
|
||
```yaml
|
||
- 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
|
||
|
||
---
|
||
|
||
## 9. Migrationsstrategie (Blöcke A–F)
|
||
|
||
**Letzte Aktualisierung:** 2026-03-26
|
||
|
||
### 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] 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
|
||
|
||
```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
|
||
```
|
||
|
||
### Block C — Frontend-Stack finalisieren
|
||
|
||
```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] 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
|
||
|
||
```text
|
||
[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
|
||
|
||
```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] 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
|
||
|
||
```text
|
||
[ ] 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:
|
||
|
||
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
|
||
|
||
### 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_file` ist für Secrets ungeeignet in diesem Setup
|
||
|
||
**Standardlösung:** Portainer Environment Variables + `${VARIABLE_NAME}` in der Compose
|
||
|
||
```yaml
|
||
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=true` existiert **nicht** — diese Variable gibt es in v4 nicht, führt zu `field not found` Fehler
|
||
- `DIUN_NOTIF_GOTIFY_TOKEN_FILE` wird **nicht** unterstützt — Token muss direkt gesetzt werden
|
||
- Gotify ist automatisch aktiv wenn `DIUN_NOTIF_GOTIFY_ENDPOINT` und `DIUN_NOTIF_GOTIFY_TOKEN` gesetzt sind
|
||
- `DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT=true` nötig damit alle Container überwacht werden (sonst nur Container mit `diun.enable=true` Label)
|
||
|
||
**Korrekte Minimal-Konfiguration:**
|
||
```yaml
|
||
environment:
|
||
- DIUN_PROVIDERS_DOCKER=true
|
||
- DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT=true
|
||
- DIUN_NOTIF_GOTIFY_ENDPOINT=http://gotify:80
|
||
- DIUN_NOTIF_GOTIFY_TOKEN=${GOTIFY_TOKEN} # via Portainer ENV
|
||
```
|
||
|
||
---
|
||
|
||
### ddns-updater — Netz-Ausnahme
|
||
|
||
`ddns-updater` 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_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/Portainer 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, keine produktiven `bridge`-Container mehr. |