KI-Agent auf der eigenen NAS: Hermes Agent mit Docker, Dashboard und Telegram einrichten

Eine praxisgeprüfte Schritt-für-Schritt-Anleitung, mit den Stolpersteinen aus einem realen Aufbau

Ein fehlender Zeilenumbruch genügte. Hermes konnte die Konfigurationsdatei nicht mehr lesen und fiel unbemerkt auf Standardwerte zurück. Der Container lief weiter, das Dashboard antwortete, nichts blinkte rot nur galten sämtliche Sicherheitsregeln nicht mehr, die eine Stunde zuvor gesetzt worden waren.

Das ist die Sorte Fehler, die dich nicht anschreit. Sie sitzt einfach da und wartet. Genau deshalb zeigt dir diese Anleitung nicht nur, wie du Hermes Agent auf einer Synology-NAS installierst, sondern auch, wie du nach jedem kritischen Schritt nachsiehst, ob Konfiguration, Berechtigungen und Zugriffsschutz wirklich greifen. Das ist der Teil, den Installationsanleitungen gern weglassen – und der Teil, in dem die eigentliche Arbeit steckt.

Keine Panik: Das wirkt komplizierter, als es ist. Du musst kein Linux-Profi sein. Du musst nur bereit sein, ab und zu einen Prüfbefehl abzusetzen, statt zu hoffen.

Um diese Anleitung verständlicher zu machen, begleiten dich drei Personen: Die typischen Büro-Charaktere: Die kompetente IT-Kollegin, der selbsternannte Experte und der ehrliche Anfänger. Diese drei Perspektiven helfen dir, typische Stolperfallen zu erkennen.
Tanja ist die IT-Expertin. Sie weiß, wie es funktioniert, erklärt geduldig, strukturiert und lässt sich von schlechten Ratschlägen nicht aus der Ruhe bringen. Wenn du eine Frage hast, hat Tanja die Antwort.
Bernd ist der selbsternannte „Experte“, der alles besser weiß und meistens falsch liegt. Seine Abkürzungen und sein Halbwissen führen regelmäßig zu Problemen. Er steht für alle gefährlichen Mythen und schlechten Praktiken, die du vermeiden solltest.
Ulf ist der Lernende, genau wie du. Er stellt die Fragen, die dir im Kopf herumschwirren, und braucht manchmal einen Vergleich aus dem Alltag, um IT zu verstehen. Wenn Ulf etwas nicht versteht, ist das völlig in Ordnung, dafür ist Tanja da.

„Und… Action!“

Getesteter Stand

Hermes Agent v2026.7.20 (Release 0.19.0) · DSM 7.3.2 · Synology x86_64 · Installation über Container Manager und Docker Compose · Modellzugang OpenAI Codex via OAuth · Messenger Telegram · Stand der Prüfung: 25. Juli 2026.

Menüführung, Modellnamen, unterstützte Architekturen und Umgebungsvariablen können sich in späteren Releases ändern. Wo eine Angabe versionsabhängig ist, steht das im Text.

Eine Namensverwirrung, gleich zu Beginn aus dem Weg geräumt

Der Agent in dieser Anleitung heißt „Hermes-Bernd“. Der Ordner heißt hermes-bernd, der Container auch, und auf jedem Screenshot steht es ebenfalls.

Bernd: „Sehr gute Wahl. Endlich benennt mal jemand was nach mir.“
Tanja: „Reiner Zufall. Der Name stammt vom Auftraggeber des Projekts, nicht von dir.“
Ulf: „Also ist der Agent nicht… Bernd?“
Tanja: „Der Agent ist Software. Bernd ist Bernd. Verwechsle die beiden nicht, sonst wunderst du dich, warum dein Container plötzlich Kaffee holen geht.“

Damit ist das geklärt. Wo im Text hermes-bernd steht, ist immer der Projektname gemeint. Du kannst ihn selbstverständlich durch etwas anderes ersetzen wichtig ist nur, dass du es dann überall konsequent tust: im Ordnernamen, im Container-Namen und in jedem Pfad. Halbherzig umbenennen ist schlimmer als gar nicht umbenennen.

Warum sich das lohnt

Freitagnachmittag, Großraumbüro. Ulf starrt auf einen Blogartikel über KI-Agenten.
Ulf: „Da steht, so ein Agent macht Sachen von allein. Also wie ein Praktikant, nur ohne Mittagspause?“
Tanja: „Ungefähr. Ein Agent antwortet nicht nur, er handelt. Er liest Dateien, führt Befehle aus, recherchiert im Netz.“
Bernd: „Hab ich längst. Läuft in der Cloud, kostet nix, ich muss mich um gar nichts kümmern.“
Tanja: „Genau. Um gar nichts. Auch nicht darum, wo deine Daten landen.“

KI-Agenten also Systeme, die nicht nur antworten, sondern eigenständig Aufgaben erledigen, Dateien anfassen, Befehle ausführen, im Web recherchieren sind seit ungefähr zwei Jahren das Versprechen der Branche. Viele Agentenplattformen werden dabei vollständig als Cloud-Dienst betrieben: Der Agent selbst, seine Konfiguration und sein Gedächtnis liegen bei einem Anbieter.

Hermes geht einen anderen Weg. Hermes Agent von Nous Research steht unter MIT-Lizenz, ist als Docker-Image verfügbar und lässt sich über ein Web-Dashboard und über Messenger wie Telegram bedienen. Agentenlaufzeit, Konfiguration, Sitzungen und Erinnerungen hostest du selbst – auf dem Gerät, das ohnehin bei dir im Haus steht, etwa einer Synology-NAS, also einem Netzwerkspeicher, wie ihn viele für Backups und Fotos nutzen.

Was das konkret bedeutet, lohnt eine genaue Formulierung, weil hier oft mehr versprochen wird, als zutrifft. Konfiguration, Sitzungsverlauf, Erinnerungen und Protokolle werden lokal auf der NAS gespeichert. Sobald Hermes aber ein Cloud-Modell, eine Websuche, Telegram, Sprachausgabe oder einen Bildanbieter nutzt, werden die für den jeweiligen Vorgang nötigen Daten an diese Dienste übertragen.

Ulf: „Moment. Self-hosted heißt doch, dass gar nichts rausgeht, oder?“
Bernd: „Klar. Steht ja bei dir zu Hause. Wie ein Tresor.“
Tanja: „Nein. Es ist eher wie ein Aktenschrank in deinem Büro, aus dem du regelmäßig Kopien an externe Dienstleister schickst. Der Schrank steht bei dir. Die Kopien sind trotzdem unterwegs.“

Self-hosted heißt hier also vor allem: lokale Kontrolle über Agent, Speicher und Berechtigungen – nicht vollständige Offline-Verarbeitung.

Welche Daten verlassen die NAS?

Modellanbieter (hier OpenAI Codex): Prompts, Gesprächskontext und alles, was der Agent zur Bearbeitung an das Modell schickt – auch Auszüge aus Dateien, die er gerade bearbeitet.
Telegram: sämtliche Nachrichten in beide Richtungen. Telegram-Chats mit Bots sind nicht Ende-zu-Ende-verschlüsselt.
Suchanbieter und aufgerufene Webseiten: Suchbegriffe, Seitenaufrufe, IP-Adresse des Anschlusses.
Browser-Automatisierung: verarbeitet Seiteninhalte und – je nach Nutzung – auch Logins und Cookies.
Sprachausgabe und Bildgenerierung: die jeweiligen Texte beziehungsweise Bildanfragen, sofern ein externer Anbieter gewählt wurde.

Lokal gespeichert ist nicht dasselbe wie lokal verarbeitet. Wer streng vertrauliche Daten vollständig lokal verarbeiten will, braucht nicht nur ein lokales Modell, sondern muss zusätzlich sämtliche externen Dienste und ausgehenden Werkzeugzugriffe abschalten oder kontrollieren – das ist eine andere Baustelle als diese Anleitung.

Was bleibt, ist trotzdem substanziell: die Entscheidung darüber, was der Agent überhaupt darf, welche Werkzeuge er bekommt, wer mit ihm sprechen darf und wo seine Spuren liegen.

Diese Anleitung folgt einem realen Aufbau, der über einen Nachmittag und Abend hinweg entstanden ist. Alles, was hier steht, ist an einem laufenden System überprüft worden – auch die Fehler. Besonders die Fehler.

Das Ergebnis in Kurzform

Bevor du loslegst, solltest du wissen, worauf du dich einlässt. Am Ende dieser Anleitung läuft: ein dauerhaft aktiver Hermes-Container mit automatischem Neustart, ein lokal gespeichertes Datenverzeichnis mit korrekten Besitzrechten, ein passwortgeschütztes Dashboard im LAN, ein Telegram-Bot mit Zugangsliste, OpenAI Codex als Modellzugang, ein durchgesetztes Speicherlimit samt Logrotation – und weder ein veröffentlichter API-Port noch ein eingebundener Docker-Socket.

Nicht gelöst ist damit: Vertraulichkeit gegenüber dem Modellanbieter, HTTPS im lokalen Netz, eine Beschränkung ausgehender Verbindungen, der tatsächliche Werkzeugumfang auf dem Telegram-Kanal, der praktische Nachweis der Genehmigungspflicht und ein externes Backup.

Bernd: „Das ist ja gar nicht fertig.“
Tanja: „Doch. Es ist fertig genug zum Betreiben und ehrlich genug, dass du weißt, wo du nachlegen musst. Das ist mehr, als die meisten Anleitungen dir mitgeben.“

Voraussetzungen

Bernd: „Ich hab hier noch eine alte NAS im Schrank. Die reicht bestimmt.“
Tanja: „Welches Modell?“
Bernd: „Weiß ich nicht. Steht was mit DS und Zahlen drauf.“
Tanja: „Dann fangen wir genau da an.“

Diese Anleitung entstand auf einer x86-DiskStation der Mittelklasse, aber nichts daran ist an ein bestimmtes Modell gebunden. Hermes stellt erstaunlich bescheidene Ansprüche – die eigentliche Hürde ist nicht die Rechenleistung, sondern ob deine DiskStation überhaupt Container ausführen darf. Der Reihe nach.

Läuft Container Manager auf meiner DiskStation?

Das ist die erste und wichtigste Frage, denn sie entscheidet alles Weitere. Container Manager ist Synologys grafische Docker-Oberfläche. Docker wiederum isoliert Prozesse, Dateisystembereiche und Netzwerke voneinander – die Programme laufen in Containern und kommen sich gegenseitig und dem restlichen System weitgehend nicht in die Quere.

Ulf: „Container. Wie beim Hafen?“
Tanja: „Genau das Bild ist gemeint. Jedes Programm bekommt seine eigene Kiste, mit allem drin, was es braucht. Du stellst die Kiste ab, sie läuft, und sie fasst die Kisten daneben nicht an.“
Ulf: „Also komplett dicht?“
Tanja: „Nein, und das ist der wichtige Teil. Alle Kisten stehen auf demselben Schiff und teilen sich denselben Motor.“

