31. Mai 2026
Hermes Agent auf EKS: von docker-compose zu Helm
In einem zweitägigen internen Workshop haben wir Hermes Agent als selbst gehostete KI-Agent-Plattform evaluiert. Was als schnelles docker-compose-Experiment begann, endete mit einem Custom-Helm-Chart, einer ECR-gesicherten Deployment-Pipeline auf EKS und echtem Respekt für das Design des Agent-Loops bei Nous Research. Hier ist der vollständige Weg vom lokalen Container bis zum produktionsreifen Kubernetes.
Was ist Hermes?
Hermes ist ein Open-Source-KI-Agent-Framework, entwickelt von Nous Research, einem unabhängigen KI-Forschungsinstitut. Das Kernfeature ist ein geschlossener Lernkreislauf: Der Agent erstellt Skills aus Erfahrungen, verfeinert sie bei der weiteren Nutzung, pflegt ein persistentes Gedächtnis über Sessions hinweg und durchsucht vergangene Konversationen, um relevanten Kontext abzurufen. Er ist nicht an ein bestimmtes Modell gebunden. Man zeigt ihn auf OpenRouter, AWS Bedrock oder einen beliebigen OpenAI-kompatiblen Endpunkt und wechselt das Modell on the fly mit hermes model.
Eine wichtige Klarstellung: Die Bezeichnung „selbstverbessernd” löst leicht das mentale Modell von Reinforcement Learning aus. Hermes trainiert oder fine-tunet das zugrundeliegende Modell nicht. Es gibt kein Gradient-Update. Die Verbesserung findet vollständig auf der Ebene des Context Engineering statt: Skills werden als strukturierte Prompts gespeichert, das Gedächtnis ist kuratierter Text, der in den Systemkontext injiziert wird, und Nutzerprofile akkumulieren Fakten, die das Antwortverhalten des Agenten formen. Man könnte es als semi-überwachten Inferenz-Ansatz beschreiben: Ein Mensch ist nach wie vor in der Schleife, der validiert und bereinigt, was bestehen bleibt. Aber der Agent stupst sich selbst, nützliche Dinge zu speichern, und verbessert seine eigenen Skill-Definitionen über die Zeit.
Architektonisch läuft Hermes als zwei Prozesse:
- Gateway: der zentrale Agent-Loop plus ein optionaler OpenAI-kompatibler API-Server auf Port 8642. Hier laufen Konversationen, Tools und Skills; hier verbindet sich die Messaging-Bridge (Telegram, Slack, Discord etc.).
- Dashboard: eine Web-UI auf Port 9119 zum Browsen von Konversationen, Verwalten von Skills und Gedächtnis sowie Monitoring des Agenten.
Beide Prozesse teilen dasselbe HERMES_HOME-Verzeichnis (~/.hermes lokal, /opt/data in Containern).
Schritt 1: Lokaler Start mit docker-compose
Das upstream-Repository liefert eine docker-compose.yml für das Zwei-Service-Setup. Ein paar Dinge fallen sofort auf.
services:
gateway:
build: .
image: hermes-agent
container_name: hermes
restart: unless-stopped
network_mode: host
volumes:
- ~/.hermes:/opt/data
environment:
- HERMES_UID=${HERMES_UID:-10000}
- HERMES_GID=${HERMES_GID:-10000}
# Zum Exponieren des API-Servers auskommentieren:
# - API_SERVER_HOST=0.0.0.0
# - API_SERVER_KEY=${API_SERVER_KEY}
command: ["gateway", "run"]
dashboard:
image: hermes-agent
container_name: hermes-dashboard
restart: unless-stopped
network_mode: host
depends_on:
- gateway
volumes:
- ~/.hermes:/opt/data
environment:
- HERMES_UID=${HERMES_UID:-10000}
- HERMES_GID=${HERMES_GID:-10000}
command: ["dashboard", "--host", "127.0.0.1", "--no-open"]
Das HERMES_UID / HERMES_GID-Muster wird von einem s6-overlay-Init-Stage im Image verwaltet: Der Container startet als Root, mappt den internen hermes-User über gosu/usermod auf die Host-UID um, bevor die Supervision-Tree-Dienste gestartet werden. Dateien unter /opt/data bleiben damit auf dem Host lesbar und schreibbar.
Das Dashboard bindet standardmäßig an 127.0.0.1. Der Grund steht in den Kommentaren: Das Dashboard speichert API-Keys und hat keine Authentifizierungsschicht. Für Remote-Zugriff ist ein SSH-Tunnel der richtige Weg (ssh -L 9119:localhost:9119), nicht 0.0.0.0 auf einem gemeinsamen Netzwerk.
Der schnellste Start:
HERMES_UID=$(id -u) HERMES_GID=$(id -g) docker compose up -d
Schritt 2: Image bauen und in ECR pushen
Für das EKS-Deployment wird das Image in der privaten ECR-Registry benötigt.
# Bei ECR authentifizieren
aws ecr get-login-password --region eu-central-1 \
| docker login --username AWS --password-stdin \
<account-id>.dkr.ecr.eu-central-1.amazonaws.com
# Aus dem upstream-Dockerfile bauen
docker build -t hermes-agent .
# Taggen und pushen
docker tag hermes-agent \
<account-id>.dkr.ecr.eu-central-1.amazonaws.com/hermes-agent:latest
docker push \
<account-id>.dkr.ecr.eu-central-1.amazonaws.com/hermes-agent:latest
Das upstream-Dockerfile nutzt einen Multi-Stage-Build: Ein Build-Stage installiert Python-Abhängigkeiten mit uv, der Runtime-Stage paketiert den Agenten auf einem schlanken Basis-Image mit bereits integriertem s6-overlay. Das Image ist self-contained.
Schritt 3: Das Kubernetes-Manifest
Namespace und Secret
apiVersion: v1
kind: Namespace
metadata:
name: hermes-agent
---
apiVersion: v1
kind: Secret
metadata:
name: hermes-secrets
namespace: hermes-agent
type: Opaque
data:
API_SERVER_KEY: "<base64-codierter-zufälliger-Key>"
Der API_SERVER_KEY authentifiziert Anfragen an den OpenAI-kompatiblen API-Server des Gateways. Vor dem Anwenden des Manifests generieren:
kubectl create secret generic hermes-secrets \
--from-literal=API_SERVER_KEY="$(openssl rand -base64 32)" \
-n hermes-agent --dry-run=client -o yaml | kubectl apply -f -
Niemals einen echten Key im YAML-File committen. In der Produktion empfiehlt sich External Secrets Operator oder ein vergleichbares Tool, das den Secret aus dem eigenen Secrets Manager bezieht.
Persistent Volumes
Beide Deployments erhalten eigene PVCs. ReadWriteOnce entspricht EBS GP3. Ein einzelner Node kann es zur Zeit mounten, was ausreicht, da jedes Deployment mit einem Replikat läuft. Für horizontales Skalieren des Gateways wäre ReadWriteMany (EFS) notwendig. Für einen zustandsbehafteten Agent-Loop ist ein einzelnes Replikat jedoch der sinnvolle Standard.
ServiceAccount mit IRSA
apiVersion: v1
kind: ServiceAccount
metadata:
name: hermes-agent
namespace: hermes-agent
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::<account-id>:role/hermes-agent-role
Die eks.amazonaws.com/role-arn-Annotation ist der Einstiegspunkt für IRSA (IAM Roles for Service Accounts). Wenn der Cluster den Pod Identity Agent oder den OIDC-Provider konfiguriert hat, erhält jeder Pod, der diesen ServiceAccount nutzt, automatisch kurzlebige Credentials für hermes-agent-role injiziert (keine statischen AWS-Keys, kein geteiltes EC2-Instance-Profile).
In unserem Fall gewährt die IAM-Rolle bedrock:InvokeModel und bedrock:InvokeModelWithResponseStream auf den gewünschten Modellen. Damit kann das Gateway AWS Bedrock direkt für Inferenz nutzen. Kein Drittanbieter-API-Key erforderlich. Wer kein Bedrock nutzt, entfernt die Annotation einfach und übergibt Provider-Keys als zusätzliche Umgebungsvariablen über das Secret.
Gateway-Deployment
Zwei Umgebungsvariablen sind über die offensichtlichen hinaus relevant:
env:
- name: API_SERVER_HOST
value: "0.0.0.0"
- name: API_SERVER_KEY
valueFrom:
secretKeyRef:
name: hermes-secrets
key: API_SERVER_KEY
optional: true
API_SERVER_HOST: "0.0.0.0" öffnet den API-Server auf allen Interfaces im Pod, damit der Kubernetes ClusterIP-Service Traffic routen kann. API_SERVER_KEY wird aus dem Secret bezogen und erscheint nie in der Deployment-Spec.
Dashboard-Deployment
args: ["dashboard", "--host", "0.0.0.0", "--no-open", "--insecure"]
Drei Flags, die jeweils ein anderes Problem lösen:
--host 0.0.0.0: gleiche Begründung wie beim Gateway: Der ClusterIP-Service erreicht das Dashboard nicht, wenn es nur auf Loopback lauscht.--no-open: unterdrückt das “Browser beim Start öffnen”-Verhalten, das im headless Container lautlos fehlschlägt.--insecure: das ist der wichtigste. Der eingebaute HTTP-Server des Dashboards enforced standardmäßig, dass Anfragen auf einem bestimmten FQDN ankommen. Ohne--insecurewerden alle Anfragen vom ALB (mit anderemHost-Header) abgelehnt. Hinter einem Load Balancer, der TLS und Zugriffskontrolle übernimmt, ist--insecurekein Sicherheitsrückschritt.
Services und Ingress
Beide Deployments erhalten ClusterIP-Services. Der Ingress routet:
/→ Dashboard/api/→ Gateway (interne API)/v1/→ Gateway (OpenAI-kompatibler Endpunkt)
Das Ingress ist als alb.ingress.kubernetes.io/scheme: internal markiert und in einer internal-ALB-Gruppe. Nie öffentlich erreichbar. SSL-Redirect wird auf ALB-Ebene mit einer TLS 1.2+-Policy enforced.
Modellwahl und Kosten
Im Workshop haben wir Claude Sonnet 4.6 (über AWS Bedrock) statt Opus verwendet. Opus ist rund 5× teurer pro Token als Sonnet, und für die Aufgaben, die wir Hermes gestellt haben (Code-Reviews, Skripte schreiben, interne Dokumentation zusammenfassen), hat Sonnet alles problemlos bewältigt. Hermes macht den Wechsel einfach: hermes model erlaubt es, Provider und Modell mid-Session zu wechseln, ohne den Kontext zu verlieren.
Schritt 4: Helm-Chart
Sobald das rohe Manifest stabil war, wurde es in ein Helm-Chart extrahiert:
caruso-hermes/
├── Chart.yaml
├── values.yaml
└── templates/
├── _helpers.tpl
├── namespace.yaml
├── secret.yaml
├── pvc.yaml
├── serviceaccount.yaml
├── deployment-gateway.yaml
├── deployment-dashboard.yaml
├── service-gateway.yaml
├── service-dashboard.yaml
└── ingress.yaml
Die values.yaml exponiert die tatsächlich umgebungsspezifischen Parameter: Image-Tag, Resource-Requests/-Limits, Storage-Größe, IRSA-Rollen-ARN, Ingress-Hostname und ob Namespace und Secret chart-managed oder extern sind. Ohne Chart würde man entweder mehrere Manifest-Kopien pflegen oder find-and-replace in der CI. Mit dem Chart reicht ein helm upgrade --install mit einem umgebungsspezifischen Values-Override.
secret.create: false in den Values erlaubt es, dem Chart die Secret-Verwaltung zu entziehen und stattdessen External Secrets Operator oder Vault zu nutzen. Das wäre der produktionsreife Ansatz.
Nach zwei Tagen
Wir haben Hermes deployed, mit einem Bedrock-gesicherten Modell konfiguriert und einige PoC-Szenarien durchgespielt: Shell-Skripte schreiben und ausführen lassen, interne Dokumentation zusammenfassen, mehrstufige Aufgaben über das Subagent-Spawning koordinieren. Der Lernkreislauf funktionierte wie beschrieben. Skills, die wir dem Agenten an Tag eins beigebracht haben, tauchten an Tag zwei ohne explizites Prompting im Kontext auf.
Was wir nicht erkunden konnten: die vollständige Messaging-Gateway-Integration, den Cron-Scheduler, die Trajectory-Kompression für Trainingsdaten-Generierung und tiefere MCP-Server-Integrationen. Hermes hat mehr Oberfläche, als ein zweitägiger Workshop auch nur ansatzweise abdecken kann.
Ich komme zurück mit einem ausführlicheren Writeup, sobald wir aussagekräftige Produktionsdaten haben: wie die Skill-Akkumulation über Wochen hält, ob das Memory-System aktive Kuration braucht und wie das tatsächliche Bedrock-Kostenprofil bei größerem Maßstab aussieht.