Date: 2026-08-08
tl;dr#
Setting up Hermes Agent in a Kubernetes (K3s) environment allows for robust, scalable AI orchestration. This post covers the architecture, the services involved, and how to expose them via Traefik.
Why?#
In early 2026 openclaw gained significant popularity, and I wanted to learn about it myself. While struggeling with the first steps to configure openclaw inside a container, I read about hermes-agent beeing better prepared for containerization and implementing some more security mechanisms. A simple test with podman worked within 5 minutes. Decision done.
Podman#
With podman it was easy to run the same container image interactively with different commands:
IMG=docker.io/nousresearch/hermes-agent podman run -it –rm -v /home/…/hermes:/opt/data $IMG setup podman create –name hermes -v /home/…/hermes:/opt/data -p 9119:9119 -e HERMES_DASHBOARD=1 $IMG gateway run podman start podman exec -it hermes chat –tui
Kubernetes#
But having the k3s cluster on the same machine since years, routing all ingres through with the traefik inside k3s, it was the goal to migrate hermes-agent into the cluster. To keep my initial setup, I just copied the complete hermes-directory to the location of my volumes and mounted it (yes, hostDirectory in single node k3s) into the k3s container.
The container image from upstream runs an internal supervisor to start and manage multiple prozesses, for gateway, dashboard, API and proxy. Inside k3s this s6 supervisor detected to not have a terminal and refused to start. Supervisors in pods are somewhat awkward, so I disabled it and had to start the services explicitly:
containers: - name: hermes-a1-gateway image: docker.io/nousresearch/hermes-agent command: [“hermes”, “gateway”, “run”] volumeMounts: - mountPath: “/opt/data” name: volume-hermes-a1-ws env: - name: HERMES_GATEWAY_NO_SUPERVISE value: “1”
To run the gateway and the dashboard, one option was to start both processes in the same container, but that is also not the best choice. Hermes-agent itself suggested to run the gateway as one container and the dashboard as the sidecar using the same image and the same volume mounts.
second container in same pod#
- name: hermes-a1-dashboard image: docker.io/nousresearch/hermes-agent command: ["hermes", "dashboard", "--host", "0.0.0.0", "--port", "9119", "--no-open"] volumeMounts: - mountPath: "/opt/data" name: volume-hermes-a1-ws ports: - containerPort: 9119 env: - name: HERMES_GATEWAY_NO_SUPERVISE value: "1"
To exec into the container and work with the correct UID to check files, modify hermes config etc I needed a shortcut. Containers in Kubernetes got UUIDs attached to there basename, so first find the deployment, then the containerm then exec and use runuser to change UUID, see code below
Channels to communicate#
A channel is a way to communicate with the gateway, Hermes-Agent support many of these. The first and most simple one is the commandline chat client with TUI or not: hermes chat --tui. But inside k3s it looks more like:
CONFIG="–kubeconfig=/home/…/.kube/config" POD=
kubectl $CONFIG -n tools get pods |grep hermes-a1 |cut -d ' ' -f 1CNT="-c hermes-a1-gateway" if [ “$1” == “sh” ]; then kubectl $CONFIG -n tools exec -it $POD $CNT – runuser -u hermes – /bin/bash else kubectl $CONFIG -n tools exec -it $POD $CNT – hermes $* fi
Email#
Because I already have a domain and mail provider, it was easy to setup a dedicated email account for hermes-agent and give it the credentials to IMAP and SMTP. So the agent can send from this email account and should also receive commands. But on important security measure is that hermes-Agent only accepts commands from well-defined and identified users. In email systems this is not easy.
First step was to define the From: mypersonaladdress@mydomain.org as the authorized sender. But the From-header of email can be spoofed easily. It was suggested to configure SPF, DKIM and DMARC completly which should create a safe way to identify the authorized sender.
My email provider already configured SPF and DKIM as DNS entries, DMARC was also just a new TXT entry in the DNS section of my domain.
But Hermes-Agent complained that a special authorized-... mail header was not set and the receiving provider is the one set it after verifying the DMARC completly. My provider did not do that and the helpdesk could not resolve the issue.
Finally, I set the TRUST_FROM_HEADER=truefor some tests, and Hermes-Agent was able to receive commands per mail, answer per mail and could send mails to arbitrary recipients.
But lacking a real security measure I disabled mail commands by settingTRUST_FROM_HEADER=falseagain.
Matrix#
Telegram and Matrix are both messenger with full support from Hermes-Agent. Matrix can be setup with an own server, so I prefer that over Telegram. The self-hosted Matrix serve is a future project, I registered two account at matrix.org (fake names here!):
@me:matrix.org @me-agent:matrix.org
On my mobile and my iPad I installed Element X as an app and logged in with my new personal account @me:matrix.org. To manage the agent’s account I used the web interface from https://app.element.io.
Following the manual pages the setup was straight forward, but to get the required token, this script was very useful:
curl -X POST https://matrix.org/_matrix/client/v3/login \
-H "Content-Type: application/json" \
-d '{
"type": "m.login.password",
"user": "@me-agent:matrix.org",
"password": "your-password-here"
}'Beeing too lazy to configure encryption support for now, I created a chat between me and me-agent without encryption. Now my Hermes-Agent directly reacts on all messages from me in that channel.
Signal#
My primary personal messenger is Signal, so I want to connect it to Hermes-Agent, too. The manual strongly suggestes a dedicated Signal account for the agent and not share the personal account, so I registered a second account with a different phone number (could be a landline, SMS-to-voice works!) and asked hermes to configure it bei itself. It starts the onboarding and asks me for the auth code send by SMS to the phone number, and done! Sending outbound messages works with the signal-cli immediately. But to receive inbound messages, the signal-cli process must be run as server in parallel to the gateway. I decided to not setup a new sidecar, because I would have to clone the configuration and authentication tokens and deploy another pod with a dedicated image. Instead of a sidecar, I start the signal client with a wrapper script before the gateway process.
containers:
- name: hermes-a1-gateway
image: docker.io/nousresearch/hermes-agent
command: ["/bin/bash", "-c"]
args: [ "/opt/data/tools/sigclient-wrapper.sh; exec hermes gateway run"]sigclient-wrapper.sh:
#!/bin/bash
set -a
. /opt/data/.env
set +a
/opt/data/tools/sigclient --config /opt/data/.local/share/signal-cli --log-file /opt/data/tools/sigclient.log -u "$SIGNAL_ACCOUNT" daemon --http 127.0.0.1:8080 &
sleep 2Dashboard and Ingress Setup#
To access the dashboard also to use the chat there, I exposed it to the internet with a traefik ingress route and certifiactes, as done for most of my other services before. A kuberbnetes service is needed and the ingress targets that service. No specials here. (In future I want to expose API and dashbaord over the same host and port, that will be a new article)
Making-of#
All this stuff above has been developed with my iPad Air 3rd Gen (2021?) with ish as a terminal during my summer holidays in Spain.
SSH into my home server and modifying config files is not that hard, but screensaved me lots of head aches by providing multiple pseudo terminal and preserving sessions over disconnects.