„Weitgehend“ ist dabei wörtlich zu nehmen: Container teilen sich den Kernel des Hosts und sind deshalb keine vollständige Sicherheitsgrenze wie eine virtuelle Maschine. Kritisch wird es vor allem bei privilegierten Containern und bei einem eingebundenen Docker-Socket – zu Letzterem gleich mehr.

Drei Bedingungen müssen zusammenkommen.

Erstens die DSM-Version. Ab DSM 7.2 heißt das Paket „Container Manager“ und bringt Unterstützung für Docker Compose mit. Auf DSM 7.1 und älter heißt es schlicht „Docker“ – die Anleitung funktioniert dort im Kern auch, aber der Projekt-Assistent aus Phase 7 fehlt, du musst die Compose-Datei über die Kommandozeile starten.

Zweitens der Prozessor. Offiziell unterstützt Synology x86_64 und armv8 (64 Bit). Modelle mit 32-Bit-ARM-Prozessor (armv7) fallen raus, endgültig.

Drittens – und das ist die ärgerlichste Hürde – Synologys eigene Modellliste. Entscheidend ist nämlich nicht allein die Architektur, sondern ob Synology das Paket für das konkrete Modell freigibt. Einige 64-Bit-ARM-Geräte sind ausgeschlossen, obwohl sie technisch könnten: DS118, DS218, DS218play, DS418, DS418j, DS119j und RS819 teilen sich den Prozessor mit unterstützten Modellen wie der DS220j, dürfen Container Manager aber nicht installieren (Stand der Prüfung: Juli 2026, Quelle: Community-Projekt ContainerManager_for_all_armv8). Für diese Geräte existieren Community-Umwege, die hier ausdrücklich nicht Gegenstand sind.

Bernd: „Ich such mir einfach ein Modell aus der Liste raus, das ähnlich klingt.“
Tanja: „Oder du machst es in zehn Sekunden richtig.“

Weil solche Listen veralten, ist die verlässlichste Prüfung ohnehin die schnellste.

Fakten-Check: Läuft es auf deinem Gerät?

Paket-Zentrum öffnen und nach „Container Manager“ suchen.
Erscheint es mit Installations-Button → dein Gerät ist freigegeben, weiter geht’s.
Erscheint es nicht → an dieser Stelle ist Schluss, unabhängig davon, was irgendeine Liste behauptet. Prüfe dann, ob deine DSM-Version älter als 7.2 ist; dann suche stattdessen nach dem Paket „Docker“.

Was Hermes tatsächlich braucht

Die offizielle Dokumentation zum hier verwendeten Versionsstand nennt konkrete Zahlen:

RessourceMinimumEmpfohlen
Arbeitsspeicher1 GB2–4 GB
CPU-Kerne12
Plattenplatz (Datenverzeichnis)500 MB2 GB und mehr

Der entscheidende Satz steht direkt darunter: „Browser automation (Playwright/Chromium) is the most memory-hungry feature. If you don’t need browser tools, 1 GB is sufficient. With browser tools active, allocate at least 2 GB.“

Übersetzt: Der Speicherhunger kommt fast vollständig von der Browser-Automatisierung – also davon, dass der Agent selbst Webseiten aufrufen und bedienen kann. Ohne dieses Werkzeug reicht ein Gigabyte. Mit ihm mindestens zwei.

Ulf: „Ein Gigabyte? Mein Handy hat mehr. Dann läuft das doch überall.“
Tanja: „Vorsicht, das ist die Zahl für den Container allein. DSM will auch noch leben, und deine anderen Dienste ebenfalls.“

Wichtig ist genau diese Lesart: Das sind Angaben zum Bedarf des Containers, nicht zur empfohlenen Gesamtausstattung der NAS. Übertragen auf konkrete Geräte heißt das:
Unter 1 GB Gesamt-RAM – nicht geeignet. Eine DS220j etwa hat ab Werk 512 MB und liegt damit schon unter dem Hermes-Minimum, obwohl Container Manager auf ihr installierbar wäre.
1 GB Gesamt-RAM – rechnerisch am Minimum, praktisch kaum sinnvoll. Die DS223j wird mit 1 GB ausgeliefert. Was davon nach DSM übrig bleibt, reicht für Hermes nicht verlässlich.
2 GB Gesamt-RAM – eingeschränkter Probebetrieb. DS224+ und DS423+ kommen ab Werk mit 2 GB. Browser-Automatisierung abschalten, Container-Limit auf 1 GB. Zum Ausprobieren brauchbar, für den Dauerbetrieb zu knapp.
4 bis 6 GB – praxistauglich. Bei vielen Plus-Modellen erst nach einer Speichererweiterung: Die DS224+ etwa lässt sich offiziell von 2 auf 6 GB aufrüsten. Container-Limit 2 GB, Browser-Werkzeuge nutzbar.
8 GB und mehr – komfortabel. mem_limit: 4g wie im Beispiel unten. Die Synology-Community empfiehlt für Docker-Betrieb generell mindestens 8 GB Gesamtsystem-Speicher – nicht wegen Hermes, sondern weil DSM mit knappem Speicher anfängt, auf die Festplatte auszulagern, und dann alles zäh wird.
Plattenplatz: Das entpackte Image belegt rund 2,4 GB. Das Datenverzeichnis startet klein, wächst aber mit Sitzungen, Erinnerungen und Skills – nach dem ersten Betriebstag waren es im Beispielprojekt bereits 146 MB. Wer Browser-Werkzeuge nutzt, sollte 10 GB einplanen; ohne sie reichen 5 GB bequem.
Prozessorleistung ist überraschend nachrangig. Die Denkarbeit erledigt ein Modell in der Cloud, die NAS wartet die meiste Zeit auf Antworten – die Dokumentation nennt die Last treffend „mostly I/O bound“. Ein einzelner Kern genügt für gelegentliche Nutzung. Zwei helfen, wenn der Browser Seiten rendert.

Ulf: „Also ist meine NAS quasi der Torwart, der die meiste Zeit rumsteht und wartet, bis mal ein Ball kommt?“
Tanja: „Sehr gutes Bild. Und die Cloud ist der Rest der Mannschaft, die das Spiel macht.“

Und wenn meine DiskStation einen ARM-Prozessor hat?

Hier hat sich die Lage im Sommer 2026 geändert, und die Antwort ist erfreulicher als erwartet. Das offizielle Image wird inzwischen mehrarchitektonisch gebaut: Für den hier verwendeten Versionsstand liegen auf Docker Hub sowohl ein linux/amd64– als auch ein linux/arm64-Abbild bereit, beide am selben Tag veröffentlicht, beide rund 940 MB komprimiert. Auf einer 64-Bit-ARM-DiskStation zieht Docker automatisch die passende Variante – du musst nichts weiter tun.

Zwei Vorbehalte gehören dazu. Erstens: Der ursprüngliche Wunsch nach ARM64-Unterstützung wurde vom Projekt formal als „closed as not planned“ abgeschlossen, während die Bauskripte die Architektur inzwischen trotzdem mitliefern – ein Image ist also da, ein zugesagtes Feature ist es nicht. Zweitens, und praktisch wichtiger: Der Ablauf dieser Anleitung wurde ausschließlich auf x86_64 durchgespielt. Dass ein ARM64-Image existiert, beweist noch nicht, dass jede Werkzeug- und Browser-Abhängigkeit auf jedem Synology-ARM-Modell rund läuft. Wer auf ARM baut, sollte Browser-Automatisierung und optionale Werkzeuge zusätzlich einzeln prüfen; wer keine Lust auf Überraschungen hat, bleibt bei x86_64.

Ein Detail, das die Dokumentation selbst für NAS-Betreiber hervorhebt

Bemerkenswert: Die offizielle Docker-Dokumentation hat einen eigenen Absatz für NAS-Systeme, in dem Synology namentlich vorkommt. Sinngemäß steht dort, dass der Container bei eingehängten Netzwerkspeicher-Verzeichnissen andere Besitzrechte erwartet und man deshalb PUID/PGID passend zum Host-Benutzer setzen soll.

Genau darum dreht sich Phase 1 dieser Anleitung – und die Falle in Phase 3, die im Beispielprojekt eine Stunde gekostet hat. Es ist also ein bekanntes Problem. Nur eben eines, dessen Tragweite man erst versteht, wenn es einen erwischt hat. (In dieser Anleitung heißen die Variablen HERMES_UID/HERMES_GIDPUID/PGID sind bestätigte Aliase, beide funktionieren.)

Was du sonst noch brauchst

Zugänge. Ein Konto beim Modellanbieter, hier ChatGPT. Ein Telegram-Konto, falls der Agent per Messenger erreichbar sein soll. Und – das ist der Punkt, den man leicht unterschätzt – SSH-Zugang zur NAS. SSH ist die Fernsteuerung per Textbefehl.

Bernd: „SSH brauch ich nicht, ich klick alles.“
Tanja: „An mindestens vier Stellen in dieser Anleitung geht es nicht ohne. Du kannst es dir aussuchen: SSH lernen oder an Schritt 1.2 hängenbleiben.“

Ein Netzwerk-Audit, bevor es losgeht. Für diese Anleitung muss auf der NAS lediglich Port 9119 frei sein – der Standardport des Dashboards. Ein Port ist die Hausnummer, unter der ein Dienst im Netzwerk erreichbar ist; sind zwei Dienste auf derselben Nummer, startet einer davon nicht. Im Container Manager nachsehen, ob 9119 noch frei ist – wer schon andere Dienste auf der NAS betreibt, hat ihn womöglich belegt.

Port 8642 taucht in der Hermes-Dokumentation ebenfalls auf, wird hier aber nicht gebraucht: Er gehört zur optionalen OpenAI-kompatiblen API, die eigens über API_SERVER_ENABLED=true eingeschaltet werden muss. In dieser Anleitung wird sie weder aktiviert noch auf dem Host veröffentlicht.

Zeit. Realistisch ein halber Tag, wenn du sorgfältig arbeitest. Wer die hier dokumentierten Stolpersteine überspringt, schafft es schneller – aber genau das war ja der Plan.

Fakten-Check: Voraussetzungen auf einen Blick

DSM: 7.2 oder neuer für „Container Manager“ samt Projekt-Assistent. DSM 7.1 und älter: Paket „Docker“, Compose über die Kommandozeile.
Prozessor: x86_64 oder 64-Bit-ARM – und das Modell muss im Paket-Zentrum tatsächlich freigegeben sein.
Arbeitsspeicher: 1 GB für den Container ohne Browser-Werkzeuge, mindestens 2 GB mit ihnen – zusätzlich zu dem, was DSM selbst braucht.
Plattenplatz: rund 2,4 GB für das Image, dazu 5 GB ohne beziehungsweise 10 GB mit Browser-Werkzeugen für die Daten.
Zugänge: Konto beim Modellanbieter, optional Telegram, und SSH.
Netzwerk: Port 9119 frei. Port 8642 wird in dieser Anleitung nicht gebraucht.


Die Landkarte: Was am Ende dastehen soll

Bevor der erste Befehl fällt, lohnt ein Blick auf das Zielbild. Stell dir das wie einen Grundriss vor, bevor du mit dem Bohren anfängst. Auf der NAS entsteht ein einziger Projektordner:

