<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: RevOS.ai</title>
    <description>The latest articles on DEV Community by RevOS.ai (revos).</description>
    <link>https://dev.to/revos</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F15122%2F9651c7e6-30fa-4b81-893a-e2cc856a1d48.png</url>
      <title>DEV Community: RevOS.ai</title>
      <link>https://dev.to/revos</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/revos"/>
    <language>en</language>
    <item>
      <title>Running Hermes Agent on Kubernetes: What Breaks, What Doesn't, and a Production-Safe Setup</title>
      <dc:creator>RevOS</dc:creator>
      <pubDate>Tue, 06 Oct 2026 10:07:05 +0000</pubDate>
      <link>https://dev.to/revos/running-hermes-agent-on-kubernetes-what-breaks-what-doesnt-and-a-production-safe-setup-3bbm</link>
      <guid>https://dev.to/revos/running-hermes-agent-on-kubernetes-what-breaks-what-doesnt-and-a-production-safe-setup-3bbm</guid>
      <description>&lt;p&gt;Hermes Agent is Nous Research's self-improving AI agent. It builds skills from experience, keeps persistent memory across sessions, and connects to Telegram, Discord, Slack, WhatsApp, and Signal out of the box. With 230k+ GitHub stars, plenty of teams now want to run it somewhere more durable than a laptop.&lt;/p&gt;

&lt;p&gt;Kubernetes is the obvious destination and the least documented one. There's no official Helm chart. The container image's init sequence needs root in a way that collides with a standard restricted pod security context. Its state model assumes a single writer. And the reload story that works fine in a terminal has no equivalent your GitOps controller can call.&lt;/p&gt;

&lt;p&gt;None of that is a reason to avoid Hermes on Kubernetes. It's a reason to know the constraints before you write the manifest. Below: what genuinely breaks, what only looks like it breaks, and a reference deployment to start from.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tested against:&lt;/strong&gt; Hermes Agent &lt;code&gt;v2026.8.27&lt;/code&gt; · official image &lt;code&gt;nousresearch/hermes-agent&lt;/code&gt; · Kubernetes 1.31+ · containerd 2.x. Hermes ships releases every few days. Before you copy anything below, check it against the version you're actually deploying, and pin that version.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Is there an official Helm chart for Hermes Agent?
&lt;/h2&gt;

&lt;p&gt;No. Nous Research's own documentation covers &lt;code&gt;install.sh&lt;/code&gt;, Docker and Docker Compose, and Nix packages. Kubernetes never comes up as a deployment target. That gap is filled entirely by the community, and the three charts that fill it don't agree on much:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ultraworkers/hermes-agent-helm-chart:&lt;/strong&gt; the most feature-complete option: renders a Deployment, PVC, Secret, Service, Ingress, and an Istio VirtualService, plus an "operator-ready" mode defining a &lt;code&gt;HermesTenant&lt;/code&gt; CRD. It's also the only one that encodes the single-writer rule as a hard constraint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;jyje/hermes-agent:&lt;/strong&gt; listed on Artifact Hub as a verified publisher, distributed over an OCI registry, with multi-arch images and example ArgoCD manifests. It tracks upstream Hermes releases more closely than the other two.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;duyet/hermes-agent:&lt;/strong&gt; the simplest of the three, splitting persistence into separate data and workspace volumes, with an optional Prometheus &lt;code&gt;ServiceMonitor&lt;/code&gt;. Not a verified publisher, so read the templates before trusting the defaults.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of the three is backed by Nous Research. Read the templates end to end before you apply one, and expect to override the security context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hermes Agent is stateful: define your persistence boundary first
&lt;/h2&gt;

&lt;p&gt;This is the decision everything else hangs off, so make it before you pick a chart.&lt;/p&gt;

&lt;p&gt;Hermes keeps &lt;strong&gt;all&lt;/strong&gt; of its mutable state (configuration, &lt;code&gt;MEMORY.md&lt;/code&gt;, &lt;code&gt;USER.md&lt;/code&gt;, session history in a local SQLite database, and every learned skill) under &lt;code&gt;HERMES_HOME&lt;/code&gt;. In the official image that's the &lt;code&gt;/opt/data&lt;/code&gt; volume, which the Docker docs call "the single source of truth for all Hermes state."&lt;/p&gt;

