Startseite / Artikel

Hermes Agent lokal unter Rootless Podman mit Ollama auf einer AMD-GPU ausführen

Hermes Agent lokal und sicher ausführen: rootloser Podman, ein montierter Ordner, interne Netzwerkkommunikation, Ollama auf einer AMD-GPU mit ROCm sowie eine systemd-Einheit.

Verlag: REACTAPP.TOP20 Min. Lesezeit4524 Wörter

Quellen: Run Hermes Agent Locally (and securely) with Ollama and Rootless Podman on Arch Linux

Der Einsatz eines KI-Agenten auf lokalen Markdown- und Obsidian-Notizen erscheint einfach, bis die Frage des Zugriffs aufkommt. Der Hermes Agent von Nous Research kann Terminal-Befehle ausführen, Dateien bearbeiten, Code ausführen und andere Tools aufrufen. Wenn man ihm ein Modell gibt, dem gesagt wird, nur einen Ordner zu betreten, entsteht keine Sicherheitsgrenze, denn eine Anweisung ist lediglich Text. Auch die eigene Dokumentation von Hermes weist in diese Richtung: Die Einstellung HERMES_WRITE_SAFE_ROOT schützt die normalen Dateibearbeitungstools, doch das Terminal-Tool kann weiterhin auf alles zugreifen, wozu das Betriebssystemkonto, unter dem Hermes läuft, Zugriff hat.

Die unten beschriebene Konfiguration platziert Hermes selbst innerhalb eines rootlosen Podman-Kontainers, neben einem Ollama-Server, der ein lokales Modell auf einer AMD-GPU ausführt. Hermes kann genau einen absichtlich montierten Ordner bearbeiten. Der Rest des Host-Dateisystems ist einfach nicht in seinem Container-Namespace vorhanden, sodass Hermes dorthin nicht zugreifen kann. Hermes unterstützt offiziell den Betrieb in einem Container, speichert den persistenten Zustand unter /opt/data und arbeitet mit selbst gehosteten Inferenzservern wie Ollama über einen OpenAI-kompatiblen Endpunkt zusammen.

Die Anleitung wurde unter EndeavourOS, einer Arch-Derivat, durchgeführt. Passen Sie die Paketkommandos an andere Distributionen an.

Installierung von rootlosem Podman

Unter Arch oder EndeavourOS installieren Sie Podman mit Pacman:

sudo pacman -Syu podman

Pacman kann fragen, welchen OCI-Runtime zu verwenden ist, und bietet dabei diese drei Optionen an:

1) crun
2) krun
3) runc

Die hier getroffene Wahl ist die erste:

1

Dieser Runtime ist crun. Er lohnt sich später, denn die keep-groups-Funktion von Podman, die zusätzliche Host-Gruppen in einen rootlosen Container überträgt, funktioniert mit crun.

Bestätigen Sie die Installation, indem Sie die Version ausgeben und Podman fragen, welchen Runtime es verwendet:

podman --version
podman info --format '{{.Host.OCIRuntime.Name}}'

Der zweite Befehl sollte mit dem Laufzeitnamen antworten:

crun

Rootless-Container benötigen ebenfalls untergeordnete UID- und GID-Konvertierungen für den Benutzer, daher prüfen Sie, ob solche Einträge in beiden Dateien vorhanden sind:

grep "^$USER:" /etc/subuid
grep "^$USER:" /etc/subgid

führen Sie ab jetzt alle Podman-Befehle als normaler Benutzer aus. Fügen Sie ihnen kein sudo voran.

Wiederherstellung von rootless OverlayFS bei Bedarf

Das erste Herunterladen einer Image auf diesem Rechner scheiterte mit dem Fehler, dass der Kernel Overlay auf einem ext-Dateisystem nicht unterstützt:

kernel does not support overlay fs:
'overlay' is not supported over extfs

Wenn das der Fall ist, installieren Sie die Benutzerspace-Implementierung:

sudo pacman -S fuse-overlayfs

Podman empfiehlt fuse-overlayfs für rootlose Einrichtungen, bei denen das native Overlay nicht verfügbar ist, und es kann ausdrücklich als das Programm für rootlose Mounts angegeben werden. Überprüfen Sie, ob es installiert ist:

which fuse-overlayfs

Der erwartete Pfad ist:

/usr/bin/fuse-overlayfs

Die Konfiguration befindet sich in einer benutzerbezogenen Datei:

~/.config/containers/storage.conf

Falls es bereits existiert, bearbeiten Sie es stattdessen, anstatt es blind zu ersetzen. Im Speicherbereich sollte der Overlay-Treiber ausgewählt werden:

[storage]
driver = "overlay"

Und ein zweiter Abschnitt sollte das Mount-Programm benennen:

[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"

Überprüfen Sie anschließend die tatsächlichen Einstellungen, indem Sie die Debug-Ausgabe von Podman filtern:

podman info --debug | grep -Ei 'graphDriverName|mount_program|overlay'

Speicher auf eine größere Festplatte verschieben

Für dieses Projekt wurde dieser Schritt fast sofort notwendig. Allein das Ollama-Containerbild umfasst mehrere Gigabyte, Modelle fügen oft weitere 5 bis 20 GB oder mehr hinzu, und Podman nutzt außerdem temporären Speicherplatz beim Herunterladen und Entpacken der Schichten. Der erste Versuch, Ollama herunterzuladen, scheiterte schließlich mit der Meldung, dass auf einem Pfad innerhalb des temporären Verzeichnisses kein Platz mehr vorhanden sei:

write /var/tmp/container_images_storage...:
no space left on device

Es sind zwei getrennte Orte beteiligt, die leicht übersehen werden können. Die dauerhafte, wurzellose Speicherung von Bildern, Containern und Volumina befindet sich in der Regel unter dem Home-Verzeichnis:

$HOME/.local/share/containers/storage

Die heruntergeladenen Schichten landen vorübergehend in:

/var/tmp

Das Ändern von Podmans --tmpdir verschiebt den Download-Ordner nicht. In Podmans Dokumentation wird empfohlen, stattdessen die Umgebungsvariable TMPDIR zu verwenden.

Wenn ein größeres lokales Dateisystem verfügbar ist, erstellen Sie eine Struktur mit einem Verzeichnis für die Speicherung und einem für temporäre Dateien:

/path/to/large-drive/podman/
├── storage/
└── tmp/

Erstellen Sie beides und übertragen Sie das Eigentum an den aktuellen Benutzer:

sudo mkdir -p /path/to/large-drive/podman/storage
sudo mkdir -p /path/to/large-drive/podman/tmp
sudo chown -R "$USER:$USER" /path/to/large-drive/podman

Dann öffnen Sie die Speicherkonfiguration zur Bearbeitung:

mkdir -p ~/.config/containers
nano ~/.config/containers/storage.conf

Weisen Sie den Graphen-Root auf den neuen Speicherverzeichnis hin und behalten Sie dabei den Overlay-Treiber bei:

[storage]
driver = "overlay"
graphroot = "/path/to/large-drive/podman/storage"

Und beibehalten Sie die Einstellung für das Mount-Programm fuse-overlayfs:

[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"

Wählen Sie dazu ein lokales Dateisystem. In Podmans Dokumentation wird gewarnt, dass NFS und andere Netzwerkdateisysteme als Speicher für rootlose Container unerwünschtes Verhalten zeigen.

Auch temporäre Downloads müssen umgelegt werden, daher setzen Sie TMPDIR für die aktuelle Shell ein:

export TMPDIR=/path/to/large-drive/podman/tmp

Um es in Bash dauerhaft zu machen, fügen Sie es dem Profil hinzu und laden Sie es neu:

echo 'export TMPDIR=/path/to/large-drive/podman/tmp' >> ~/.bashrc
source ~/.bashrc

Überprüfen Sie die neuen Orte, bevor Sie etwas aus dem alten löschen. Drucken Sie den Graphenwurzelwert aus und filtern Sie die Debug-Ausgabe für den Temporärordner sowie das Mount-Programm:

podman info --format 'GraphRoot: {{.Store.GraphRoot}}'
podman info --debug | grep -Ei 'graphRoot|imageCopyTmpDir|mount_program'

Nur bei einer frischen Podman-Installation und ausschließlich dann, wenn die neue Graphenwurzel eindeutig korrekt ist, sollte der in der alten Location befindliche, nicht mehr genutzte Speicher zur Löschung in Betracht gezogen werden:

~/.local/share/containers/storage

Tun Sie das nicht einfach so auf einer bestehenden Installation. Deshalb sollte auch der Speicherort bereits vor der Erstellung des Ollama-Modellvolumens festgelegt werden.

Ein Ordner für Hermes, zwei Volumen für den Zustand

Wählen Sie den einzigen Host-Ordner, den Hermes erreichen darf, und wählen Sie ihn bewusst aus. Exportieren Sie seinen Pfad in eine Variable und erstellen Sie anschließend den Ordner:

export HERMES_WORKSPACE="$HOME/path/to/Hermes-Workspace"
mkdir -p "$HERMES_WORKSPACE"

Mit Obsidian ist eine gute Wahl ein spezieller Ordner innerhalb eines Vaults, wie beispielsweise ein Forschungsverzeichnis neben den persönlichen und beruflichen Ordnern:

Obsidian Vault/
├── Personal/
├── Work/
├── Research/
└── Agent Workspace/       ← only this folder is exposed

Alles andere im Tresor bleibt für den Agenten unsichtbar.

Als Nächstes erstellen Sie zwei Volumes, die von Podman selbst verwaltet werden:

podman volume create hermes-data
podman volume create ollama-models

Der erste enthält die Konfiguration, den Speicher sowie den persistenten Zustand von Hermes. Der zweite enthält das lokale Modell. Keiner von beiden erfordert, dass weitere Host-Verzeichnisse dem Agenten zugänglich gemacht werden.

Gewährung von Zugang zu einer AMD-GPU für den Container

Diese Einrichtung richtet sich an AMD ROCm und nicht an NVIDIA CUDA. Auf einer AMD-Karte sollte man das nvidia-container-toolkit nicht installieren – das war ein Umweg, der zuvor eingeschlagen wurde, bevor der Fehler bemerkt wurde. Ollamas Anleitungen für Container verwenden das Bild ollama/ollama:rocm für AMD-GPUs und übergeben zwei Geräte-Node an den Container:

/dev/kfd
/dev/dri

Überprüfen Sie, ob sie auf dem Host vorhanden sind:

ls -l /dev/kfd
ls -l /dev/dri/render*

Dann schauen Sie sich die Gruppen des Kontos an:

groups

Falls das Konto in den Video- und Render-Gruppen fehlt, fügen Sie es hinzu:

sudo usermod -aG video,render "$USER"

Dann melden Sie sich vollständig ab und melden Sie sich erneut an. Wenn Sie die Gruppenliste erneut in derselben alten Terminal-Ansicht aufrufen, wird die neue Mitgliedschaft nicht angezeigt, was zu unnötigem Kopfzerbrechen führt:

groups

Nach einem neuen Anmelden ausführen Sie es erneut:

groups

Die Ausgabe sollte nun beide Gruppen enthalten:

video render

Die Option --group-add keep-groups von Podman übermittelt später diese zusätzlichen Gruppen an den rootless-Container, damit dieser GPU-Geräte nutzen kann.

Überprüfung des TUN-Geräts

Ein weiterer Fehler trat durch das rootless-Netzwerk auf. Pasta, Podmans Standardmechanismus für rootless-Netzwerke, meldete, dass es das TUN-Gerät nicht öffnen könne:

pasta failed with exit code 1:
Failed to open() /dev/net/tun: No such device

Suchen Sie nach dem Geräte-Node:

ls -l /dev/net/tun

Falls es fehlt, laden Sie das Kernel-Modul:

sudo modprobe tun

Prüfen Sie es dann erneut. Wenn modprobe angibt, dass das Modul für den laufenden Kernel nicht existiert und das System kürzlich aktualisiert wurde, vergleichen Sie die Version des laufenden Kernels mit den Verzeichnissen der Module:

uname -r
ls /usr/lib/modules/

In einer rollingen Distribution wie Arch läuft das Gerät möglicherweise immer noch mit einem älteren Kernel, dessen Module während des Updates entfernt wurden. Starten Sie den Rechner neu und versuchen Sie es erneut.

Wahl eines lokalen Modells

Für Karten mit etwa 16 GB VRAM empfiehlt Ollama derzeit gemma4 als lokales Modell für Hermes, und Systeme mit nahezu 24 GB können sich Modelle wie qwen3.6 ansehen. Das hier gezeigte Beispiel verwendet eine gemma4-Variante mit 12 Milliarden Parametern:

gemma4:12b

Speichern Sie es in einer Variablen und passen Sie das Modell an die tatsächliche Hardware an:

export HERMES_MODEL="gemma4:12b"

Die andere wichtige Zahl ist die Kontextlänge. Ollama konfiguriert GPUs mit weniger als 24 GiB VRAM standardmäßig nur auf einen 4K-Kontext, während beide Projekte bei Beteiligung von Agenten einen Mindestwert von 64.000 Token empfehlen. Dies wird später ausdrücklich konfiguriert.

Bildherunterladen und das Modell

Drei Bilder sind erforderlich: das reguläre Ollama-Bild, die ROCm-Version sowie das Hermes-Bild:

podman pull docker.io/ollama/ollama:latest
podman pull docker.io/ollama/ollama:rocm
podman pull docker.io/nousresearch/hermes-agent:latest

Das reguläre Ollama-Bild kümmert sich um den einmaligen Download des Modells. Das ROCm-Bild dient als dauerhafter AMD-Inferenzserver.

Laden Sie das Modell in sein persistentes Volumen mithilfe eines temporären Containers herunter. Löschen Sie zunächst alle veralteten Container:

podman rm -f ollama-bootstrap 2>/dev/null

Dann starten Sie den Downloader mit dem gelösten Modellvolumen – und nichts weiter.

podman run -d \
  --name ollama-bootstrap \
  -v ollama-models:/root/.ollama \
  docker.io/ollama/ollama:latest

Falls das rootlose Pasta-Netzwerk weiterhin nicht funktioniert, ist das Host-Netzwerk nur für diesen temporären Downloader akzeptabel. Entfernen Sie den Container erneut:

podman rm -f ollama-bootstrap 2>/dev/null

Und beginnen Sie mit dem Host-Netzwerk:

podman run -d \
  --network host \
  --name ollama-bootstrap \
  -v ollama-models:/root/.ollama \
  docker.io/ollama/ollama:latest

Es ist keine Hermes-Arbeitsumgebung in diesem Container montiert. Ziehen Sie das Modell daran vorbei:

podman exec -it ollama-bootstrap \
  ollama pull "$HERMES_MODEL"

Bestätigen Sie mit einer Auflistung:

podman exec ollama-bootstrap ollama list

Das ausgewählte Modell sollte angezeigt werden:

gemma4:12b

Nehmen Sie den temporären Container entfernen. Das Modell bleibt im Volume:

podman rm -f ollama-bootstrap

Falls Podman meldet, dass der Containername bereits in Gebrauch ist:

container name "ollama-bootstrap" is already in use

Erfinden Sie keine neuen Namen mehr. Löschen Sie stattdessen den veralteten Container:

podman rm -f ollama-bootstrap

oder fügen Sie das --replace-Flag hinzu.

Eine interne Netzwerkstruktur und ein Pod

Das erste Design nutzte überhaupt kein Netzwerk:

--network none

Dies wurde in eine dedizierte interne Podman-Brücke geändert. Hermes und Ollama erhalten einen gemeinsamen Netzwerknamensraum, in dem sie miteinander kommunizieren können, während Podman den Zugriff von außen einschränkt. Die Option --internal existiert speziell, um den externen Zugriff auf Brückennetzwerke einzuschränken. Erstellen Sie sie:

podman network create \
  --ignore \
  --internal \
  hermes-internal

Erstellen Sie anschließend den Pod in diesem Netzwerk:

podman pod create \
  --name hermes-local \
  --network hermes-internal \
  --userns=keep-id:uid=10000,gid=10000

Die Option keep-id platziert das nicht privilegierte Host-Konto innerhalb des Benutzernamensraums des Containers, und die darin angezeigten Zahlen können frei gewählt werden. Der Hermes-Container läuft unter 10000:10000, weshalb genau diese Werte angezeigt werden.

Ollama mit ROCm starten

Es trat hier ein weniger offensichtliches Problem auf. Der Befehl, der schließlich funktionierte, sieht so aus:

podman run -d \
  --name ollama \
  --pod hermes-local \
  --device /dev/kfd \
  --device /dev/dri \
  --group-add keep-groups \
  -e HOME=/root \
  -e OLLAMA_MODELS=/root/.ollama/models \
  -e OLLAMA_CONTEXT_LENGTH=64000 \
  -v ollama-models:/root/.ollama \
  docker.io/ollama/ollama:rocm

Seine AMD-spezifischen Zeilen sind:

--device /dev/kfd
--device /dev/dri
--group-add keep-groups
ollama/ollama:rocm

In dieser Podman-Anordnung sollten zwei Umgebungsvariablen nicht weggelassen werden:

HOME=/root
OLLAMA_MODELS=/root/.ollama/models

Der Grund ist ein rätselhafter Fehler. Das Modell wurde korrekt heruntergeladen, die Blob-Dateien sowie das Manifest existierten und der Laufwerk wurde ordnungsgemäß montiert. Dennoch zeigte die Auflistung der Modelle überhaupt nichts an:

podman exec ollama ollama list

Das Protokoll des Containers erzählte die Geschichte:

podman logs ollama

Darin wurde gezeigt, dass Ollama einen Home-Ordner am Wurzelverzeichnis des Dateisystems sowie einen daraus abgeleiteten Models-Pfad verwendet:

HOME=/
OLLAMA_MODELS=/.ollama/models

während das Modell tatsächlich in:

/root/.ollama/models

Ollama erstellt seinen Standardmodellpfad aus dem Heimverzeichnis zusammen mit .ollama/models, es sei denn, OLLAMA_MODELS überschreibt diesen Wert. Deshalb fehlte das Modell nie – Ollama suchte nur am falschen Ort. Durch die explizite Einstellung beider Variablen wurde das Problem sofort behoben:

HOME=/root
OLLAMA_MODELS=/root/.ollama/models

Nun sollte die Auflistung das Modell anzeigen:

podman exec ollama ollama list

Bestätigung der Nutzung der GPU

Suchen Sie im Protokoll nach Zeilen zu ROCm und der GPU:

podman logs ollama 2>&1 | grep -Ei 'gpu|rocm|amd|gfx'

Eine funktionierende AMD-Installation erkennt ein ROCm-Rechengerät. Anschließend wird ein schneller Testabfragen ausgeführt, der vom Modell eine genaue Antwort verlangt:

podman exec -it ollama \
  ollama run gemma4:12b \
  "Reply with exactly: AMD GPU test successful"

Sofort danach schauen Sie sich die laufenden Modelle an:

podman exec ollama ollama ps

Zwei Spalten verdienen Aufmerksamkeit:

PROCESSOR
CONTEXT

Idealerweise zeigt die Spalte für den Prozessor die GPU an und nicht eine CPU-Offloading-Funktion, und der Kontext betrifft in der Regel:

64000

Ollama selbst empfiehlt, die Liste der laufenden Prozesse zu überprüfen, um sowohl den zugewiesenen Kontext als auch die Delegation zu sehen. Ein Modell, das einen sehr großen nativen Kontext anbietet, erhält nicht unbedingt wirklich so viel zugewiesen.

Hermes mit einer Mount-Option starten

Erstellen Sie nun den Hermes-Container innerhalb desselben Pods:

podman run -d \
  --name hermes \
  --pod hermes-local \
  --security-opt=no-new-privileges \
  --pids-limit 512 \
  -v hermes-data:/opt/data \
  -v "$HERMES_WORKSPACE:/opt/data/workspace:rw,nodev,nosuid" \
  -w /opt/data/workspace \
  docker.io/nousresearch/hermes-agent:latest \
  sleep infinity

Was zählt, ist das, was darin fehlt. Keines davon erscheint:

/:/host
/home:/home
~/.ssh
~/.config
Docker socket
Podman socket

Die einzige willkürliche Host-Bindung ist das Arbeitsplatzverzeichnis, das in den Datenordner des Containers abgebildet wird:

$HERMES_WORKSPACE
        ↓
/opt/data/workspace

Hermes’ eigener Zustand befindet sich im genannten Volume, das an seinem Datenpfad montiert ist:

hermes-data
        ↓
/opt/data

Der offizielle Container verwendet /opt/data für den persistenten Zustand und setzt HERMES_WRITE_SAFE_ROOT automatisch darauf. Diese Schreibschutzmaßnahme gilt als zusätzliche Sicherheitsebene, nicht als Haupt-Sandbox, da die Dokumentation von Hermes warnt, dass Terminalbefehle die Einschränkungen bezüglich der Dateiwerkzeuge umgehen können. Die eigentliche Grenze ist der Dateisystem-Namespace von Podman.

Überprüfung der Mount-Grenze

Prüfen Sie, was der Host tatsächlich an Hermes freigibt:

podman inspect hermes \
  --format '{{range .Mounts}}{{println .Type .Source "->" .Destination}}{{end}}'

Erwarten Sie zwei Zeilen, eine für das Datenvolumen und eine für den zulässigen Ordner:

Podman volume -> /opt/data
your allowed folder -> /opt/data/workspace

Der Heimverzeichnis und der Socket von container-runtime dürfen nicht angezeigt werden. Überprüfen Sie den Socket ausdrücklich:

podman exec hermes sh -c \
  'test ! -S /var/run/docker.sock && echo "No Docker socket exposed"'

Auch hostabhängige Pfade können überprüft werden, zum Beispiel indem man prüft, ob ein SSH-Verzeichnis sichtbar ist:

podman exec hermes sh -lc \
  'test -e "$HOME/.ssh" && echo "SSH directory visible" || echo "Host SSH directory not visible"'

Denken Sie daran, dass der Container seine eigenen Systemverzeichnisse hat. Dass man diese sieht, ist normal, da sie zum Container und nicht zum Host gehören.

Hermes erreicht Ollama

Sowohl Container teilen den Netzwerknamenraum des Pods, weshalb sie über Loopback miteinander kommunizieren. Testen Sie Ollamas OpenAI-kompatibles API von innerhalb des Hermes-Containers:

podman exec hermes python -c \
'import urllib.request; print(urllib.request.urlopen("http://127.0.0.1:11434/v1/models").read().decode())'

Die Antwort sollte ein JSON enthalten, das das lokale Modell enthält. Ein erster Versuch nutzte einen Python-Heredoc und gab nichts aus:

podman exec hermes python - <<'PY'
...
PY

Der Grund war einfach. Python liest sein Programm in dieser Form von stdin ein, doch podman exec wurde nicht angewiesen, stdin anzuschließen. Durch Hinzufügen des interaktiven Flags funktioniert die Heredoc-Version:

podman exec -i hermes python - <<'PY'
import urllib.request
print(
    urllib.request.urlopen(
        "http://127.0.0.1:11434/v1/models"
    ).read().decode()
)
PY

Dieses fehlende Flag kostete ein paar zusätzliche Minuten.

Nachweisen, dass die Außenwelt unerreichbar ist

Probieren Sie, eine externe Website von innen aus Hermès’ Container abzurufen:

podman exec hermes python -c \
'import urllib.request; print(urllib.request.urlopen("https://example.com", timeout=5).read())'

Es sollte fehlschlagen – und das ist beabsichtigt. Hermes kann Ollama innerhalb der Pod-Struktur erreichen, doch das Netzwerk gewährt keinen normalen externen Zugriff.

Verzweifeln Sie nicht, wenn die Liste der Container so aussieht:

podman ps

zeigt einen Port neben Ollama an:

11434/tcp

Das bedeutet nicht, dass der Port am Host veröffentlicht wurde. Ein veröffentlichter Port würde eher so aussehen:

0.0.0.0:11434->11434/tcp

Die Angabe -p 11434:11434 wird in dieser Konfiguration nirgends verwendet.

Hermes auf den lokalen Ollama verweisen

Starten Sie die Konfiguration von Hermes als Benutzer des Containers, wobei Sie sich im Arbeitsbereich befinden:

podman exec -it \
  --user 10000:10000 \
  -w /opt/data/workspace \
  hermes \
  hermes model

Der Anbieterauswahler listet viele Cloud-Dienste auf. Wählen Sie die Cloud-Option nicht aus:

Ollama Cloud

Wählen Sie stattdessen den manuellen Endpunkt-Eintrag:

Custom endpoint (enter URL manually)

Für die Basis-URL verwenden Sie die Loopback-Adresse und den Pfad v1:

http://127.0.0.1:11434/v1

Falls eine API-Schlüssel angefordert wird, lassen Sie ihn leer. Die aktuellen Hermes-Integrationshinweise von Ollama verwenden genau diesen Flow mit einem benutzerdefinierten Endpunkt und einem leeren Schlüssel für einen lokalen Server. Wenn nach API-Kompatibilität gefragt wird, wählen Sie den für OpenAI kompatiblen Chat-Completions-Pfad. Für den Modellnamen geben Sie ein:

gemma4:12b

Und was die Kontextlänge angeht:

64000

In der Dokumentation von Hermes wird angegeben, dass die lokale Ollama-Version den v1-Endpunkt verwenden sollte und für Agentenworkflows, die Tools nutzen, mindestens 64 K an Kontext benötigt.

Verwendung des lokalen Terminal-Backends innerhalb des Containers

Das klingt anfangs seltsam. Normalerweise warnt Hermes davor, dass die Einstellung des lokalen Terminal-Backends verwendet wird:

terminal:
  backend: local

führt Terminal-Befehle direkt auf derselben Maschine wie Hermes aus, ohne Isolierung. Bei einer direkt installierten Hermes wäre das gefährlich. Hier befindet sich Hermes bereits innerhalb von Podman, sodass der lokale Backend auf den Hermes-Container verweist:

Hermes "local"
      ↓
Hermes container

und nicht an den Arbeitsplatz:

Hermes "local"
      ↓
your Arch workstation

Stellen Sie den Backend ein:

podman exec --user 10000:10000 hermes \
  hermes config set terminal.backend local

Und der Arbeitsverzeichnis:

podman exec --user 10000:10000 hermes \
  hermes config set terminal.cwd /opt/data/workspace

Eine Präferenz für einen profilbasierten Heimmodus lohnt sich ebenfalls einzustellen:

podman exec --user 10000:10000 hermes \
  hermes config set terminal.home_mode profile

Konfigurieren Sie den Docker-Backend von Hermes nicht für dieses Design. Dafür wäre es notwendig, Hermes Zugriff auf einen Container-Runtime zu gewähren, was eine der wichtigsten Isolierungsentscheidungen zunichtemacht.

Hermes starten

Eine interaktive Sitzung beginnt mit dem gleichen Ausführungsmuster wie bei der Einrichtung:

podman exec -it \
  --user 10000:10000 \
  -w /opt/data/workspace \
  hermes \
  hermes

Zur Aufbereitung der Notizen könnte eine Anweisung Hermes bitten, die Markdown-Notizen im Arbeitsbereich durchzusehen, Struktur und Überschriften zu ordnen, Formatierungs- und Grammatikfehler zu korrigieren, alle technischen Details, Befehle, Links sowie Obsidian-Wikilinks unverändert zu lassen und niemals echten Inhalt zu entfernen, nur um eine Notiz kürzer zu machen. Die Änderungen werden direkt in dem einzigen montierten Host-Ordner gespeichert.

Starten der Umgebung beim Booten

Sobald alles interaktiv funktioniert, sollte der Pod nach einem Neustart ohne manuellen Start von Ollama und Hermes wieder funktionieren. Erstellen Sie eine systemd-Unit für den Benutzer:

mkdir -p ~/.config/systemd/user
nano ~/.config/systemd/user/hermes-local.service

Die Einheit beginnt mit einer Beschreibung und einer Bestellzeile:

[Unit]
Description=Hermes Local AI Pod
After=default.target

Der Service-Bereich ist eine einmalige Aktion, die den Pod startet, ihn mit einer 30-sekündigen Frist zum Stoppen versehen hat und zwei Minuten Zeit zum Neustart lässt:

[Service]
Type=oneshot
RemainAfterExit=yesExecStart=/usr/bin/podman pod start hermes-local
ExecStop=/usr/bin/podman pod stop -t 30 hermes-localTimeoutStartSec=120
TimeoutStopSec=60[Install]
WantedBy=default.target

Falls die Wurzel des Podman-Graphs auf einer separaten Festplatte liegt, muss diese Festplatte zuverlässig zum Starten des Systems montiert werden und nicht etwas sein, was der Desktop nach dem Anmelden interaktiv montiert. Eine optionaler ExecStartPre-Montage-Check, der auf dem Dateisystem abgestimmt ist, kann hinzugefügt werden.

Systemd neu laden:

systemctl --user daemon-reload

Einheit aktivieren:

systemctl --user enable hermes-local.service

Starten Sie es:

systemctl --user start hermes-local.service

Und überprüfen Sie es:

systemctl --user status hermes-local.service

Weil es sich um einen One-Shot-Service handelt, bei dem RemainAfterExit aktiviert ist, ist dieser Status normal:

Active: active (exited)

In Podman gibt es einen neueren Quadlet-Mechanismus zur deklarativen Definition von Containern unter systemd. Wenn jedoch bereits eine Umgebung manuell zusammengestellt und getestet wurde, störte ein kleiner Benutzerdienst, der den Pod startet und bereits als funktionierend bekannt ist, am wenigsten.

Um den rootlosen Benutzerdienst beim Booten bereits vor dem Anmelden am Desktop starten zu lassen, aktivieren Sie „lingering“:

sudo loginctl enable-linger "$USER"

Überprüfen Sie es:

loginctl show-user "$USER" -p Linger

Das erwartete Ergebnis:

Linger=yes

Die linger-Funktion von Systemd sorgt dafür, dass der Dienstmanager des Benutzers beim Start des Systems gestartet wird und außerhalb normaler Anmelde-Sitzungen weiterhin verfügbar bleibt. Nach einem Neustart zeigt die Liste der Container:

podman ps

Es sollte bereits der Pod angezeigt werden, in dem Ollama und Hermes laufen. Eine interaktive Hermes-Sitzung wird dennoch mit dem exec-Befehl gestartet:

podman exec -it \
  --user 10000:10000 \
  -w /opt/data/workspace \
  hermes \
  hermes

Dienst zum Starten des Systems startet die Umgebung. Er öffnet keinen interaktiven Chat.

Tatsächlich aufgetretene Probleme

Hier ist die zusammengefasste Liste der im Laufe des Vorgangs aufgetretenen Fehler, jeweils als Problem und Lösung dargestellt.

Ein Überlagerungsfehler des Kernels:

Problem:
kernel does not support overlay fs

Das Problem wurde durch die Installation von fuse-overlayfs und dessen Einrichtung als Overlay-Mount-Programm gelöst:

Fix:
Install fuse-overlayfs and configure it as the overlay mount program.

Es fehlt Platz im temporären Verzeichnis:

Problem:
no space left on device under /var/tmp

Es war sowohl ein verschobener Graphen-Root-Pfad bei Bedarf als auch eine TMPDIR-Einstellung erforderlich, da Podmans tmpdir-Flag etwas anderes ist:

Fix:
Move Podman's graphroot if needed AND set TMPDIR.
Changing Podman's --tmpdir is not the same thing.

Es besteht ein Namenskonflikt für den Bootstrap-Container:

Problem:
ollama-bootstrap name already in use

wird durch Entfernung des veralteten Containers behoben:

Fix:
podman rm -f ollama-bootstrap

Pasta kann das TUN-Gerät nicht öffnen:

Problem:
pasta cannot open /dev/net/tun

Wird durch das Laden des tun-Moduls behoben, oder durch Neustart, falls die installierten Module nicht mit dem laufenden Kernel übereinstimmen:

Fix:
sudo modprobe tun
If the installed modules don't match the running kernel, reboot.

Ein NVIDIA-Toolkit, das auf einem AMD-Rechner installiert ist:

Problem:
Installed nvidia-container-toolkit on an AMD machine

Das sollte einfach nicht gemacht werden – stattdessen sollte das ROCm-Bild mit den kfd- und dri-Geräten verwendet werden:

Fix:
Don't.
Use ollama/ollama:rocm with /dev/kfd and /dev/dri.

Neue Mitgliedschaften in Video- und Render-Gruppen, die in der Gruppenliste nicht angezeigt wurden:

Problem:
Added myself to video/render but `groups` still didn't show them

Es ist ein Logout und Login erforderlich, da die bestehende Sitzung ihre ursprünglichen Gruppen beibehält:

Fix:
Log out and back in.
The existing login session retains its original supplementary groups.

Modelldateien und Manifest vorhanden, während die Liste von Ollama leer ist:

Problem:
Ollama model files and manifest exist, but `ollama list` is empty

Es wird durch das Lesen des Logs diagnostiziert, und falls die Pfade zu Home und Modellen auf die Wurzel des Dateisystems verweisen, während das Laufwerk unter dem Verzeichnis des Root-Benutzers montiert ist, müssen beide Variablen explizit gesetzt werden:

Fix:
Check:
podman logs ollamaIf Ollama is using:
OLLAMA_MODELS=/.ollama/modelswhile the volume is mounted under:
 /root/.ollamaset explicitly:
HOME=/root
OLLAMA_MODELS=/root/.ollama/models

Ein Python-Test von Hermes zu Ollama, der stumm nichts ausgab:

Problem:
Hermes → Ollama Python test silently prints nothing

Es wurde behoben, indem die interaktive Flagge beim Verwenden eines Heredoc hinzugefügt wurde, oder einfach durch die Verwendung der inline-Befehlsform:

Fix:
If using `python -` with a heredoc, add `podman exec -i`.
Or simply use `python -c`.

Ein Anbietermenü ohne offensichtlichen lokalen Ollama-Eintrag:

Problem:
The Hermes provider menu doesn't contain "local Ollama"

Das Problem wurde gelöst, indem der manuelle, benutzerdefinierte Endpunkt ausgewählt und die lokale v1-URL eingegeben wurde:

Fix:
Choose:
Custom endpoint (enter URL manually)Then:
http://127.0.0.1:11434/v1

Und Hermes beschwert sich über ein unzureichendes Kontextfenster, obwohl alles andere funktionierte:

Problem:
Everything works, but Hermes reports an inadequate context window

Es wird gelöst, indem die Kontextlänge serverseitig auf 64000 gesetzt wird, Hermes auf denselben Wert konfiguriert wird und die tatsächliche Zuweisung über die Prozessliste von Ollama überprüft wird:

Fix:
Set OLLAMA_CONTEXT_LENGTH=64000 server-side and configure Hermes for the same value.
Verify the real allocation with:
ollama ps

Wo die Vertrauensgrenze liegen sollte

Die wichtigste Erkenntnis hier betrifft nicht unbedingt Ollama oder ROCm, sondern vielmehr die Entscheidung darüber, wo die Vertrauensgrenze angesiedelt werden soll. Eine Anweisung, niemals etwas außerhalb eines Verzeichnisses zu lesen, ist eine verhaltensbezogene Anweisung. Ein Podman-Container, dem der Rest des Host-Dateisystems nie zugänglich gemacht wurde, stellt eine Betriebssystemgrenze dar. Hermes’ schreibsichere Root-Struktur ist nützlich, doch Hermes selbst bezeichnet sie als zusätzliche Schicht und nicht als echten Sandbox, da Shell-Befehle in der Lage sind, die Schutzmechanismen der Dateifunktionen zu umgehen.

Das resultierende Vertrauensmodell stapelt die Schichten von schwächster zu stärkster, beginnend mit Anweisungsbeschränkungen und Hermes’ Schutz vor Dateiänderungen und endend bei der Containergrenze:

Prompt restrictions
        ↓
Hermes file write protections
        ↓
Hermes container filesystem
        ↓
Rootless Podman user namespace
        ↓
Host filesystem permissions

Auch wenn das Modell eine bösartige Anweisung erhält, die in einer Markdown-Datei versteckt ist, lautet die interessante Frage nicht, ob es der ursprünglichen Anfrage folgen wird. Vielmehr geht es darum, was der kompromittierte Agent tatsächlich erreichen kann. Bei dieser Konzeption ist die Antwort absichtlich begrenzt.

Debuggen in Schichten

Sowohl Hermes als auch Ollama entwickeln sich schnell weiter. Die aktuelle Dokumentation von Hermes unterstützt selbst gehostete, OpenAI-kompatible Endpunkte – einschließlich Ollama –, es gab jedoch auch Berichte über das Verhalten von Tool-Aufrufen über benutzerdefinierte Ollama-Endpunkte. Wenn normale Chats funktionieren, aber Hermes Tool-Aufrufe nur beschreibt, ohne sie auszuführen, sollten Sie zunächst den aktuellen Hermes-Issue-Tracker überprüfen, bevor Sie annehmen, dass der Podman-Sandbox defekt ist.

Deshalb sollte der Stack als Kette von Fragen getestet werden: Ob Podman die GPU erkennt, ob Ollama das Modell erkennt und schließlich, ob Hermes mit Ollama kommunizieren kann.

Does Podman see the GPU?
        ↓
Does Ollama see the model?
        ↓
Does Ollama actually use the GPU?
        ↓
Is the context really 64K?
        ↓
Can Hermes reach /v1/models?
        ↓
Can Hermes perform an actual file operation?
        ↓
Can Hermes reach anything it shouldn't?

Versuchen Sie nicht, alle sechs Schichten gleichzeitig zu debuggen.

Was die fertige Einrichtung bietet

Die resultierende Konfiguration ermöglicht lokale Inferenz, AMD-GPU-Verstärkung, dauerhaftes Hermes-Gedächtnis sowie einen schreibbaren Host-Arbeitsbereich:

Local inference                YES
AMD GPU acceleration           YES
Hermes persistent memory       YES
One writable host workspace    YES

Und es benötigt kein Cloud-Modell, gibt keinen Host-Dateisystemzugriff frei, stellt keine Podman- oder Docker-Sockete zur Verfügung und verfügt über keinen normalen Internet-Ausgang:

Cloud LLM required             NO
Host filesystem exposed        NO
Podman/Docker socket exposed   NO
Normal Internet egress         NO
Entire Obsidian vault exposed  NO

Das ist eine weitaus bequemere Möglichkeit, mit einem leistungsstarken lokalen Agenten zu experimentieren. Hermes hat immer noch ausreichenden Zugriff, um nützlich zu sein. Er kann lediglich nicht selbst entscheiden, was „ausreichender Zugriff“ bedeutet.