/volume1/docker/hermes-bernd/
├── data/              → wird in den Container gemountet als /opt/data
│                        (Konfiguration, Persona, Sessions, Erinnerungen, Logs)
├── container.env      → Zugangsdaten fürs Dashboard, nur für den Besitzer lesbar
└── docker-compose.yml → die Bauanleitung für den Container

Der Container selbst bekommt genau eine Tür nach außen: Port 9119, und den auch nur gebunden an die LAN-Adresse der NAS. Port 8642 wird nicht auf dem Host veröffentlicht – die zugehörige API bleibt ohnehin abgeschaltet. Kein SSH für den Container – und vor allem kein Docker-Socket.

Ulf: „Was ist ein Docker-Socket?“
Tanja: „Die Fernbedienung für Docker selbst. Wer sie hat, kann Container starten, stoppen und neue bauen.“
Bernd: „Praktisch. Dann geb ich die dem Agenten, damit er sich selbst updaten kann.“
Tanja: „Damit gibst du ihm die Schlüssel zum ganzen Haus, inklusive Werkzeugkasten und Hausmeisterbüro.“

Ein beschreibbarer Docker-Socket im Container gäbe diesem faktisch weitreichende Kontrolle über Docker selbst und damit in den meisten Konfigurationen root-ähnliche Kontrolle über den Host. Bei einem Agenten, der eigenständig Befehle ausführt, ist das die mit Abstand folgenreichste Zeile, die du nicht schreiben solltest.

Zur Einordnung der Netzwerksicherheit, weil hier oft mehr versprochen wird, als stimmt: Die Bindung an 192.168.1.50:9119 bewirkt lediglich, dass Docker auf genau dieser Host-Adresse lauscht statt auf allen Schnittstellen. Sie verhindert weder gerouteten Zugriff aus anderen Netzsegmenten noch eine spätere Öffnung durch Firewall, Router, Reverse Proxy oder Tunnel – und sie unterscheidet nicht zwischen Geräten: Jedes Gerät, das diese Adresse erreicht, erreicht auch den Port. Der eigentliche Zugriffsschutz ist deshalb das Passwort, nicht die Bindung.

Zwei Angaben in den folgenden Befehlen sind Beispielwerte. 192.168.1.50 ist die LAN-Adresse der Beispiel-NAS – deine eigene steht unter Systemsteuerung → Netzwerk → Netzwerkschnittstelle, und sie sollte fest vergeben sein, sonst zeigt die Portbindung nach dem nächsten Neustart ins Leere. Und /volume1 gilt für DiskStations mit einem einzigen Datenträgerverbund; wer mehrere angelegt hat, prüft den Pfad in File Station per Rechtsklick → Eigenschaften und setzt seine Nummer ein.

Phase 1: Das Fundament

Jetzt wird gebaut. Und wie in jeder Werkstatt gilt: Das Fundament ist der langweiligste und wichtigste Teil.

Schritt 1.1 – Ordner anlegen

In File Station (Synologys Dateimanager) im freigegebenen Ordner docker einen neuen Ordner hermes-bernd anlegen, darin einen Unterordner data.

Erwartetes Ergebnis: Beide Ordner erscheinen in File Station mit dem heutigen Datum.

Bernd: „Zwei Ordner. Bahnbrechend.“
Tanja: „Der zweite ist der, in dem später jedes Gespräch mit deinem Agenten liegt. Ich würde ihn nicht unterschätzen.“

Schritt 1.2 – Die wichtigste Zahl des ganzen Projekts

Jetzt kommt der Schritt, dessen Bedeutung sich erst zwei Stunden später zeigt. Wir brauchen die numerische UID und GID des Ordner-Besitzers.

Ulf: „Was soll das sein?“
Tanja: „Linux kennt keine Namen, nur Nummern. Was du als ‚nasadmin‘ siehst, ist intern zum Beispiel die 1026. Wie eine Trikotnummer: Auf dem Spielberichtsbogen steht die Nummer, nicht der Name.“
Ulf: „Und wenn ich die falsche Nummer eintrage?“
Tanja: „Dann spielt jemand anderes. Und der räumt dir gleich noch die Kabine um.“

Die UID ist die Benutzernummer, die GID die Gruppennummer, unter der Linux Dateirechte verwaltet. File Station zeigt dir nur den Anzeigenamen (etwa „nasadmin“), nicht die Nummer.

Per SSH:

stat -c 'UID=%u GID=%g Owner=%U:%G Rechte=%A' /volume1/docker/hermes-bernd/data

Erwartetes Ergebnis: eine Zeile der Form UID=1026 GID=100 Owner=nasadmin:users Rechte=drwx------

Wer keinen SSH-Zugang hat, kann sich behelfen: Systemsteuerung → Aufgabenplaner → Erstellen → Geplante Aufgabe → Benutzerdefiniertes Skript, als Benutzer denselben Account wählen, dem der Projektordner gehören soll – nicht root, denn dann liefert die Prüfung UID 0 und GID 0 zurück und damit genau die falschen Werte für HERMES_UID/HERMES_GID (nebenbei löst root auch eine Passwortabfrage aus), als Skript denselben stat-Befehl mit > /volume1/docker/hermes-bernd/data/owner.txt dahinter, Aufgabe manuell ausführen, Ergebnisdatei in File Station lesen, danach Datei und Aufgabe wieder löschen.

Fakten-Check: Notiere dir diese beiden Zahlen.

Im Beispiel: 1026 und 100. Sie tauchen ab jetzt in jedem einzelnen Docker-Befehl wieder auf. Wer sie weglässt, ruiniert seine Ordnerrechte – warum genau, steht gleich in Phase 3. Schreib sie dir auf einen Zettel neben die Tastatur.

Schritt 1.3 – Rechte verschärfen

Der data-Ordner steht bei Synology standardmäßig oft auf drwxrwxrwx, also für alle lesbar und beschreibbar, häufig noch mit einer zusätzlichen Zugriffsliste („Everyone“). Das ist für ein Verzeichnis, in dem später sämtliche Gesprächsverläufe liegen, keine gute Ausgangslage.

sudo chmod 700 /volume1/docker/hermes-bernd/data
sudo chmod 700 /volume1/docker/hermes-bernd
ls -ldn /volume1/docker/hermes-bernd/data

Erwartetes Ergebnis: drwx------ 1 1026 100 ... – nur der Besitzer hat Zugriff.

Bernd: „Fertig. Abgeschlossen.“
Tanja: „Fast. Du hast das Türschloss ausgetauscht. Ob jemand noch einen Zweitschlüssel hat, hast du nicht geprüft.“

chmod setzt nämlich nur die klassischen Unix-Rechte. Ob zusätzlich noch eine Synology-Zugriffsliste (ACL) mit einem Eintrag wie „Everyone“ aktiv ist, verrät ein + am Ende der Rechte-Spalte – und im Zweifel der Blick in File Station unter Eigenschaften → Berechtigung. Diese Prüfung lohnt sich, denn eine ACL kann die Unix-Rechte überstimmen.

Schritt 1.4 – Die Zugangsdaten-Datei vorbereiten

Im Projektordner (nicht in data/!) eine Datei container.env anlegen. Vorerst nur mit Platzhaltern:

HERMES_DASHBOARD_BASIC_AUTH_USERNAME=nasadmin
HERMES_DASHBOARD_BASIC_AUTH_PASSWORD=PLATZHALTER
HERMES_DASHBOARD_BASIC_AUTH_SECRET=PLATZHALTER

Danach zwingend die Rechte setzen:

chmod 600 /volume1/docker/hermes-bernd/container.env
ls -ln /volume1/docker/hermes-bernd/container.env

Erwartetes Ergebnis: -rw------- 1 1026 100 ... – nur der Besitzer darf lesen und schreiben.

Kleine Notiz am Rande, die dich sonst fünf Minuten Suchen kostet: File Station kann keine leeren Textdateien erstellen. Der Umweg führt über die DSM-App Text-Editor (Datei → Neu, Inhalt einfügen, Speichern unter…, Kodierung UTF-8) oder über SSH.

Fakten-Check: Phase 1 ist abgeschlossen, wenn …

hermes-bernd/ und darin data/ existieren.
UID und GID des Besitzers sind notiert – im Beispiel 1026 und 100.
ls -ldn meldet für beide Ordner drwx------.
container.env liegt im Projektordner, nicht in data/, und meldet -rw-------.
Eine mögliche Synology-Zugriffsliste („Everyone“) ist geprüft – chmod allein sagt darüber nichts aus.

Phase 2: Das Image

Schritt 2.1 – Herunterladen, aber bitte mit Datum

Container Manager → Registrierung, nach nousresearch/hermes-agent suchen, doppelklicken. Im Tag-Dropdown erscheint latest vorausgewählt.

Bernd: „Latest. Also das Neueste. Nimm ich immer.“
Tanja: „Und deshalb läuft bei dir jeden Monat was anderes, ohne dass du es merkst.“

Nicht übernehmen. Stattdessen nach unten scrollen und einen konkreten Versionsstand wählen, im Beispiel v2026.7.20.

Der Grund ist banal und wichtig: latest ist ein bewegliches Ziel. Beim nächsten Neustart kann eine andere Version starten als die, die du getestet hast. Ein fester Tag (die Versionsmarkierung eines Images) ist deutlich nachvollziehbarer.

Ganz fest ist aber auch er nicht: Tags lassen sich grundsätzlich nachträglich auf ein anderes Image umbiegen. Unveränderlich ist allein der Digest, der kryptografische Fingerabdruck des Imageinhalts. Für diese Anleitung genügt der Tag – wer es exakt haben will, dokumentiert zusätzlich den Digest oder schreibt ihn direkt in die Compose-Datei (image: nousresearch/hermes-agent@sha256:…). Zu beachten: Bei einem Mehrarchitektur-Image unterscheidet sich der Digest je nach Architektur, der Wert vom x86-Rechner passt also nicht zwangsläufig auf eine ARM-NAS.

Erwartetes Ergebnis: Download läuft rund eine Minute durch, Image erscheint mit etwa 2,43 GB in der Liste.

Schritt 2.2 – Nachsehen, was du da geladen hast

Image-Detailseite öffnen und drei Werte notieren:

  • Digest – der kryptografische Fingerabdruck, im Beispiel sha256:28d5fdf7…ecdfc36. Damit lässt sich später zweifelsfrei belegen, welcher Build tatsächlich lief.
  • Entrypoint – /init /opt/hermes/docker/main-wrapper.sh. Das /init verrät: Im Container läuft s6-overlay, ein kleines Aufsichtssystem, das Dienste überwacht und bei Absturz neu startet.
  • Volume – /opt/data. Das ist der Pfad, den wir gleich mit unserem data-Ordner verbinden.

Ulf: „Warum soll ich mir das aufschreiben? Das ist doch nur so ein Buchstabensalat.“
Tanja: „Weil du in drei Monaten wissen willst, welche Version damals lief, als noch alles funktionierte. Das ist deine Seriennummer.“

Fakten-Check: Phase 2 ist abgeschlossen, wenn …