&lt;p&gt;So the accurate invariant is not "Hermes can't scale." It's:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Treat each &lt;code&gt;HERMES_HOME&lt;/code&gt; as a single-writer state domain.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can run many Hermes instances in one cluster. What you must not do is put two active pods behind the same mutable state and assume Kubernetes has handed you horizontal scaling. Nothing arbitrates concurrent writes to that SQLite database and those Markdown files.&lt;/p&gt;

&lt;p&gt;In practice, for any deployment with persistence enabled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;replicaCount&lt;/code&gt; stays at &lt;strong&gt;1&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strategy.type&lt;/code&gt; must be &lt;strong&gt;&lt;code&gt;Recreate&lt;/code&gt;&lt;/strong&gt;, not &lt;code&gt;RollingUpdate&lt;/code&gt;, a rolling update deliberately runs the old and new pod together, which is exactly the two-writer window to avoid.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ReadWriteOnce&lt;/code&gt; is the sensible guardrail on the PVC. RWX isn't automatically unsafe (the invariant is one &lt;em&gt;active writer&lt;/em&gt;, not one &lt;em&gt;mount&lt;/em&gt;) but RWO enforces it at the storage layer instead of trusting your deployment config.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Need Hermes for more than one team or tenant? One release per tenant, each with its own volume. Any chart that doesn't enforce this is a data-corruption risk waiting for a bad rollout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the official image fights &lt;code&gt;runAsNonRoot&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The common shorthand, "Hermes has to run as root," is wrong, and the precise version matters when you're arguing with a platform team about a Pod Security Standard exemption.&lt;/p&gt;

&lt;p&gt;The official image uses &lt;strong&gt;s6-overlay&lt;/strong&gt; as its init system. s6-overlay's &lt;code&gt;/init&lt;/code&gt; runs as root so it can &lt;code&gt;chown&lt;/code&gt; the volume on first boot, then drops to the &lt;code&gt;hermes&lt;/code&gt; user via &lt;code&gt;s6-setuidgid&lt;/code&gt; for the main program and all supervised services.&lt;/p&gt;

&lt;p&gt;So the agent process itself does &lt;em&gt;not&lt;/em&gt; run as root. Only the bootstrap does. But that's still enough to break a restricted security context: a pod spec with &lt;code&gt;runAsNonRoot: true&lt;/code&gt; or a non-zero &lt;code&gt;runAsUser&lt;/code&gt; blocks the sequence before the agent ever starts, and you get something close to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/package/admin/s6-overlay/libexec/preinit: fatal: /run belongs to uid 0 instead of 1000,
has insecure and/or unworkable permissions, and we're lacking the privileges to fix it.
s6-overlay-suexec: fatal: child failed with exit code 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One detail worth getting right, because published examples routinely get it wrong: &lt;strong&gt;the &lt;code&gt;hermes&lt;/code&gt; user is UID 10000, not 1000.&lt;/strong&gt; Set &lt;code&gt;fsGroup: 1000&lt;/code&gt; and the volume ends up owned by the wrong group.&lt;/p&gt;

&lt;p&gt;A reasonable starting security context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;podSecurityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;runAsUser&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="na"&gt;runAsNonRoot&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;fsGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10000&lt;/span&gt;
  &lt;span class="na"&gt;fsGroupChangePolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OnRootMismatch&lt;/span&gt;
  &lt;span class="na"&gt;seccompProfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RuntimeDefault&lt;/span&gt;
&lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;allowPrivilegeEscalation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;readOnlyRootFilesystem&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;runAsNonRoot&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;capabilities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;drop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ALL"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CHOWN"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SETUID"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SETGID"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is meaningfully narrower than a privileged pod: every capability is dropped and only the ones the bootstrap needs come back. Treat the exact capability list as a &lt;strong&gt;starting point to verify against your image version&lt;/strong&gt;, not a universal recipe. Start from &lt;code&gt;drop: ["ALL"]&lt;/code&gt;, add back only what your logs prove necessary, and re-test on version bumps.&lt;/p&gt;

&lt;p&gt;If your cluster enforces the &lt;code&gt;restricted&lt;/code&gt; Pod Security Standard, this workload needs a namespace exemption. Forcing the official image to start non-root isn't a setting you've missed, it wasn't built for that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Hermes can hot-reload, and what it can't
&lt;/h2&gt;

