kohlmajer.de



Ein Heimserver mit Docker

— zuletzt geändert am
1806 Wörter, Lesezeit 11 min

Die Heizungssteuerung, die Kameras, der Kalender, die Messwerte aus dem Haus — das alles lief hier über Jahre gewachsen auf irgendwelchen Geräten, halb dokumentiert und mit dem unguten Gefühl, dass ein Ausfall eine Nachmittagsbeschäftigung wird. Im Januar habe ich das zusammengeräumt: ein Rechner im Haus, Docker darauf, jeder Dienst in einem Container.

Der Grund, warum das hier steht und nicht bei einem Anbieter, ist unspektakulär: die Heizung muss auch dann regeln, wenn das Internet weg ist. Nach der Installation unserer LG Therma V Wärmepumpe im August 2024 habe ich mich mehrere Monate mit der LG-Steuerung und der Anbindung an HomeAssistant beschäftigt. Nach dem Umstieg auf diesen Docker Host habe ich die Regelung auf eine Zustandsmaschine (endlicher Automat, finite state machine) umgebaut. Das wird später nochmal in einem separaten Post behandelt.

Dass die Kamerabilder und die Verbrauchsdaten damit ebenfalls im Haus bleiben, ist ein willkommener Nebeneffekt 😊.

Die öffentliche Seite, die Sie gerade lesen, läuft übrigens ausdrücklich nicht auf diesem Server, sondern auf einem gemieteten virtuellen Server — siehe Webseiten mit Hugo. Was ins Internet gehört, und was nicht, sind zwei verschiedene Maschinen.

Ein Host, viele Container

Auf dem Rechner läuft ein schlankes Debian-Linux und sonst fast nichts: Docker, ein paar Skripte, ein SSH-Zugang. Alles Weitere steckt in Containern, und jede Gruppe zusammengehöriger Container ist ein »Stack« mit einer eigenen docker-compose.yaml.

Der wichtige Teil daran ist: die Wahrheit steht in den Compose-Dateien, nicht in irgendeiner Oberfläche. Wer einen Stack nur zusammenklickt, hat ihn nicht dokumentiert, sondern nur gestartet — und fängt beim Neuaufbau nach einem Ausfall von vorn an. Mehr als diese drei Befehle brauche ich im Alltag praktisch nicht:

docker ps                         # was läuft gerade
docker logs <container> --tail 100 -f
cd /pfad/zum/stack && docker compose up -d

Was gerade läuft, im Einzelnen.

Home Assistant

Der mit Abstand größte Stack, und der einzige, bei dem es wirklich darauf ankommt: Fällt er aus, wird das Haus kalt. Sieben Container, die zusammengehören und deshalb auch zusammen in einer Compose-Datei stehen — sie teilen sich ein eigenes Docker-Netz und finden einander darüber unter ihrem Namen, ohne dass ein einziger Port nach außen offen sein muss.

homeassistant

Die Hausautomation selbst: Sie sammelt alle Messwerte ein und ist die Oberfläche, auf die ich täglich schaue. An ihr hängen die Wärmepumpe über Modbus, die Photovoltaikanlage, die Firewall, die Kalender für Feiertage und Abfuhrtermine und der Warndienst des Bundes. Ihre Konfiguration liegt nicht in der Oberfläche, sondern in YAML-Dateien, die ich mit einem Skript vom Server hole, lokal bearbeite und wieder hochlade — so hat jede Änderung eine Historie.

Was fehlte, habe ich nachgebaut: iapws95-ha ist eine eigene Integration, die die Stoffwerte von Wasser nach der IAPWS-95-Zustandsgleichung berechnet — Dichte, Wärmekapazität, Enthalpie und einiges mehr, für flüssiges Wasser. Der Anlass war handfest: Meine Wärmepumpe hat keinen Wärmemengenzähler, wohl aber Fühler für Vor- und Rücklauf, den Anlagendruck und den Volumenstrom. Für die Wärmeleistung braucht man daraus Dichte und Wärmekapazität, und beide hängen von Temperatur und Druck ab. Der oft benutzte Festwert — rund 69 W/K bei Litern pro Minute — ist eine Näherung; die Integration rechnet stattdessen bei jedem Messwert richtig, und daraus fällt die Arbeitszahl ganz nebenbei mit ab.

appdaemon

AppDaemon führt Python-Programme aus, die auf dieselben Sensoren und Schalter zugreifen wie Home Assistant. Hier läuft die Regelung der Wärmepumpe als Zustandsmaschine — alles, was mit Automatisierungen in YAML zwar möglich, aber nicht mehr lesbar wäre: Heizkurve, Mischerregelung, Verdichterschutz, Pufferladung aus dem PV-Überschuss.

Dass das ein eigener Container ist, hat sich als Segen erwiesen: Ich kann die Regelung dutzendfach neu starten, ohne Home Assistant anzufassen.