Das Image liegt mit festem Tag lokal vor, nicht als latest.
Drei Werte sind notiert: Digest als einzige wirklich unveränderliche Kennung, Entrypoint /init /opt/hermes/docker/main-wrapper.sh und Volume /opt/data.
Dir ist klar, dass der Digest bei einem Mehrarchitektur-Image nur für die Architektur gilt, auf der du ihn abgelesen hast.

Phase 3: Der Einrichtungsassistent

Falle 1: UID und GID vergessen – und der Host-Ordner wechselt den Besitzer

Bevor der erste Container startet, musst du eine Eigenheit dieses Images kennen. Sie ist unangenehm, und sie hat im Beispielprojekt eine Stunde gekostet.

Ulf: „Was kann denn beim Starten schon passieren? Der Container ist doch in seiner Kiste.“
Tanja: „Normalerweise ja. Aber wir reichen ihm einen Ordner von außen rein. Und an dem fasst er beim Start etwas an.“

Der Entrypoint startet als root, dem Systemverwalter mit unbeschränkten Rechten, und wechselt erst danach auf einen normalen Benutzer. Beim Wechsel passt er die Eigentumsrechte des gemounteten Verzeichnisses an. Und weil es sich um einen Bind-Mount handelt – der Ordner wird nicht kopiert, sondern direkt aus dem Host durchgereicht –, ist das kein containerinterner Vorgang: Der Befehl läuft auf dem echten Ordner deiner NAS.

Fehlen beim Start die Variablen HERMES_UID und HERMES_GID, nimmt der Container seinen eingebauten Standardwert – im Test die UID 10000 – und schreibt ihn auf den Host durch. Dein sorgfältig gesetzter Besitzer 1026:100 ist damit weg.

Bernd: „Dann schütz ich den Ordner halt mit chmod 700. Hab ich ja schon.“
Tanja: „Root interessiert sich nicht für Dateirechte. Das ist ja gerade der Punkt an root.“

Genau das ist im Projekt passiert. Ein Container, gestartet ohne die beiden Variablen, hat den Datenordner übernommen. Die Reparatur brauchte Root-Rechte per SSH.

Fakten-Check: Der Merksatz für den Rest der Anleitung
HERMES_UID und HERMES_GID gehören in jeden Container-Start. Auch in Wegwerf-Container. Auch in den einen kurzen Diagnoselauf, bei dem das doch bestimmt egal ist. Besonders in den.

Schritt 3.1 – Den Setup-Container starten

Es gibt zwei Wege, und einer davon ist deutlich weniger schmerzhaft.

Der ursprüngliche Weg lief über den Container Manager: Container aus dem Image erstellen, Volume data → /opt/dataeinhängen, unter „Erweiterte Einstellungen → Ausführungsbefehl“ den Befehl auf setup setzen, starten, dann „Aktion → Terminal öffnen“. Das funktionierte beim ersten Anlauf – und schlug beim zweiten reproduzierbar mit „Kein Teletype-Terminal gefunden“ fehl.

Die wahrscheinlichste Erklärung: Ob der Container beim Erstellen ein TTY zugewiesen bekommt, entscheidet über Erfolg oder Misserfolg. Ein TTY ist eine Terminal-Schnittstelle – salopp: der Hörer, über den ein interaktives Programm mit dir spricht. Ohne Hörer kein Gespräch. Nachprüfbar mit sudo docker inspect <container> --format 'Tty={{.Config.Tty}}'.

Der empfohlene Weg umgeht die Frage vollständig, weil er seinen eigenen Hörer mitbringt:

sudo docker run --rm -it \
  -v /volume1/docker/hermes-bernd/data:/opt/data \
  -e HERMES_UID=1026 -e HERMES_GID=100 \
  nousresearch/hermes-agent:v2026.7.20 setup

Das -it garantiert das Terminal, --rm räumt den Container nach dem Beenden selbst wieder weg.

Erwartetes Ergebnis: Der Wizard startet und fragt: „How would you like to set up Hermes?“

Schritt 3.2 – Sofort gegenprüfen

Und hier kommt der Teil, den du bitte nicht überspringst. Der Befehl läuft im Vordergrund. Öffne eine zweite SSH-Sitzung und tippe dort direkt danach:

ls -ldn /volume1/docker/hermes-bernd/data

Erwartetes Ergebnis: drwx------ 1 1026 100 ...

Steht dort etwas anderes – insbesondere 10000 10000 –, dann brich in der ersten Sitzung sofort mit Strg+C ab und mach nicht weiter. Dann fehlten die Variablen, und je länger du weiterläufst, desto mehr Dateien legt der Container unter der falschen Nummer an.

Ulf: „Zwei Fenster gleichzeitig? Das fühlt sich übertrieben an.“
Tanja: „Es dauert zehn Sekunden und erspart dir eine Stunde. Das ist die beste Quote in dieser ganzen Anleitung.“

Schritt 3.3 – Durch den Wizard

Der Assistent führt dich durch etwa zehn Auswahlbildschirme. Navigation mit Pfeiltasten, Bestätigen mit Enter, Mehrfachauswahl mit der Leertaste.

Setup-Modus. Drei Optionen: „Quick Setup (Nous Portal)“ – kostenloser OAuth-Login beim Hersteller, keine eigenen Schlüssel. „Full setup“ – jeder Anbieter, jedes Werkzeug einzeln. „Blank Slate“ – alles aus außer dem Nötigsten. Wenn du ein eigenes ChatGPT-Abo nutzen willst, wähle Full setup; nur dort lässt sich der Anbieter gezielt bestimmen.

Bernd: „Quick Setup natürlich. Steht ja Quick drauf.“
Tanja: „Und dann wunderst du dich, warum dein bezahltes Abo nicht benutzt wird.“

Anbieter. In der Liste OpenAI wählen, im Untermenü Codex.

Der Login. Jetzt zeigt das Terminal einen OAuth-Gerätecode – ein kurzer Code, den du auf einer Webseite eingibst, um die Anmeldung zu bestätigen, ohne dein Passwort in fremde Software zu tippen. Du öffnest die angezeigte Adresse im Browser, gibst den Code ein, meldest dich an, bestätigst.

Ein Hinweis zur Einordnung, bevor du das tust: OpenAI dokumentiert die Anmeldung mit einem ChatGPT-Konto für die eigenen Codex-Clients. Hermes wird als Drittanwendung nicht ausdrücklich genannt – daraus folgt weder eine Freigabe noch ein Verbot. Ob und unter welchen Bedingungen diese OAuth-Nutzung dauerhaft unterstützt wird, ist damit nicht eindeutig dokumentiert und kann sich ändern. Wer diese Unsicherheit vermeiden will, nimmt einen separaten API-Schlüssel mit Kostenlimit oder den Zugang des Herstellers. Hinzu kommt: Codex ist in mehreren ChatGPT-Tarifen enthalten, welche Modelle und welche Nutzungsgrenzen dabei gelten, hängt aber vom Tarif ab – das hier verwendete gpt-5.6-sol steht nicht automatisch in jedem Tarif zur Verfügung.

Modell. Die Liste zeigt mehrere Varianten. Im Beispiel wurde gpt-5.6-sol gewählt (vorausgewählt).

Terminal-Backend. Auf „lokal“ belassen. Der Agent führt Befehle dann innerhalb seines eigenen Containers aus – nicht auf dem Host. Genau so soll es sein.

Ulf: „Und wenn ich ‚Host‘ wähle, wird’s schneller?“
Tanja: „Nein. Dann darf der Agent auf deiner NAS direkt Befehle absetzen statt in seiner Kiste. Lass das.“

Messaging-Plattformen. Hier nur auswählen, was du wirklich brauchst. Im Beispiel: Telegram. Die eigentliche Einrichtung passiert später in einem separaten Assistenten.

Werkzeuge für die Kommandozeile. Eine Checkliste mit 25 Einträgen, davon 17 aktiviert. Aktiv sind unter anderem Websuche, Browser-Automatisierung, Terminal und Prozesse, Dateioperationen, Code-Ausführung, Bildanalyse, Sprachausgabe, Aufgabenplanung, Erinnerungen, Delegation und Cron-Jobs. Deaktiviert bleiben Videoanalyse, Bild- und Videogenerierung, X-Suche, Home Assistant, Spotify.

Bernd: „Ich hak einfach alle an. Mehr Funktionen, mehr Leistung.“
Tanja: „Jedes Häkchen ist eine Fähigkeit, die dein Agent später wirklich benutzt. Du stellst hier keinen Wunschzettel zusammen, du verteilst Werkzeuge.“

Ein Rat, den man in dieser Liste leicht überliest: „Terminal & Processes“, „File Operations“ und „Code Execution“ zusammen bedeuten, dass ein per Messenger erreichbarer Assistent Befehle ausführen und Dateien ändern darf. Das ist der Sinn der Sache – aber es ist auch der Grund, warum wir gleich in Phase 4 die Zügel anlegen.

Und hier ist die Stelle, an der Besitzer kleinerer DiskStations eine bewusste Entscheidung treffen sollten: „Browser Automation“ ist der mit Abstand größte Speicherfresser. Dahinter steckt ein vollständiger Chromium-Browser, den der Agent fernsteuert. Ohne ihn kommt Hermes laut Dokumentation mit einem Gigabyte aus, mit ihm braucht es mindestens zwei. Wer 2 GB Gesamtspeicher hat, schaltet das Werkzeug hier ab – die Websuche bleibt davon unberührt und funktioniert weiter.

Browser-, Bild-, Sprach- und Suchanbieter. Im Beispiel: lokaler Browser, Bildgenerierung über Codex, Sprachausgabe über Microsoft Edge TTS, Suche über DuckDuckGo.

Abschluss. Der Wizard meldet „🚀 Ready to go!“ und listet Folgebefehle auf. Danach fährt der Container seine Dienste geordnet herunter und beendet sich mit Exit-Code 0.

Ulf: „Er hat sich beendet! Ist was kaputt?“
Tanja: „Nein. Der Container hatte genau eine Aufgabe: den Assistenten ausführen. Der ist fertig, also geht der Container nach Hause. Exit-Code 0 heißt ‚sauber beendet‘.“

Schritt 3.4 – Der erste Gesundheitscheck

sudo docker run --rm -it \
  -v /volume1/docker/hermes-bernd/data:/opt/data \
  -e HERMES_UID=1026 -e HERMES_GID=100 \
  nousresearch/hermes-agent:v2026.7.20 doctor

Erwartetes Ergebnis: Ein strukturierter Bericht. Wichtig sind ✓ OpenAI Codex auth (logged in) und ✓ Config version up to date (v33). Am Ende steht „Found 4 issue(s) to address“ – im Beispiel waren das durchweg harmlose Punkte: eine noch fehlende .env-Datei (entsteht erst mit der Telegram-Einrichtung), ein fehlender Symlink (kosmetisch) und zwei Meldungen zu npm-Paketen, die das Werkzeug selbst als „build-tool advisory, not runtime“ einstuft.