&lt;p&gt;The claim that Hermes has "no hot-reload" is false, and the real limitation is more interesting.&lt;/p&gt;

&lt;p&gt;Hermes documents three reload commands: &lt;code&gt;/reload-mcp&lt;/code&gt; (reload MCP servers from &lt;code&gt;config.yaml&lt;/code&gt;), &lt;code&gt;/reload-skills&lt;/code&gt; (re-scan for newly installed or removed skills), and &lt;code&gt;/reload&lt;/code&gt; (reload &lt;code&gt;.env&lt;/code&gt; variables into the running session).&lt;/p&gt;

&lt;p&gt;The runtime can absolutely pick up new MCP servers and skills without a restart. The gap is the &lt;em&gt;interface&lt;/em&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Hermes can hot-reload MCP servers and skills interactively, but doesn't expose the same lifecycle through its admin API.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the actual Kubernetes problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ConfigMap updated -&amp;gt; kubelet syncs file into the pod -&amp;gt; no reconciliation hook -&amp;gt; nothing happens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file changes on disk. Nothing tells the process. Your options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Restart the workload.&lt;/strong&gt; &lt;code&gt;kubectl rollout restart deployment/hermes-agent&lt;/code&gt;. Blunt but declarative, and with &lt;code&gt;Recreate&lt;/code&gt; you're accepting a brief outage anyway. Wire a checksum annotation over the ConfigMap into the pod template so the rollout fires automatically on config change, the standard Helm &lt;code&gt;checksum/config&lt;/code&gt; pattern.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drive the runtime reload path&lt;/strong&gt; from a sidecar or an operator with a channel into the agent. Workable, but you're building the missing hook yourself.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Until an HTTP reload endpoint exists upstream, option 1 with a config checksum is the pragmatic default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources, probes, and graceful shutdown
&lt;/h2&gt;

&lt;p&gt;Three things almost every Hermes-on-Kubernetes writeup omits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resources.&lt;/strong&gt; Nous Research's Docker documentation recommends a minimum of 1 GB memory and 1 CPU core, with 2-4 GB memory and 2 cores recommended. Browser automation (Playwright/Chromium) is the most memory-hungry feature, so budget above that range if the agent drives a browser. Set a memory &lt;em&gt;limit&lt;/em&gt;, but consider leaving CPU unlimited, agent workloads are bursty, and CPU throttling shows up as mysteriously slow tool calls rather than a clean failure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Probes.&lt;/strong&gt; The gateway listens on port &lt;strong&gt;8642&lt;/strong&gt;. The Dockerfile ships &lt;strong&gt;no &lt;code&gt;HEALTHCHECK&lt;/code&gt; and no &lt;code&gt;EXPOSE&lt;/code&gt;&lt;/strong&gt; instruction, so you're defining this yourself. A &lt;code&gt;tcpSocket&lt;/code&gt; probe against 8642 is the portable choice. The one that matters most is the &lt;code&gt;startupProbe&lt;/code&gt;: first boot does volume &lt;code&gt;chown&lt;/code&gt; work and profile reconciliation, and a liveness probe with a short threshold will kill the pod mid-bootstrap and loop forever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Graceful shutdown.&lt;/strong&gt; Hermes is stateful, so SIGTERM handling isn't academic. Give it room with &lt;code&gt;terminationGracePeriodSeconds: 30&lt;/code&gt;, and test what actually happens to in-flight agent runs, SQLite session writes, and open gateway connections when a pod is evicted. Node upgrades and spot reclaims will do this to you eventually, and &lt;code&gt;Recreate&lt;/code&gt; means no second pod is covering the gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two security boundaries, not one
&lt;/h2&gt;

&lt;p&gt;Most Kubernetes writeups on agents cover pod security and stop. For an agent that executes code, that's half the problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boundary 1, pod security.&lt;/strong&gt; Root init, capabilities, seccomp, filesystem, service account. Covered above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boundary 2, agent execution.&lt;/strong&gt; What the model can actually run, and what it can reach. This is the one that should worry you more, because the pod &lt;em&gt;is&lt;/em&gt; the blast radius.&lt;/p&gt;

