Skip to main content

Kubernetes Deployment

Deploy AgentVisor™ to Kubernetes with sandbox isolation (gVisor by default).

Production Deployment

This page covers Kubernetes manifests and configuration. For production deployment patterns including scaling strategies, high availability, and resource planning, see the Scaling and Resilience Guide.

License required

AgentVisor production images require a valid Manetu license key. Store it in a Kubernetes Secret and inject it via secretKeyRef. See the License Key section below and the Licensing guide for full details.

Deployment Options​

AgentVisor uses gVisor by default to isolate agents using unprivileged user namespaces. gVisor provides syscall-level interception for the strongest available isolation on Linux. You can choose between two configurations:

ApproachDescriptionWhen to Use
RootlessNo elevated capabilities (default)Default; works on any cluster with unprivileged userns enabled
PrivilegedFull privileged: true security contextWhen DirectFS performance is needed or userns is unavailable

Option 1: Rootless Mode (Default)​

AgentVisor uses gVisor to sandbox agent processes. gVisor's rootless mode creates a user namespace via CLONE_NEWUSER. Two host mechanisms can block this syscall and must both be relaxed:

  • seccomp: Kubernetes' default RuntimeDefault profile may block CLONE_NEWUSER
  • AppArmor: Ubuntu 24.04+, GKE, and AKS nodes enforce AppArmor policies that restrict unprivileged user namespaces by default
Node Prerequisites

Rootless mode requires unprivileged user namespaces enabled on all nodes:

  • Linux 5.15+ supports this by default on most distributions
  • Some distributions (Debian, Ubuntu 23.10+) require: sysctl -w kernel.unprivileged_userns_clone=1
  • Verify userns: cat /proc/sys/kernel/unprivileged_userns_clone (should be 1) or cat /proc/sys/user/max_user_namespaces (should be > 0)
  • Ubuntu 24.04+, GKE, AKS: also check cat /proc/sys/kernel/apparmor_restrict_unprivileged_userns (should be 0). Set appArmorProfile: Unconfined in the container securityContext (Kubernetes 1.30+) or use the annotation on pre-1.30 clusters: container.apparmor.security.beta.kubernetes.io/<container-name>: unconfined

Container images built with agentvisor build have newuidmap/newgidmap (provided by shadow-utils on UBI 9) and subuid/subgid pre-configured.

apiVersion: apps/v1
kind: Deployment
metadata:
name: agentvisor
spec:
replicas: 3
selector:
matchLabels:
app: agentvisor
template:
metadata:
labels:
app: agentvisor
spec:
containers:
- name: agent
image: my-agent:v1
securityContext:
seccompProfile:
type: Unconfined # Allows CLONE_NEWUSER needed by gVisor rootless mode
appArmorProfile:
type: Unconfined # Required on Ubuntu 24.04+, GKE, AKS
ports:
- containerPort: 8090
env:
- name: AGENTVISOR_LICENSE_KEY
valueFrom:
secretKeyRef:
name: agentvisor-license
key: license-key
- name: AGENTVISOR_TEMPORAL_TARGET
value: "temporal.temporal-system.svc:7233"
- name: AGENTVISOR_GUEST_SANDBOX
value: "gvisor"
- name: AGENTVISOR_AUTHZ_TYPE
value: "embedded"
- name: AGENTVISOR_AUTHZ_EMBEDDED_POLICY_DOMAIN_FILES
value: "/etc/agentvisor/policies/domain.yml"
volumeMounts:
- name: policies
mountPath: /etc/agentvisor/policies
readOnly: true
resources:
limits:
memory: "2Gi"
cpu: "2"
volumes:
- name: policies
configMap:
name: agentvisor-policies

Option 2: Privileged Mode​

Use privileged mode when you need DirectFS performance or your nodes don't support unprivileged user namespaces.

Privilege Dropping

Although the container requires privileged: true, the AgentVisor host runtime immediately drops privileges to uid/gid 1000 after initializing the gVisor sandbox. The elevated privileges are only used during the brief startup phase; all subsequent operations run as an unprivileged user.

apiVersion: apps/v1
kind: Deployment
metadata:
name: agentvisor
spec:
replicas: 3
selector:
matchLabels:
app: agentvisor
template:
metadata:
labels:
app: agentvisor
spec:
containers:
- name: agent
image: my-agent:v1
securityContext:
privileged: true # Required for gVisor privileged mode
runAsUser: 0
ports:
- containerPort: 8090
env:
- name: AGENTVISOR_LICENSE_KEY
valueFrom:
secretKeyRef:
name: agentvisor-license
key: license-key
- name: AGENTVISOR_TEMPORAL_TARGET
value: "temporal.temporal-system.svc:7233"
- name: AGENTVISOR_GUEST_SANDBOX
value: "gvisor"
- name: AGENTVISOR_GUEST_ROOTLESS
value: "false"
- name: AGENTVISOR_AUTHZ_TYPE
value: "embedded"
- name: AGENTVISOR_AUTHZ_EMBEDDED_POLICY_DOMAIN_FILES
value: "/etc/agentvisor/policies/domain.yml"
volumeMounts:
- name: policies
mountPath: /etc/agentvisor/policies
readOnly: true
resources:
limits:
memory: "2Gi"
cpu: "2"
volumes:
- name: policies
configMap:
name: agentvisor-policies

Comparison of Options​