Bernd: „Vier Probleme! Das ist doch Schrott.“
Tanja: „Lies nach, was für Probleme. Drei davon lösen sich in den nächsten zwei Phasen von selbst, eins ist Kosmetik. Ein Werkzeug, das nichts meldet, hat meistens nur nicht hingeschaut.“

Eine Warnung zur Interpretation gehört noch dazu: ✓ SOUL.md exists (persona configured) bedeutet nur, dass die Datei existiert – nicht, dass jemand sie angefasst hat. Sie enthält an dieser Stelle noch den generischen Standardtext des Images.

Fakten-Check: Phase 3 ist abgeschlossen, wenn …
Der Wizard endete mit „Ready to go!“ und der Container mit Exit-Code 0.
ls -ldn …/data meldet weiterhin 1026 100 – und eben nicht 10000.
hermes doctor zeigt ✓ OpenAI Codex auth (logged in) und ✓ Config version up to date.
Die gemeldeten offenen Punkte hast du gelesen und eingeordnet, nicht nur gezählt.
Und der Merksatz sitzt: HERMES_UID und HERMES_GID gehören in jeden weiteren Container-Start.


Phase 4: Die Zügel anlegen

Jetzt wird es interessant, denn hier entscheidet sich, wie viel Eigenständigkeit dein Agent bekommt. Bearbeitet wird data/config.yaml – entweder per SSH oder über File Station (Rechtsklick → „Mit öffnen“ → „Text-Editor“).

Schritt 4.1 – Drei Einstellungen ändern

Erstens: Genehmigungen. Der Standard ist smart – ein Hilfsmodell entscheidet, welche Aktionen ohne Rückfrage durchgehen.

Ulf: „Klingt doch komfortabel.“
Tanja: „Es ist komfortabel. Aber du kennst das System noch nicht. Vertrauen kommt nach der Erfahrung, nicht davor.“

Für die Einführungsphase ist smart also die falsche Reihenfolge des Vertrauens. Neuer Block auf oberster Ebene:

approvals:
  mode: manual
  cron_mode: deny

manual soll genehmigungspflichtige Werkzeugaufrufe an eine ausdrückliche Freigabe binden. deny heißt: zeitgesteuerte Aufgaben werden gar nicht erst ausgeführt. Wichtig ist die Formulierung „soll“ – dass der Konfigurationswert gesetzt ist, beweist noch nicht, dass er zur Laufzeit bei jedem relevanten Aufruf greift. Genau dafür gibt es Schritt 8.5.

Zweitens: die Notbremse. Suche nach tool_loop_guardrails: und ändere den Wert:

tool_loop_guardrails:
  hard_stop_enabled: true

Das ist der Schutz gegen Endlosschleifen – ein Agent, der sich in einem fehlschlagenden Werkzeugaufruf verheddert und ihn hundertmal wiederholt.

Drittens: die Leine kürzen. Im agent:-Block steht standardmäßig max_turns: 150. Das erlaubt bis zu 150 Agentenrunden innerhalb einer Aufgabe.

Ulf: „150 klingt nach viel.“
Tanja: „Ist es auch. Stell dir vor, dein Agent hängt in einer Schleife und probiert 150-mal dasselbe. Jede Runde kostet Zeit, Geld und kann eine Werkzeugaktion auslösen.“

Eine so hohe Grenze verlängert im Zweifel die Laufzeit, treibt die Kosten und vervielfacht die Zahl der Werkzeugaktionen, die eine entgleiste Aufgabe auslösen kann – bei einem unbeaufsichtigten Messenger-Bot ein unnötiges Risiko. Für den Anfang:

agent:
  max_turns: 40

Falle 2: Ungültiges YAML – Hermes fällt lautlos auf Standardwerte zurück

Und hier ist die Geschichte vom Anfang. Nach dem Speichern lief der Gesundheitscheck erneut – und meldete plötzlich einen fünften Punkt:

⚠ hermes config: Failed to parse /opt/data/config.yaml:
  mapping values are not allowed in this context
  in "/opt/data/config.yaml", line 32, column 55.
  Falling back to default config — every user override
  (auxiliary providers, fallback chain, model settings) is being IGNORED

Die Ursache lag nicht bei den drei Änderungen, sondern in Zeile 32, in einem völlig unbeteiligten Abschnitt mit Persönlichkeitsvorlagen. Dort waren beim Editieren zwei Einträge zu einer Zeile verschmolzen – ein fehlender Zeilenumbruch. YAML (das Format dieser Datei, das Struktur über Einrückung ausdrückt) konnte das nicht mehr lesen und warf die gesamte Konfiguration weg. Nicht nur die kaputte Zeile. Alles.

Ulf: „Alles? Wegen einem Zeilenumbruch?“
Tanja: „YAML ist wie ein Regal, in dem die Einrückung bestimmt, was worauf steht. Wenn ein Brett schief hängt, kippt nicht nur das Brett.“
Bernd: „Und wo war die Fehlermeldung?“
Tanja: „Im Gesundheitscheck. Den du übersprungen hättest.“

Das ist die eigentliche Lehre dieses Abschnitts: Eine ungültige Konfigurationsdatei führt hier nicht zu einem Startfehler, sondern zu einem stillen Rückfall auf Standardwerte. Wer nicht nachsieht, betreibt ein System, das er zu kennen glaubt.

Schritt 4.2 – Also nachsehen

sudo docker run --rm -it \
  -v /volume1/docker/hermes-bernd/data:/opt/data \
  -e HERMES_UID=1026 -e HERMES_GID=100 \
  nousresearch/hermes-agent:v2026.7.20 \
  bash -c 'hermes config get approvals; hermes config get tool_loop_guardrails; hermes config get agent'

Erwartetes Ergebnis: mode: manualcron_mode: denyhard_stop_enabled: truemax_turns: 40 – jeweils im tatsächlich geladenen Zustand, nicht nur als Text in einer Datei.

Fakten-Check: Zwei Werkzeuge, zwei Aussagen

hermes doctor gibt eine Zusammenfassung – „sieht insgesamt gut aus“.
hermes config get liest aus, was das Programm wirklich verstanden hat.
Für Konfigurationswerte ist die zweite Aussage die belastbare.

Schritt 4.3 – Die Persona

Die Datei data/SOUL.md bestimmt, wer dein Assistent sein soll. Standardmäßig steht dort ein generischer Text. Ersetze ihn durch etwas Konkretes: Name, Rolle, Zugangswege und – nicht zu unterschätzen – die Sicherheitspräferenzen in Prosa, etwa dass vor kritischen Aktionen nachgefragt wird und mit Zugangsdaten vorsichtig umzugehen ist.

Ulf: „Bringt das was? Das ist doch nur Text.“
Tanja: „Es ersetzt keine technische Einstellung. Aber es ist die Stellenbeschreibung. Und die liest der Agent bei jeder Aufgabe mit.“

Fakten-Check: Phase 4 ist abgeschlossen, wenn …

hermes config get – nicht der Blick in die Datei – meldet mode: manualcron_mode: denyhard_stop_enabled: true und max_turns: 40.
hermes doctor meldet keinen Parse-Fehler zu config.yaml.
SOUL.md enthält deinen eigenen Text statt der Standardvorlage.
Und du weißt jetzt: Eine kaputte config.yaml bricht nicht ab, sie fällt still auf Standardwerte zurück.


Phase 5: Telegram

Schritt 5.1 – Bot anlegen

In Telegram @BotFather suchen (das offizielle Verwaltungskonto), /newbot senden, Anzeigename vergeben, dann den Benutzernamen.

Stolperstein: Telegram-Benutzernamen erlauben keine Bindestrichemein-agent-bot wird abgelehnt, mein_agent_botfunktioniert. Und ein Tipp aus der Praxis: Wähle einen Namen, der nicht schon ein Dutzend Mal vergeben ist – warum, siehst du in Schritt 8.2.

Erwartetes Ergebnis: BotFather bestätigt und gibt einen Token aus – die Zeichenkette im Format <zahlen>:AA…, die vollständige Kontrolle über den Bot gewährt.

Falle 3: Der Bot-Token, der nicht abläuft

Zwei Dinge sind an diesem Token wichtig. Er ist ein Passwort. Und er verfällt nicht von selbst.

Bernd: „Ich schick mir den kurz per WhatsApp aufs andere Gerät, dann hab ich ihn überall.“
Tanja: „Damit hast du dein Passwort gerade durch zwei fremde Systeme geschickt. Und anders als ein Login-Code läuft dieser hier nie ab.“

Wenn du ihn versehentlich preisgibst – in einem Screenshot, beim Kopieren durch ein Chatfenster, in einer Fehlermeldung –, behebst du das so: @BotFather → /mybots → Bot wählen → „API Token“ → „Revoke current token“. Der alte Token wird sofort ungültig, der neue muss in die Konfiguration nachgetragen und der Container mit --force-recreate neu geladen werden.

Die praktische Regel, die sich daraus ergibt: Token direkt aus der BotFather-App ins Terminal kopieren, ohne Zwischenstationen.

Schritt 5.2 – Die eigene Nutzer-ID herausfinden

In Telegram nach einem ID-Bot suchen (etwa @userinfobot) und /start senden.

Erwartetes Ergebnis: eine Antwort mit Id: 123456789 oder ähnlich. Diese Nummer ist kein Authentifizierungsgeheimnis wie der Bot-Token, aber sehr wohl eine personenbezogene technische Kennung – sie sollte nicht unnötig veröffentlicht werden. In diesem Artikel stehen deshalb durchgehend Beispielwerte.

Stolperstein: Es gibt mehrere ähnlich benannte Bots, manche inaktiv. Bei ausbleibender Antwort einen anderen aus der Trefferliste probieren.

Schritt 5.3 – Der Gateway-Assistent

sudo docker run --rm -it \
  -v /volume1/docker/hermes-bernd/data:/opt/data \
  -e HERMES_UID=1026 -e HERMES_GID=100 \
  nousresearch/hermes-agent:v2026.7.20 gateway setup

Ein Hinweis für alle, die es in Dokumentationen anders gelesen haben: Der Befehl lautet gateway setup, nicht setup gateway. Die Reihenfolge wurde im Projekt live per --help gegengeprüft.

In der Plattformliste Telegram markieren und bestätigen.

Falle 4: Die Telegram-Automatik erzeugt einen anderen Bot

Jetzt fragt der Assistent, ob der Bot automatisch oder manuell eingerichtet werden soll. Option [1] „Automatic“ ist vorausgewählt.

Bernd: „Automatic. Enter. Weiter.“
Tanja: „Stopp. Genau hier ist im Projekt jemand reingelaufen.“

Nicht bestätigen. „Automatic“ legt einen zweiten, separaten Bot über einen Vermittlungsdienst des Herstellers an – nicht den, den du gerade selbst bei BotFather erstellt hast. Im Projekt wurde das erst am angezeigten QR-Code bemerkt und mit Strg+C abgebrochen.

Wechsle mit den Pfeiltasten auf [2] Manual.

Erwartetes Ergebnis nach Eingabe des Tokens: Telegram token saved

Stolperstein: „Invalid token format“ bedeutet in der Regel eine unvollständige Kopie. Der Token ist länger, als er im Chatfenster aussieht – lass ihn dir per Antippen vollständig markieren, statt von Hand zu ziehen.