&lt;p&gt;Hermes has real defences here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Credential filtering.&lt;/strong&gt; &lt;code&gt;execute_code&lt;/code&gt; blocks environment variables whose names contain &lt;code&gt;KEY&lt;/code&gt;, &lt;code&gt;TOKEN&lt;/code&gt;, &lt;code&gt;SECRET&lt;/code&gt;, &lt;code&gt;PASSWORD&lt;/code&gt;, &lt;code&gt;CREDENTIAL&lt;/code&gt;, &lt;code&gt;PASSWD&lt;/code&gt;, or &lt;code&gt;AUTH&lt;/code&gt;. MCP stdio subprocesses receive only a short safe list of variables.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The bypass is explicit.&lt;/strong&gt; Variables declared by a skill or listed in &lt;code&gt;env_passthrough&lt;/code&gt; skip those filters. That's the mechanism to audit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approval checks are skipped in container backends.&lt;/strong&gt; Hermes skips dangerous-command approval in the &lt;code&gt;docker&lt;/code&gt;, &lt;code&gt;singularity&lt;/code&gt;, &lt;code&gt;modal&lt;/code&gt;, &lt;code&gt;daytona&lt;/code&gt;, and &lt;code&gt;vercel_sandbox&lt;/code&gt; backends, on the reasoning that "the container itself is the security boundary." In a Kubernetes pod that assumption is load-bearing: whatever your pod can reach, prompt-injected code can reach.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSRF protection exists, and is disableable.&lt;/strong&gt; Web tools block RFC 1918 ranges and loopback by default. In a cluster, RFC 1918 &lt;em&gt;is&lt;/em&gt; your service mesh, your databases, and the kubelet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which leads to the control most often missing: &lt;strong&gt;default-deny egress.&lt;/strong&gt; An agent that browses the web and calls tools is not a normal web app, and "what can this pod reach?" is a more consequential question than which Linux capabilities it holds. Start from deny-all and allow only what's needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NetworkPolicy&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hermes-agent-egress&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;podSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app.kubernetes.io/name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hermes-agent&lt;/span&gt;
  &lt;span class="na"&gt;policyTypes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Egress"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;egress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# DNS&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;namespaceSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;kubernetes.io/metadata.name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kube-system&lt;/span&gt;
          &lt;span class="na"&gt;podSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;k8s-app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kube-dns&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UDP&lt;/span&gt;
          &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;53&lt;/span&gt;
    &lt;span class="c1"&gt;# HTTPS to the internet, minus internal ranges&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;ipBlock&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;cidr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.0.0/0&lt;/span&gt;
            &lt;span class="na"&gt;except&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;10.0.0.0/8&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;172.16.0.0/12&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;192.168.0.0/16&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;169.254.169.254/32&lt;/span&gt;   &lt;span class="c1"&gt;# cloud instance metadata&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
          &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Blocking &lt;code&gt;169.254.169.254&lt;/code&gt; matters especially: on a node without IMDSv2 enforced, an agent that can reach instance metadata can often reach the node's IAM role.&lt;/p&gt;

&lt;p&gt;On credentials: because the agent has terminal access, anything in its environment is potentially readable by it. On EKS, use IRSA or EKS Pod Identity rather than static access keys in a Secret. Elsewhere, External Secrets Operator, Vault, or Sealed Secrets all keep plaintext keys out of Git. And scope the IAM role tightly: the agent's permissions are the agent's capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  A minimal deployment that actually works
&lt;/h2&gt;

