apiVersion: apps/v1 kind: Deployment metadata: name: hermes namespace: platform-engineer labels: app: hermes spec: replicas: 1 # MUST be 1 — Hermes' /opt/data is single-writer. strategy: type: Recreate # never run two pods against the same PVC selector: matchLabels: app: hermes template: metadata: labels: app: hermes spec: serviceAccountName: platform-engineer imagePullSecrets: - name: gitea-registry # Pin to the powerful amd64 node (image is linux/amd64; the NUC has 24 GiB). nodeSelector: kubernetes.io/arch: amd64 affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 preference: matchExpressions: - key: hardware operator: In values: ["high-memory"] podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: app: hermes topologyKey: kubernetes.io/hostname initContainers: # Seed /opt/data with config.yaml + SOUL.md on first boot only. # ArgoCD owns the manifests; the PVC is runtime state and is NOT reconciled. - name: seed-data image: busybox:1.36 command: ["sh", "-c"] args: - | set -e if [ ! -f /opt/data/config.yaml ]; then echo "First boot: seeding /opt/data from ConfigMap..." cp /seed/config.yaml /opt/data/config.yaml cp /seed/SOUL.md /opt/data/SOUL.md chmod 600 /opt/data/config.yaml else echo "/opt/data already initialized — leaving runtime state intact." fi mkdir -p /opt/data/home/.kube /opt/data/cron/output /opt/data/scripts /workspace volumeMounts: - name: data mountPath: /opt/data - name: seed mountPath: /seed containers: - name: hermes image: registry.rogi.casa/roger/hermes-agent:v1.35-1 imagePullPolicy: IfNotPresent # falls back to local image if present command: ["gateway", "run"] ports: - name: gateway containerPort: 8642 - name: dashboard containerPort: 9119 envFrom: - secretRef: name: hermes-env env: # k3s injects these automatically; kubectl inside the pod uses the SA token. - name: HERMES_HOME value: /opt/data volumeMounts: - name: data mountPath: /opt/data - name: workspace mountPath: /workspace resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "2Gi" cpu: "1000m" livenessProbe: httpGet: path: /health port: 8642 initialDelaySeconds: 60 periodSeconds: 30 failureThreshold: 3 securityContext: allowPrivilegeEscalation: false runAsNonRoot: false # official image runs as root for s6 init then drops to hermes volumes: - name: data persistentVolumeClaim: claimName: hermes-data - name: workspace emptyDir: {} - name: seed configMap: name: hermes-seed --- apiVersion: v1 kind: Service metadata: name: hermes namespace: platform-engineer spec: type: ClusterIP selector: app: hermes ports: - name: gateway port: 80 targetPort: 8642 - name: dashboard port: 9119 targetPort: 9119