Der Code liegt offen: ha-heatpump-fsm. Die Zustandsmaschine kennt acht Zustände — heizen, Puffer entladen, Warmwasser, Pufferladung aus PV-Überschuss, Heizstab-Boost, Zwangslauf zum Verdichterschutz, Schutzpause, Ruhe — und alle Parameter lassen sich im Betrieb am Dashboard verstellen, ohne die App neu zu starten. Eingebaut sind außerdem ein Not-Aus, der bei hektischem Hin und Her zwischen Zuständen selbst abschaltet, und ein Trockenlauf-Modus, in dem alles gerechnet, aber nichts geschaltet wird — beim ersten Einsatz an einer echten Heizung sehr zu empfehlen 😊.

mosquitto

Der MQTT-Broker, sozusagen das schwarze Brett des Hauses: Jeder darf anschreiben, jeder darf mitlesen, und niemand muss wissen, wer sonst noch am Brett steht. Er selbst kann nichts außer Nachrichten weiterreichen — genau deshalb ist er so nützlich.

zigbee2mqtt

Übersetzt die Zigbee-Funkgeräte im Haus — Fensterkontakte, Schalter, Thermometer — auf MQTT, und zwar über einen USB-Stick direkt. Damit brauche ich für jeden Hersteller keine eigene Bridge und für keinen einen Cloud-Zugang: Der Taster an der Wand redet mit dem Server im selben Haus, nicht mit einem Rechenzentrum.

esphome

Für die selbstgebauten Sensoren: ESPHome übersetzt eine handvoll Zeilen YAML in eine Firmware für einen ESP-Mikrocontroller und spielt sie über WLAN auf. So hängen die Temperaturfühler des Pufferspeichers, der Stellantrieb des Heizungsmischers und die Brunnenpumpe am System — Bauteile für ein paar Euro, die ohne diesen Container Bastelei mit Kabel und Laptop wären.

postgres

Die Datenbank, in der die Messwerte landen; ohne sie wäre nach ein paar Tagen Schluss mit der Historie. Es ist PostgreSQL mit zwei Erweiterungen: TimescaleDB für Zeitreihen — die Messwerte des Hauses sind nichts anderes — und PostGIS für Geodaten.

Das Abbild dazu baue ich selbst: postgres-postgis-timescaledb, PostgreSQL, PostGIS und TimescaleDB auf einem schlanken Alpine-Unterbau. Der Grund ist banal, aber lehrreich: Fertige Abbilder mit dieser Kombination gibt es fast nur für amd64, und mein gemieteter Server im Internet läuft auf arm64. Zwei Server mit zwei verschiedenen Datenbankständen wollte ich nicht, also entsteht das Abbild für beide Architekturen aus demselben Dockerfile, gebaut von einer GitHub-Action.

Sie ist außerdem der einzige Container, dem ich Sonderbehandlung gebe: von automatischen Updates ausgenommen, im Backup zuerst berücksichtigt. Warum, steht weiter unten.

signal-cli

Der Weg nach draußen für Nachrichten, die mich erreichen müssen: Störung der Wärmepumpe, Aufladen des Pufferspeichers über PV, Unwetterwarnung. Bewusst über Signal und nicht über den Push-Dienst irgendeiner App — auch wenn das der einzige Container ist, der planmäßig mit einem fremden Server spricht.

Frigate

Die Aufzeichnung der Kameras samt Objekterkennung. Sie entscheidet lokal, ob auf einem Bild ein Mensch, ein Auto oder eine Katze zu sehen ist, und legt nur dann etwas ab.

Das ist der Container, bei dem der Heimserver seinen Zweck am deutlichsten zeigt: Kein Bild verlässt das Haus, es gibt kein Abonnement, und wenn ich den Stecker ziehe, ist es wirklich aus.

Grafana

Home Assistant zeigt hervorragend, was gerade ist, und ziemlich mühsam, was im letzten Winter war. Grafana sitzt deshalb direkt auf der Datenbank und beantwortet die langen Fragen: Wie hat sich die Arbeitszahl der Wärmepumpe über die Heizperiode entwickelt, wie viel vom eigenen Strom habe ich tatsächlich selbst verbraucht, wie stark hat die Umstellung der Heizkurve gewirkt.

BookStack

Das Wiki, in dem die Dokumentation des Hauses steht — von der Bedienungsanleitung für die Familie bis zum Notfallhandbuch für den Fall, dass ich nicht erreichbar bin. Warum das ein eigener Abschnitt wert ist, steht weiter unten.

Papra

Das Dokumentenarchiv: eingescannte Rechnungen, Datenblätter, Handwerkerangebote, Wartungsprotokolle. Nichts Spektakuläres, aber es beantwortet die Frage »wo war noch mal die Rechnung von der Wärmepumpe« in zehn Sekunden statt in zwanzig Minuten 😊.

Omada