OptionPrivileges RequiredPerformanceNode Requirements
Rootless (default)NoneGood (9P filesystem)Unprivileged userns enabled
Privilegedprivileged: trueBest (DirectFS)None
Recommendation
  1. Use Rootless mode (default) — works on any modern cluster with unprivileged user namespaces
  2. Use Privileged mode when you need maximum DirectFS performance or nodes lack userns support

Service​

apiVersion: v1
kind: Service
metadata:
name: agentvisor
spec:
selector:
app: agentvisor
ports:
- name: http
port: 8090
targetPort: 8090
type: ClusterIP

The http port name is referenced by the ServiceMonitor below — Prometheus's monitoring.coreos.com operator resolves endpoints[].port against a Service's named port, not a raw port number.

Ingress​

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: agentvisor
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
spec:
rules:
- host: agents.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: agentvisor
port:
number: 8090
tls:
- hosts:
- agents.example.com
secretName: agentvisor-tls

ConfigMap for Policies​

apiVersion: v1
kind: ConfigMap
metadata:
name: agentvisor-policies
data:
domain.yml: |
apiVersion: iamlite.manetu.io/v1beta1
kind: PolicyDomain
metadata:
name: production
spec:
policies:
- mrn: "mrn:iam:policy:allow-authenticated"
name: allow-authenticated
rego: |
package authz
default allow = false
allow { input.principal.sub != "" }

resource-groups:
- mrn: "mrn:iam:resource-group:allowed"
name: allowed
policy: "mrn:iam:policy:allow-authenticated"

resources:
- name: openai
selector: ["mrn:agentvisor:http:api\\.openai\\.com.*"]
group: "mrn:iam:resource-group:allowed"

License Key​

Store the AgentVisor license key in a Kubernetes Secret:

apiVersion: v1
kind: Secret
metadata:
name: agentvisor-license
type: Opaque
stringData:
license-key: "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"

Reference it from the Deployment via secretKeyRef as shown in the examples above. See the Licensing guide for machine code configuration and offline/air-gap requirements.

Secrets​

Store sensitive configuration such as API keys and credentials. For Temporal Cloud authentication secrets (API keys or mTLS certificates), see the Temporal Configuration Guide.

apiVersion: v1
kind: Secret
metadata:
name: agentvisor-secrets
type: Opaque
stringData:
api-key: "your-api-key"
---
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: agent
envFrom:
- secretRef:
name: agentvisor-secrets

Network Policy​

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: agentvisor-network-policy
spec:
podSelector:
matchLabels:
app: agentvisor
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- port: 8090
egress:
# Temporal
- to:
- namespaceSelector:
matchLabels:
name: temporal-system
ports:
- port: 7233
# External HTTPS (LLM APIs)
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
- 169.254.0.0/16
ports:
- port: 443
# DNS
- to:
- namespaceSelector: {}
ports:
- port: 53
protocol: UDP

HorizontalPodAutoscaler​

Auto-scale AgentVisor pods based on resource utilization. For the canonical manifest (including scale-up/scale-down behavior tuning), threshold guidance, and custom metrics considerations, see Auto-Scaling Configuration in the Scaling and Resilience Guide. Its minReplicas: 3 matches the high-availability replica count used by the Deployment manifests on this page.

PodDisruptionBudget​

Protect availability during cluster maintenance and rolling updates. For the canonical manifest and the reasoning behind its replica math, see PodDisruptionBudget in the Scaling and Resilience Guide. Its minAvailable: 2 matches the high-availability replica count used by the Deployment manifests on this page.

ServiceMonitor (Prometheus)​

telemetry.metrics.enabled defaults to false, and metrics are served on the same port as the rest of the HTTP API (there is no separate metrics port) — set AGENTVISOR_TELEMETRY_METRICS_ENABLED=true on the Deployment's containers before adding this ServiceMonitor, or /metrics will 404.

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: agentvisor
spec:
selector:
matchLabels:
app: agentvisor
endpoints:
- port: http
path: /metrics
interval: 30s

Helm Chart (Example values)​

# values.yaml
replicaCount: 3

image:
repository: my-agent
tag: v1
pullPolicy: IfNotPresent

securityContext:
seccompProfile:
type: Unconfined # Required for rootless gVisor (default mode)
appArmorProfile:
type: Unconfined # Required on Ubuntu 24.04+, GKE, AKS
# For privileged mode instead: set privileged: true, runAsUser: 0,
# and AGENTVISOR_GUEST_ROOTLESS=false

resources:
limits:
cpu: 2
memory: 2Gi
requests:
cpu: 500m
memory: 512Mi

temporal:
target: temporal.temporal-system.svc:7233
namespace: default

policies:
domain: |
apiVersion: iamlite.manetu.io/v1beta1
...

ingress:
enabled: true
host: agents.example.com
tls:
enabled: true
secretName: agentvisor-tls

autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10

Troubleshooting​

For general AgentVisor troubleshooting — including gVisor rootless preflight failures (seccomp/AppArmor), policy evaluation errors, and Temporal connectivity — see the Troubleshooting Guide.

Pod Not Starting​

kubectl describe pod -l app=agentvisor
kubectl logs -l app=agentvisor

If the pod fails during sandbox initialization, check the container's securityContext against the manifests above:

kubectl get pod -l app=agentvisor -o jsonpath='{.items[*].spec.containers[*].securityContext}'

then see Sandbox Startup Failures — specifically "gVisor rootless preflight failed" — for the seccomp/AppArmor causes and fixes.

Testing Temporal Connectivity from Within the Cluster​

kubectl run -it --rm debug --image=curlimages/curl -- \
curl temporal.temporal-system.svc:7233

See Temporal Connectivity if this doesn't succeed.