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 scaling strategy, threshold tuning, and custom metrics considerations, see the Scaling and Resilience Guide.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: agentvisor
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: agentvisor
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80

PodDisruptionBudget

Protect availability during cluster maintenance and rolling updates. For high availability deployment patterns including replica counts and topology spread, see the Scaling and Resilience Guide.

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: agentvisor
spec:
minAvailable: 1
selector:
matchLabels:
app: agentvisor

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: 2
maxReplicas: 10

Troubleshooting

Pod Not Starting

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

gVisor Issues

# Check container has required privileges
kubectl get pod -l app=agentvisor -o jsonpath='{.items[*].spec.containers[*].securityContext}'

# Check logs for sandbox initialization errors
kubectl logs -l app=agentvisor | grep -i "sandbox\|gvisor\|runsc"

Temporal Connection

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