Schritt 5.4 – Die Zugangsliste

Der Assistent fragt nun nach erlaubten Nutzern. Hier trägst du deine eigene Telegram-ID ein.

Erwartetes Ergebnis:

Telegram allowlist configured – only listed users can use the bot
Telegram home channel set to 123456789

Ulf: „Kann ich das nicht leer lassen? Findet mich ja eh keiner.“
Tanja: „Dein Bot-Name ist öffentlich durchsuchbar. Und der Agent dahinter darf Dateien lesen und Befehle ausführen. Willst du wirklich, dass das Feld leer bleibt?“

Das ist der wichtigste Sicherheitsschritt dieser Phase. Ohne Zugangsliste kann jeder, der den Bot-Namen kennt, mit einem Agenten sprechen, der Dateien lesen und Befehle ausführen darf.

Zum Schluss meldet der Assistent: „Service install not supported on this platform. Run in foreground: hermes gateway run„. Auch das ist erwartet – der Wegwerf-Container hat kein Init-System, kann also keinen Hintergrunddienst registrieren. Die Konfiguration selbst ist trotzdem gespeichert. Der Dauerbetrieb kommt in Phase 7.

Fakten-Check: Phase 5 ist abgeschlossen, wenn …

Der Bot stammt von BotFather und wurde im Assistenten über [2] Manual eingetragen – nicht über „Automatic“.
Der Assistent meldete Telegram token saved und Telegram allowlist configured.
In der Zugangsliste steht deine eigene Telegram-ID.
Der Token hat keine Zwischenstationen gesehen – er ist ein Passwort und läuft nicht von selbst ab.
Die Meldung „Service install not supported on this platform“ hast du als erwartet abgehakt.


Phase 6: Das Dashboard absichern

Schritt 6.1 – Echte Werte eintragen

Jetzt ersetzt du die Platzhalter in container.env:

HERMES_DASHBOARD_BASIC_AUTH_USERNAME=nasadmin
HERMES_DASHBOARD_BASIC_AUTH_PASSWORD=<selbst gewähltes Passwort>
HERMES_DASHBOARD_BASIC_AUTH_SECRET=<Ausgabe von: openssl rand -hex 32>

Das SECRET ist der Schlüssel, mit dem Sitzungs-Cookies signiert werden – mindestens 32 zufällige Bytes, erzeugt mit:

openssl rand -hex 32

Ulf: „Kann ich als Secret auch einfach mein Passwort nochmal nehmen?“
Tanja: „Nein. Das eine schützt den Login, das andere die laufende Sitzung. Zwei Schlösser, zwei Schlüssel.“

Stolperstein: nano ist auf der DiskStation nicht installiert. Entweder vi benutzen oder den DSM-Text-Editor.

Falle 5: Klartext in der Hash-Variablen, der Login scheitert ohne Hinweis

Hermes kennt zwei gültige Varianten. HERMES_DASHBOARD_BASIC_AUTH_PASSWORD_HASH erwartet einen echten Scrypt-Hash – ein absichtlich rechenaufwendiges Hash-Verfahren, aus dem sich das ursprüngliche Passwort nicht praktikabel zurückrechnen lässt, sodass es nirgends im Klartext liegt. HERMES_DASHBOARD_BASIC_AUTH_PASSWORDerwartet das Klartext-Passwort, das erst beim Laden im Arbeitsspeicher gehasht wird. Sicherer ist die erste Variante.

Im Projekt landete ein Klartext-Passwort in der _HASH-Variable. Ergebnis: Der Login schlug fehl, ohne dass die Fehlermeldung darauf hingedeutet hätte.

Ulf: „Aber es war doch das richtige Passwort?“

Tanja: „Ja. Nur im falschen Feld. Als würdest du deinen Autoschlüssel ins Türschloss stecken – richtiger Schlüssel, falsches Schloss, und niemand sagt dir warum.“

Die anschließende Fehlersuche kostete eine dreiviertel Stunde – und führte dazu, dass bei einem grep-Befehl ein Ausschnitt des Passworts im Klartext sichtbar wurde. Beide Werte mussten danach ausgetauscht werden.

Daraus zwei Regeln. Erstens: Die Variable muss zum Wert passen – Klartext gehört in _PASSWORD, ein Hash in _PASSWORD_HASH. Zweitens, und das ist die wertvollere: Zum Prüfen von Zugangsdaten-Dateien nie cat oder grepmit voller Ausgabe verwenden. Stattdessen längenbasiert:

awk '
  /^[[:space:]]*#/ || /^[[:space:]]*$/ { next }
  {
    pos = index($0, "=")
    if (pos > 0) {
      print substr($0, 1, pos - 1) ": Länge " length(substr($0, pos + 1))
    }
  }
' /volume1/docker/hermes-bernd/container.env

Der Befehl trennt bewusst nur am ersten Gleichheitszeichen und überspringt Kommentare und Leerzeilen. Das kürzere awk -F= '{print $1": Länge "length($2)}' wäre verlockend, würde aber bei Werten, die selbst ein = enthalten – etwa Base64-Zeichenketten –, eine zu kurze Länge melden und damit ausgerechnet bei den kritischen Werten in die Irre führen.

Erwartetes Ergebnis: eine Liste der Variablennamen mit Zeichenlängen – genug, um Vollständigkeit zu prüfen, ohne einen einzigen Wert preiszugeben.

Schritt 6.2 – Struktur gegenprüfen

ENV=/volume1/docker/hermes-bernd/container.env
ls -ln "$ENV"

user=$(grep -c '^HERMES_DASHBOARD_BASIC_AUTH_USERNAME=' "$ENV")
plain=$(grep -c '^HERMES_DASHBOARD_BASIC_AUTH_PASSWORD=' "$ENV")
hash=$(grep -c '^HERMES_DASHBOARD_BASIC_AUTH_PASSWORD_HASH=' "$ENV")
secret=$(grep -c '^HERMES_DASHBOARD_BASIC_AUTH_SECRET=' "$ENV")

printf 'Username=%s Passwort=%s Hash=%s Secret=%s\n' "$user" "$plain" "$hash" "$secret"

if [ "$user" -eq 1 ] && [ "$secret" -eq 1 ] && [ $((plain + hash)) -eq 1 ]; then
  echo "Struktur: korrekt"
else
  echo "Struktur: FEHLER"
fi

Erwartetes Ergebnis: -rw------- 1 1026 100 … sowie Struktur: korrekt.

Ein bloßes Abzählen aller Zeilen mit dem Präfix HERMES_DASHBOARD_BASIC_AUTH würde hier nicht genügen – es käme auch dann auf drei, wenn Klartext- und Hash-Variante gleichzeitig gesetzt wären oder der Benutzername fehlte. Die Prüfung oben verlangt deshalb explizit genau einen Benutzernamen, genau ein Secret und genau eine der beiden Passwortvarianten. Ausgegeben werden dabei nur Zähler, keine Werte.

Eine halbwegs beruhigende Nachricht zum Schluss dieser Phase: In der getesteten Version schlägt der Start fehl, wenn das Dashboard nicht-lokal gebunden ist und kein Authentifizierungsanbieter konfiguriert wurde. Der frühere Schalter --insecure ist laut eigener Hilfeausgabe „DEPRECATED / NO-OP“ – seit einer Härtung im Juni 2026 hebelt er die Anmeldung nicht mehr aus. Das senkt das Risiko einer versehentlich völlig offenen Freigabe deutlich, ersetzt aber weder Firewall noch VPN noch HTTPS.

Bernd: „Also ist es sicher. Kann ich es ins Internet stellen.“
Tanja: „Nein. Es ist gegen Vergesslichkeit abgesichert, nicht gegen das Internet.“

Denn eines solltest du nicht überlesen: Das Dashboard läuft in diesem Aufbau über unverschlüsseltes HTTP. Passwort und Sitzungs-Cookie gehen im Klartext über dein lokales Netz. Für ein vertrauenswürdiges Heimnetz ist das vertretbar; für Zugriffe von außerhalb brauchst du ein VPN oder einen sauber eingerichteten HTTPS-Reverse-Proxy. Direkt ins offene Internet gehört auch ein passwortgeschütztes Agenten-Dashboard nicht.

Fakten-Check: Phase 6 ist abgeschlossen, wenn …

container.env enthält genau einen Benutzernamen, genau ein SECRET und genau eine der beiden Passwortvarianten.
Klartext steht in _PASSWORD, ein Scrypt-Hash in _PASSWORD_HASH – nie vertauscht.
Das SECRET stammt aus openssl rand -hex 32 und ist nicht dasselbe wie das Passwort.
Die Datei meldet -rw-------.
Geprüft hast du längenbasiert, ohne einen einzigen Wert auszugeben.
Und du hast im Kopf: Das Dashboard spricht HTTP, nicht HTTPS.

Phase 7: Der Dauerbetrieb

Schritt 7.1 – Die Compose-Datei

Docker Compose ist die Bauanleitung, die beschreibt, wie ein Container dauerhaft laufen soll.

Ulf: „Warum nicht einfach wieder so ein docker-run-Befehl?“
Tanja: „Weil du den bei jedem Neustart neu eintippen müsstest – fehlerfrei. Compose ist das aufgeschriebene Rezept statt des Kochens aus dem Gedächtnis.“

Als docker-compose.yml im Projektordner anlegen:

services:
  hermes-bernd:
    image: nousresearch/hermes-agent:v2026.7.20
    container_name: hermes-bernd
    restart: unless-stopped
    command: ["gateway", "run"]
    shm_size: "1gb"
    ports:
      - "192.168.1.50:9119:9119"
    volumes:
      - /volume1/docker/hermes-bernd/data:/opt/data
    env_file:
      - ./container.env
    environment:
      HERMES_DASHBOARD: "1"
      HERMES_DASHBOARD_HOST: "0.0.0.0"
      HERMES_UID: "1026"
      HERMES_GID: "100"
    mem_limit: 4g
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

Die IP-Adresse, den Volume-Pfad und die beiden ID-Nummern passt du natürlich an dein eigenes System an.

Zwei Werte hängen direkt an der Ausstattung deiner DiskStation. mem_limit: 4g passt auf ein Gerät mit 8 GB oder mehr; bei 4 GB Gesamtspeicher gehört dort 2g hin, bei 2 GB 1g – und dann sollten die Browser-Werkzeuge aus bleiben. Und shm_size: "1gb" vergrößert den gemeinsam nutzbaren Speicherbereich /dev/shm, den Chromium für die Browser-Automatisierung braucht; die Dokumentation nennt genau diesen Wert. Der Wert wird nicht zwingend vorab vollständig als physischer Arbeitsspeicher belegt – es ist eine Obergrenze, kein Reservat. Ohne Browser-Automatisierung kann die Zeile trotzdem entfallen.

Drei Details, die nicht offensichtlich sind. env_file steht relativ zum Projektordner – das macht die Datei umzugsfähig. mem_limit statt eines deploy:-Blocks, weil letzterer von manchen Compose-Versionen stillschweigend ignoriert wird. Und die Log-Rotation verhindert, dass Container-Protokolle unbemerkt deine Platte füllen.