Der Controller für die WLAN-Zugangspunkte. Er gehört eigentlich nicht zur Haustechnik, aber der Hersteller bietet ihn wahlweise als Cloud-Dienst, als eigenes Kästchen oder eben als Container an — und die dritte Variante spart Gerät und Fremdzugriff. Wichtig ist dabei nur eines: Das WLAN funktioniert weiter, wenn dieser Container steht. Der Controller verwaltet, er vermittelt nicht.

Portainer

Die Weboberfläche über allem: Zustände, Logs, Neustarts an einer Stelle, ohne SSH. Praktisch, wenn etwas klemmt und ich nur das Handy zur Hand habe.

Watchtower

Der Wächter, der nachts nach neuen Abbildern schaut. Bei mir heißt der Stack »Watchdog«, was streng genommen etwas anderes ist — ein Watchdog startet neu, was hängt, ein Watchtower aktualisiert, was veraltet ist. Was er darf und was nicht, steht weiter unten in einem eigenen Abschnitt.

Die Daten liegen verschlüsselt

Alle Container-Volumes liegen auf einem gespiegelten Plattenpaar (RAID 1), und dieser Spiegel ist mit LUKS verschlüsselt. Der Grund ist Ausfallsicherheit (Platte kaputt) und Diebstahl.

Zwei Dinge, die ich dabei gelernt habe:

  • RAID ist kein Backup. Der Spiegel überlebt einen Plattendefekt, aber kein versehentliches rm -rf und keinen kaputten Container-Upgrade — der schreibt seinen Schaden brav auf beide Platten.
  • Der LUKS-Kopf ist der eigentliche Schlüssel. Er steht am Anfang des Datenträgers, ist ein paar Megabyte groß, und ohne ihn ist die Passphrase wertlos; daher separat sichern!

Der Server hängt an einer kleinen Eaton-USV, um einen kurzen Stromausfall zu überbrücken. Geht die USV zu neige, dann fährt der Server brav runter.

Nichts davon steht im Internet

Kein Dienst dieses Servers ist von außen erreichbar, es ist kein Port dorthin weitergeleitet. Wenn ich von unterwegs auf die Hausautomation schaue, baue ich vorher einen WireGuard-Tunnel ins Heimnetz auf; erst dann existiert der Server für mein Handy überhaupt. Der SSH-Zugang nimmt keine Passwörter an, sondern nur einen Schlüssel, der in einem Nitrokey steckt.

Das ist der eigentliche Vorteil des Heimservers gegenüber dem gemieteten: Ein Dienst, der gar nicht ins Internet zeigt, muss auch nicht gegen das Internet abgesichert werden.

Updates: gern automatisch, aber nicht überall

Für die zustandslosen Container zieht ein Wächter-Container nachts neue Abbilder und startet sie neu. Für die Datenbanken habe ich das ausdrücklich abgeschaltet:

labels:
  - "com.centurylinklabs.watchtower.enable=false"

Ein automatisch getauschtes Datenbank-Abbild springt womöglich zwei Hauptversionen weiter, findet ein Datenverzeichnis im alten Format vor und verweigert den Start — im schlechteren Fall wandert es einmal durch die Daten und geht dann nicht mehr zurück. Diese Updates mache ich von Hand, in der Reihenfolge: erst ein Dump, dann das neue Abbild, dann die Erweiterungen nachziehen. Zehn Minuten Arbeit, viermal im Jahr.

Für den Fall, dass ich nicht da bin

Der unangenehmste Teil war nicht die Technik, sondern die Frage, was passiert, wenn im Januar die Heizung steht und ich bin irgendwo auf Dienstreise.

Deshalb läuft auf demselben Server ein kleines Wiki, und darin stehen zwei sehr verschiedene Sorten Text. Die eine ist für die Familie, ohne IT-Vorkenntnisse: Wie rufe ich das Heizungs-Dashboard auf, was bedeutet der Zustand, den es anzeigt, wen rufe ich an, wo ist der Hauptwasserhahn. Die andere ist für jemanden mit Grundkenntnissen, der im Ernstfall vorbeikommt: Wo läuft was, wie komme ich auf den Server, in welcher Reihenfolge starte ich die Dienste wieder, und wie fährt man die Wärmepumpe im Notbetrieb direkt an der LG-Steuerung.

Dass die Dokumentation auf genau dem Server liegt, dessen Ausfall sie beschreibt, ist natürlich doof — sie wird deshalb regelmäßig als PDF exportiert, und die Notfallcheckliste hängt ausgedruckt im Hauswirtschaftsraum. Das Master-Passwort des Passwortverwalters steht auf Papier. Nichts davon ist elegant, aber alles davon funktioniert ohne Strom.

Fazit

Der Server ist recht simpel. Er steht in einer Ecke auf dem Dachboden, macht kaum Lärm, und im Alltag habe ich mit ihm nichts zu tun.

Was ich beim nächsten Mal anders machen würde: zuerst die Dokumentation, dann die Dienste. Ich habe es andersherum gemacht und habe immer noch nicht alles dokumentiert.