&lt;p&gt;Pinned, single-writer, probed, and resource-bounded. Adjust the storage class and image tag, and read it before you apply it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PersistentVolumeClaim&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hermes-agent-data&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ReadWriteOnce"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;20Gi&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hermes-agent&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app.kubernetes.io/name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hermes-agent&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Recreate&lt;/span&gt;          &lt;span class="c1"&gt;# never two writers on one HERMES_HOME&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app.kubernetes.io/name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hermes-agent&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app.kubernetes.io/name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hermes-agent&lt;/span&gt;
      &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;checksum/config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;REPLACE_WITH_CONFIGMAP_CHECKSUM"&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;serviceAccountName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hermes-agent&lt;/span&gt;
      &lt;span class="na"&gt;terminationGracePeriodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
      &lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;runAsUser&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
        &lt;span class="na"&gt;runAsNonRoot&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;fsGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10000&lt;/span&gt;
        &lt;span class="na"&gt;fsGroupChangePolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OnRootMismatch&lt;/span&gt;
        &lt;span class="na"&gt;seccompProfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RuntimeDefault&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hermes-agent&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nousresearch/hermes-agent:v2026.8.27&lt;/span&gt;   &lt;span class="c1"&gt;# pin it; never :latest&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gateway"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway&lt;/span&gt;
              &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8642&lt;/span&gt;
          &lt;span class="na"&gt;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hermes-agent-secrets&lt;/span&gt;
          &lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;allowPrivilegeEscalation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
            &lt;span class="na"&gt;readOnlyRootFilesystem&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
            &lt;span class="na"&gt;runAsNonRoot&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
            &lt;span class="na"&gt;capabilities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;drop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ALL"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
              &lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CHOWN"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SETUID"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SETGID"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
          &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;
              &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2Gi&lt;/span&gt;
            &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;4Gi&lt;/span&gt;
          &lt;span class="na"&gt;startupProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;tcpSocket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway&lt;/span&gt;
            &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
            &lt;span class="na"&gt;failureThreshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
          &lt;span class="na"&gt;readinessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;tcpSocket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway&lt;/span&gt;
            &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
          &lt;span class="na"&gt;livenessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;tcpSocket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway&lt;/span&gt;
            &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
            &lt;span class="na"&gt;failureThreshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;data&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/opt/data&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;data&lt;/span&gt;
          &lt;span class="na"&gt;persistentVolumeClaim&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;claimName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hermes-agent-data&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hermes-agent&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app.kubernetes.io/name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hermes-agent&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8642&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pair it with the NetworkPolicy above, a Secret (ideally rendered by External Secrets or Sealed Secrets rather than committed), and a ConfigMap for &lt;code&gt;config.yaml&lt;/code&gt; if you're managing MCP servers declaratively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Image tag pinned to a specific release, never &lt;code&gt;:latest&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;replicas: 1&lt;/code&gt; and &lt;code&gt;strategy.type: Recreate&lt;/code&gt;, with one &lt;code&gt;HERMES_HOME&lt;/code&gt; per instance&lt;/li&gt;
&lt;li&gt;PVC is &lt;code&gt;ReadWriteOnce&lt;/code&gt;, and the storage class supports that access mode&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fsGroup: 10000&lt;/code&gt; matches the image's &lt;code&gt;hermes&lt;/code&gt; user&lt;/li&gt;
&lt;li&gt;Capabilities start from &lt;code&gt;drop: ["ALL"]&lt;/code&gt;, with additions verified against your image version&lt;/li&gt;
&lt;li&gt;Namespace exemption in place if you enforce the &lt;code&gt;restricted&lt;/code&gt; Pod Security Standard&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;startupProbe&lt;/code&gt; generous enough to survive first-boot volume work&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terminationGracePeriodSeconds&lt;/code&gt; set, and eviction behaviour tested against in-flight runs&lt;/li&gt;
&lt;li&gt;Memory limit sized for browser automation if skills use it&lt;/li&gt;
&lt;li&gt;Default-deny egress NetworkPolicy, with instance metadata (&lt;code&gt;169.254.169.254&lt;/code&gt;) blocked&lt;/li&gt;
&lt;li&gt;No static cloud credentials in Secrets, use IRSA / Pod Identity / Vault / External Secrets&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;env_passthrough&lt;/code&gt; and skill-declared variables audited, they bypass credential filtering&lt;/li&gt;
&lt;li&gt;Config changes trigger a rollout (checksum annotation) or a deliberate reload path&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where this connects to governed data
&lt;/h2&gt;

&lt;p&gt;Everything above keeps Hermes Agent alive and contained. None of it says whether the answers it produces are correct, that depends on what it's allowed to read and how well-defined that data is. The same discipline that makes an agent's infrastructure trustworthy (scoped permissions, no unmanaged state, changes that go through review) reappears in agentic data engineering, where an agent's output earns trust through layered checks rather than raw model capability. Point an agent like this at real business data instead of chat platforms and a semantic layer is what stops it guessing at what "revenue" or "active customer" means.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on the &lt;a href="https://www.revos.ai/blog/hermes-agent-kubernetes" rel="noopener noreferrer"&gt;RevOS blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>ai</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