Falle 6: cpus: verhindert auf manchen Synology-Kernels den Start

In der ursprünglichen Fassung stand hier zusätzlich cpus: 2.0. Der Start scheiterte:

Error response from daemon: NanoCPUs can not be set,
as your kernel does not support CPU CFS scheduler

Bernd: „Kernel kaputt. Neu installieren.“
Tanja: „Der Kernel ist nicht kaputt, er kann diese eine Sache nicht. Wie ein Auto ohne Tempomat – fährt trotzdem.“

Der DSM-Kernel dieser DiskStation ist ohne CONFIG_CFS_BANDWIDTH gebaut – ein bekanntes Verhalten bei etlichen Synology-Modellen. Docker kann die Vorgabe schlicht nicht umsetzen. Die Lösung ist unspektakulär: Zeile entfernen. Das Speicherlimit bleibt davon unberührt und wird durchgesetzt.

Schritt 7.2 – Starten

Container Manager → Projekt → Erstellen, Projektname hermes-bernd, Pfad auf /docker/hermes-bernd setzen. Auf die Rückfrage „Es ist eine docker-compose.yml im gewählten Pfad vorhanden“ mit „Vorhandene docker-compose.yml verwenden“ antworten. Webportal-Einstellungen deaktiviert lassen – das Port-Publishing erledigt bereits die Compose-Datei.

Alternativ per SSH:

cd /volume1/docker/hermes-bernd && sudo docker compose up -d

Erwartetes Ergebnis: Container hermes-bernd Creating → Created → Starting → Started, Exit Code 0.

Schritt 7.3 – Sofort die Rechte prüfen

Derselbe Root-Entrypoint-Effekt wie in Phase 3 gilt auch hier – diesmal an einem Container, der dauerhaft läuft. Also gleich nachsehen:

ls -ldn /volume1/docker/hermes-bernd/data

Erwartetes Ergebnis: drwx------ 1 1026 100 ...

Zusätzlich lässt sich im Startprotokoll direkt nachlesen, dass es geklappt hat:

sudo docker logs --tail 200 hermes-bernd

Erwartetes Ergebnis: unter anderem [stage2] Changing hermes UID to 1026Changing hermes GID to 100, und am Ende die entscheidende Zeile: → gateway is now running under s6 supervision (auto-restart on crash, dashboard supervised alongside if HERMES_DASHBOARD is set).

Ulf: „s6 supervision – was ist das?“
Tanja: „Ein kleiner Aufpasser im Container. Wenn der Hauptprozess stirbt, startet er ihn neu. Wie ein Trainer, der den Ersatzspieler sofort aufs Feld schickt.“

Fakten-Check: Phase 7 ist abgeschlossen, wenn …

Die Compose-Datei liegt im Projektordner und env_file zeigt relativ auf ./container.env.
mem_limit passt zur Ausstattung deiner NAS – und cpus: kommt nicht vor.
Der Port ist an eine LAN-Adresse gebunden.
Die Log-Rotation ist gesetzt.
ls -ldn …/data meldet nach dem Start weiterhin 1026 100.
Im Protokoll steht gateway is now running under s6 supervision.


Phase 8: Verifikation

Jetzt kommt der Teil, den die meisten überspringen – und genau deshalb steht er hier ausführlich.

Schritt 8.1 – Das Dashboard, zweistufig

Ruf nicht direkt die Startseite auf. Es gibt einen bekannten Fehler (Issue #55130), bei dem die automatische Weiterleitung auf / mit HTTP 500 fehlschlagen kann, wenn Basic Auth der einzige Anmeldeanbieter ist. Deshalb erst der Statusendpunkt:

curl -s http://192.168.1.50:9119/api/status

Erwartetes Ergebnis: JSON mit "gateway_running":true"gateway_state":"running""auth_required":true"auth_providers":["basic"] und – falls Telegram eingerichtet ist – "gateway_platforms":{"telegram":{"state":"connected"}}.

Dann im Browser direkt http://192.168.1.50:9119/login aufrufen, nicht die Wurzel.

Erwartetes Ergebnis: Anmeldeformular mit dem Banner „PUBLIC BIND · AUTH REQUIRED“. Nach dem Login zeigt das Dashboard unten links den angemeldeten Benutzer mit dem Zusatz „via basic“.

Bernd: „Warum erst dieser curl-Kram? Ich will doch nur einloggen.“
Tanja: „Weil dir der Statusendpunkt in einer Zeile sagt, ob Gateway, Telegram und Authentifizierung stehen. Wenn danach der Login klemmt, weißt du schon, dass es nicht am Dienst liegt.“

Schritt 8.2 – Telegram in echt

Schick deinem eigenen Bot /start und eine kurze Nachricht.

Erwartetes Ergebnis: Der Bot zeigt „tippt…“ und antwortet.

Stolperstein aus der Praxis: Die Telegram-Suche liefert mehrere ähnlich benannte Treffer. Im Projekt ging die erste Testnachricht an einen völlig fremden Bot mit vertauschten Namensteilen. Vergleiche den Benutzernamen vor dem Test exakt – genau deshalb der Tipp aus Schritt 5.1 mit dem unverwechselbaren Namen.

Danach prüfst du explizit, dass wirklich nur deine ID zugelassen ist:

sudo docker exec hermes-bernd env | grep -c '^GATEWAY_ALLOW_ALL_USERS='
sudo docker exec hermes-bernd env | grep -c '^TELEGRAM_ALLOW_ALL_USERS='

Erwartetes Ergebnis: jeweils 0.

Fakten-Check: Es sind zwei Variablen, nicht eine

GATEWAY_ALLOW_ALL_USERS ist der globale Schalter.
TELEGRAM_ALLOW_ALL_USERS ist der plattformspezifische.
Beide existieren, beide werden bei der Autorisierung berücksichtigt. Es reicht nicht, nur eine davon zu prüfen.

Schritt 8.3 – Der Neustart-Test

sudo docker restart hermes-bernd

Erwartetes Ergebnis: Nach ein bis zwei Minuten ist Telegram wieder verbunden, die Konfiguration steht, frühere Sitzungen sind im Dashboard weiterhin sichtbar. Direkt nach dem Neustart zeigt der Status kurz "gateway_state":"starting"und Telegram "connecting" – das ist ein normaler Übergangszustand, kein Fehler.

Ulf: „Ich hab nach zehn Sekunden geschaut und ‚connecting‘ gesehen. Panik?“

Tanja: „Nein. Kaffee holen, nochmal schauen.“

Schritt 8.4 – Der Ports- und Limit-Check

sudo docker port hermes-bernd
sudo docker inspect hermes-bernd --format 'Restart={{.HostConfig.RestartPolicy.Name}} Memory={{.HostConfig.Memory}} NanoCPUs={{.HostConfig.NanoCpus}}'

Erwartetes Ergebnis: 9119/tcp -> 192.168.1.50:9119 und sonst nichts – insbesondere kein auf dem Host veröffentlichter Port 8642. Sowie Restart=unless-stopped Memory=4294967296 NanoCPUs=0 – vier Gibibyte werden durchgesetzt, das CPU-Limit fehlt bekanntermaßen.

Schritt 8.5 – Der Test, den man gerne vergisst

Alle bisherigen Prüfungen bestätigen Konfigurationswerte. Ob approvals.mode: manual sich zur Laufzeit auch so verhält, ist damit nicht geprüft.

Bernd: „Der Wert steht doch in der Datei. Was soll da noch schiefgehen?“
Tanja: „Zwischen ‚steht in der Datei‘ und ‚greift im Ernstfall‘ liegen genau die Vorfälle, wegen denen man solche Tests macht.“

Dafür brauchst du einen echten Anlass – und zwar einen, der nichts kaputt machen kann.

Ein sauberer Testablauf sieht so aus: Zuerst legst du im Container einen bedeutungslosen Wegwerf-Ordner an, etwa unter /tmp, und legst eine leere Datei hinein. Dann forderst du den Agenten über Telegram auf, genau diesen Ordner mit einem rekursiven Löschbefehl zu entfernen – eine Aktion, die Hermes als potenziell gefährlich einstufen sollte. Zeig für diesen Test unter keinen Umständen auf echte Daten.

Erwartetes Ergebnis: Der Agent führt den Befehl nicht sofort aus, sondern schickt eine Rückfrage in den Telegram-Chat und wartet auf eine Antwort wie yes oder no. Solange keine ausdrückliche Zustimmung vorliegt, darf sich am Dateisystem nichts ändern.

Spiel beide Richtungen durch: einmal ablehnen und nachsehen, ob der Ordner noch da ist, einmal zustimmen und nachsehen, ob er verschwindet. Erst wenn beides stimmt, ist die Genehmigungspflicht mehr als ein Wert in einer Datei.

Im Beispielprojekt steht dieser Test bis heute aus. Er gehört ehrlicherweise auf die Liste, bevor jemand von „produktiv“ spricht.

Fakten-Check: Phase 8 ist abgeschlossen, wenn …

/api/status meldet gateway_running: trueauth_required: true und – sofern eingerichtet – Telegram connected.
Der Login über /login funktioniert, das Banner zeigt „PUBLIC BIND · AUTH REQUIRED“.
Beide Freigabe-Schalter, GATEWAY_ALLOW_ALL_USERS und TELEGRAM_ALLOW_ALL_USERS, sind nicht gesetzt.
Der Neustart überlebt Konfiguration, Telegram-Verbindung und frühere Sitzungen.
docker port zeigt ausschließlich 9119.
Der Genehmigungstest aus Schritt 8.5 ist in beide Richtungen durchgespielt – ablehnen und zustimmen.


Wartung und Betrieb

Der Container läuft. Jetzt kommt der Teil, der über Monate entscheidet.

Neustarten – und hier lauert Falle 7. Ohne Konfigurationsänderung genügt sudo docker restart hermes-bernd. Nach einer Änderung an container.env oder der Compose-Datei reicht das aber nicht: Ein einfacher Neustart liest die Umgebungsvariablen nicht neu ein, der Container läuft mit den alten Werten weiter – und du suchst den Fehler in der Datei, die längst richtig ist.

Ulf: „Das ist gemein.“
Tanja: „Das ist die häufigste verlorene halbe Stunde in der ganzen Docker-Welt.“

Nach jeder Änderung an diesen beiden Dateien deshalb:

cd /volume1/docker/hermes-bernd && sudo docker compose up -d --force-recreate

Protokolle lesen. docker logs hermes-bernd zeigt vor allem die Startsequenz und eignet sich für Absturzdiagnose. Der eigentliche Gesprächs- und Werkzeugverlauf liegt in data/logs/gateways/default/current.

Konfiguration ändern. Wo möglich hermes config get <pfad> zum Prüfen benutzen und die vom Programm angebotenen Setz-Befehle statt manueller Textbearbeitung – siehe den Zeilenumbruch-Zwischenfall. Nach jeder Änderung hermes doctor.

Aktualisieren. Release Notes lesen, vollständiges Backup ziehen, Tag in der Compose-Datei ändern, dann docker compose pull && docker compose up -d --force-recreate, danach hermes doctor und die Tests aus Phase 8 wiederholen. Alten und neuen Tag samt Digest notieren – das ist dein Rückweg, falls die neue Version sich anders verhält.

Sichern. Und zwar den gesamten Projektordner, nicht nur data/ – die Dashboard-Zugangsdaten liegen in container.enveine Ebene darüber. Ziel: ein anderes Volume oder besser ein anderes Gerät, verschlüsselt.

Bernd: „Ich kopier das einfach in einen zweiten Ordner auf derselben Platte.“
Tanja: „Und wenn die Platte stirbt? Eine Kopie neben dem Original ist kein Backup, das ist ein zweites Original.“

Das Protokollverzeichnis nicht freigeben. data/logs/ enthält Gesprächsinhalte. Es darf in keiner Netzwerkfreigabe, keinem Web-Root und keiner Tunnel-Weiterleitung auftauchen. Der Hintergrund ist ein realer Fall: Ein Angreifer wurde entdeckt, weil er die Protokolle seiner eigenen Agenten mit offenem Verzeichnislisting im Netz stehen ließ. Was für Angreifer gilt, gilt für alle anderen auch.

Am Router nachsehen. In der FRITZ!Box unter Internet → Freigaben → Portfreigaben die selbstständige Portfreigabe (UPnP, ein Verfahren, mit dem Geräte sich eigenmächtig Türen nach draußen öffnen) für die NAS deaktivieren. Sonst kann ein Dienst den mühsam eingesperrten Port beim nächsten Neustart selbst ins Internet stellen.

Fakten-Check: Die fünf Betriebsregeln

Nach Änderungen an container.env oder der Compose-Datei immer --force-recreate, nie nur restart.
Werte mit hermes config get prüfen statt in der Datei nachlesen.
Vor jedem Update: Release Notes lesen, Backup ziehen, alten Tag samt Digest notieren.
Gesichert wird der gesamte Projektordner, verschlüsselt, auf ein anderes Gerät.
data/logs/ gehört in keine Freigabe – dort stehen Gesprächsinhalte.


Troubleshooting

Falls etwas klemmt – hier die Fälle, die im Projekt tatsächlich aufgetreten sind.

SymptomUrsacheLösung
Ordner data/ gehört plötzlich UID 10000Container ohne HERMES_UID/HERMES_GID gestartet; root-Entrypoint hat den Host-Ordner übernommenPer SSH mit sudo chown -R 1026:100 zurücksetzen; Variablen künftig in jedem Container-Start mitgeben
„Kein Teletype-Terminal gefunden“ beim Terminal-ÖffnenContainer ohne TTY erstelltdocker run --rm -it … setup statt Container-Manager-Terminal verwenden; vorhandene Container prüfen mit docker inspect <name> --format 'Tty={{.Config.Tty}}'
Konfigurationsänderungen wirken nichtYAML-Syntaxfehler irgendwo in config.yaml → stiller Rückfall auf Standardwertehermes doctor liest den Parse-Fehler mit Zeilennummer vor; nach dem Fix mit hermes config get gegenprüfen
Container startet nicht, NanoCPUs can not be setDSM-Kernel ohne CFS-Bandwidth-UnterstützungZeile cpus: aus der Compose-Datei entfernen; mem_limitbleibt wirksam
Dashboard-Login schlägt fehl trotz richtigem PasswortKlartext-Wert steht in der _PASSWORD_HASH-VariableAuf HERMES_DASHBOARD_BASIC_AUTH_PASSWORDumbenennen (oder echten Scrypt-Hash erzeugen), danach docker compose up -d --force-recreate
Änderung an container.envbleibt wirkungslosdocker restart lädt env_file nicht neudocker compose up -d --force-recreate
Startseite liefert HTTP 500Bekannter Redirect-Fehler bei Basic Auth als einzigem AnbieterDirekt /login aufrufen; vorher curl /api/status zur Diagnose
Telegram: „Invalid token format“Unvollständige Kopie des TokensToken erneut vollständig aus BotFather kopieren; bei Zweifeln über /revoke neu erzeugen
Falscher Bot antwortet (oder gar keiner)Mehrere ähnlich benannte Bots in der Telegram-SucheBenutzernamen exakt vergleichen, ggf. über t.me/<name> direkt öffnen
hermes doctor meldet „4 issues“Fehlende .env, fehlender Symlink, npm-Advisories im Build-ToolingIn dieser Konstellation unkritisch – .env entsteht mit der Telegram-Einrichtung, der Rest ist kosmetisch bzw. betrifft keine Laufzeit
Gateway meldet „Service install not supported“Wegwerf-Container ohne Init-SystemErwartet – der Dauerbetrieb läuft über den Compose-Container mit s6-Supervision
„Container Manager“ taucht im Paket-Zentrum nicht aufModell nicht freigegeben (32-Bit-ARM oder von Synology ausgeschlossen) oder DSM älter als 7.2DSM aktualisieren; bei älteren Geräten nach dem Paket „Docker“ suchen. Bei ausgeschlossenen armv8-Modellen bleibt nur ein Community-Umweg
Kein „Projekt“-Menü im Container ManagerDSM 7.1 oder älter, Paket „Docker“ ohne Compose-AssistentCompose-Datei per SSH starten: cd <projektordner> && sudo docker compose up -d
Container wird immer wieder beendet, NAS wird zähZu wenig Arbeitsspeicher, DSM lagert auf Platte ausmem_limit senken (2 GB bei 4 GB Gesamt-RAM) und im Assistenten die Browser-Werkzeuge abwählen – sie sind der mit Abstand größte Speicherposten

Go-live-Checkliste

Bevor du den Agenten auf echte Daten loslässt:

[ ] Bot-Token widerrufen und ersetzt, falls er je irgendwo sichtbar war
[ ] Externes Backup eingerichtet und eine Wiederherstellung testweise geprüft
[ ] Approval-Test mit einem ungefährlichen Wegwerf-Ordner bestanden
    (beide Richtungen: ablehnen und zustimmen)
[ ] Werkzeugumfang auf dem Telegram-Kanal geprüft und bewusst festgelegt
[ ] TELEGRAM_ALLOWED_USERS gesetzt, GATEWAY_ALLOW_ALL_USERS und
    TELEGRAM_ALLOW_ALL_USERS nachweislich nicht gesetzt
[ ] Dashboard nicht ins offene Internet gestellt; Zugriff von außen
    nur über VPN oder HTTPS-Reverse-Proxy
[ ] Port 8642 nicht auf dem Host veröffentlicht
[ ] Kein Docker-Socket im Container eingebunden
[ ] data/logs/ in keiner Freigabe, keinem Web-Root, keiner Tunnel-Route
[ ] UPnP für die NAS im Router deaktiviert
[ ] Image-Tag und vollständiger Digest dokumentiert
[ ] hermes doctor ohne blockierende Befunde

Bernd: „Zwölf Punkte. Ich hab keine Zeit.“
Tanja: „Dann hast du auch keine Zeit für die Aufräumarbeiten danach.“

Status nach dem ersten Betriebstag

Der Container lief im bisherigen Test stabil. Docker ist mit restart: unless-stopped konfiguriert, Gateway und Dashboard stehen zusätzlich unter s6-Supervision – ein absichtlich herbeigeführter Absturz wurde allerdings nicht getestet, die Selbstheilung ist also konfiguriert und dokumentiert, nicht praktisch nachgewiesen. Der Neustart- und Persistenztest wurde bestanden. Dashboard und Telegram sind erreichbar, das Dashboard nur mit Passwort, der Bot nur für ein zugelassenes Konto. Das Speicherlimit wird nachweislich durchgesetzt, der API-Port bleibt geschlossen, ein Docker-Socket ist nicht eingebunden, und Konfiguration wie Sitzungsverlauf überstehen einen Neustart.

Vor einer produktiven Nutzung mit sensiblen Daten bleiben drei Freigabekriterien – unterschiedliche Risiken, aber allesamt Bedingungen, keine Wünsche:

  1. Ein getestetes externes Backup. Bislang ist es geplant, nicht eingerichtet; ein erster erfolgreicher Lauf fehlt.
  2. Ein bestandener Laufzeittest der Genehmigungsfunktion. Bestätigt ist bisher nur der Konfigurationswert approvals.mode: manual, nicht das Verhalten bei einem echten, über Telegram ausgelösten Werkzeugaufruf.
  3. Ein geprüfter und bewusst festgelegter Werkzeugumfang für Telegram. Es spricht einiges dafür, dass der Bot dort denselben vollen Satz erbt wie die Kommandozeile, inklusive Dateizugriff und Code-Ausführung – nachgewiesen ist es nicht.

Der erste Punkt schützt vor Datenverlust, die beiden anderen begrenzen das Risiko unkontrollierter Agentenaktionen. Dass man sie überhaupt so genau benennen kann, liegt daran, dass jeder Schritt mitprotokolliert wurde – mit Zeitstempel, Prüfmethode und Rückweg. Das ist der Unterschied zwischen „läuft“ und „ist im Griff“.

Der eigentliche Aufwand liegt woanders

Wer diese Anleitung durchgearbeitet hat, wird eine Sache bemerkt haben: Die Installation selbst ist nicht das Schwierige. Image laden, Container starten, Wizard durchklicken – das sind vielleicht neunzig Minuten. Der ganze Rest, also der größere Teil dieses Textes, besteht aus Nachsehen, ob das Eingestellte auch angekommen ist.

Ulf: „Ich dachte, Selbermachen spart Geld.“
Tanja: „Es verschiebt Kosten. Der Cloud-Dienst nimmt dir diese Arbeit nicht ab, weil sie dort nicht anfällt – sondern weil jemand anderes sie macht und dafür bezahlt wird.“

Das ist kein Zufall und keine Eigenheit dieser Software. Es ist die eigentliche Eintrittsschwelle bei selbstgehosteter Technik. Wer sie übernimmt, bekommt Kontrolle. Und eine Betriebsverantwortung, die vorher unsichtbar war.

Danach beginnt die schwierigere Frage. Ein Agent, der Dateien ändern, Befehle ausführen und Code schreiben kann, ist genau deshalb nützlich. Jede Einschränkung – manuelle Freigabe, gekürzte Leine, abgeschaltete Werkzeuge – macht ihn ein Stück weniger nützlich.

Bernd: „Also stell ich das nach zwei Wochen alles wieder ab.“
Tanja: „Vielleicht. Aber dann als Entscheidung, nicht aus Bequemlichkeit. Das ist der Unterschied.“

In der Einführungsphase ist die Abwägung leicht: Du kennst das System nicht, also ziehst du die Zügel an. Aber approvals.mode: manual ist genau die Sorte Vorsichtsmaßnahme, die nach drei Wochen zuverlässiger Nachfragen anfängt, lästig zu werden.

Die richtige Einstellung ist deshalb kein Installationswert, den man einmal setzt, sondern eine Betriebsentscheidung, die man regelmäßig überprüfen muss. Bleibt die Frage, an der sich das entscheidet: Wann lockerst du sie – und woran genau würdest du erkennen, dass es zu früh war?

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Nach oben scrollen