Kubernetes Deployment (Helm)
Deploy Dvara on Kubernetes with a single helm install command using the official Helm chart.
Prerequisites
- Kubernetes 1.28+
- Helm 3.x
kubectlconfigured for your cluster
Quick Start
From OCI Registry (recommended)
Pre-built images and the Helm chart are published to GitHub Container Registry:
# Install from OCI registry (uses pre-built GHCR images)
helm install dvara oci://ghcr.io/kdhrubo/dvara/charts/dvara \
--set secrets.mockProviderEnabled=true
# Wait for pods to be ready
kubectl rollout status deployment/dvara-server
kubectl rollout status deployment/dvara-ui
# Verify
kubectl port-forward svc/dvara-server 8080:8080 &
curl http://localhost:8080/status
From Local Chart
# Install from local chart directory
helm install dvara charts/dvara/ \
--set secrets.mockProviderEnabled=true
# Wait for pods to be ready
kubectl rollout status deployment/dvara-server
kubectl rollout status deployment/dvara-ui
# Verify
kubectl port-forward svc/dvara-server 8080:8080 &
curl http://localhost:8080/status
Installing with Provider Keys
helm install dvara charts/dvara/ \
--set secrets.providerKeys.openai=sk-... \
--set secrets.providerKeys.anthropic=sk-ant-...
Or use a values file:
# my-values.yaml
secrets:
providerKeys:
openai: sk-...
anthropic: sk-ant-...
gemini: AIza...
helm install dvara charts/dvara/ -f my-values.yaml
Configuration Reference
Gateway Server
| Parameter | Description | Default |
|---|---|---|
gatewayServer.enabled | Enable gateway-server | true |
gatewayServer.replicaCount | Replicas (ignored when HPA enabled) | 1 |
gatewayServer.image.repository | Image repository | ghcr.io/kdhrubo/dvara/gateway-server |
gatewayServer.image.tag | Image tag (defaults to chart appVersion) | "" |
gatewayServer.gatewayMode | Operating mode: standalone or full | full |
gatewayServer.region.id | Region identity for multi-region deployments | "" |
gatewayServer.region.name | Human-readable region name | "" |
gatewayServer.javaOpts | JVM options | "" |
gatewayServer.resources.requests.cpu | CPU request | 250m |
gatewayServer.resources.requests.memory | Memory request | 512Mi |
gatewayServer.resources.limits.cpu | CPU limit | 2 |
gatewayServer.resources.limits.memory | Memory limit | 1Gi |
gatewayServer.service.type | Service type | ClusterIP |
gatewayServer.service.port | Service port | 8080 |
Gateway UI
| Parameter | Description | Default |
|---|---|---|
gatewayUi.enabled | Enable gateway-ui | true |
gatewayUi.replicaCount | Number of replicas | 1 |
gatewayUi.image.repository | Image repository | ghcr.io/kdhrubo/dvara/gateway-ui |
gatewayUi.gatewayServerUrl | Override auto-discovered server URL | "" (auto) |
gatewayUi.resources.requests.cpu | CPU request | 100m |
gatewayUi.resources.requests.memory | Memory request | 256Mi |
gatewayUi.service.type | Service type | ClusterIP |
gatewayUi.service.port | Service port | 8090 |
Gateway UI Health Probes:
| Probe | Path | Purpose |
|---|---|---|
| Liveness | /actuator/health/liveness | Basic liveness check |
| Readiness | /actuator/health/readiness | Includes controlPlane check (gateway-server connectivity) |
| Startup | /actuator/health/liveness | Allows JVM warmup before liveness kicks in |
Secrets
| Parameter | Description | Default |
|---|---|---|
secrets.create | Create the Secret resource | true |
secrets.existingSecret | Use an existing Secret instead | "" |
secrets.providerKeys.openai | OpenAI API key | "" |
secrets.providerKeys.anthropic | Anthropic API key | "" |
secrets.providerKeys.gemini | Gemini API key | "" |
secrets.providerKeys.awsAccessKeyId | AWS access key (Bedrock) | "" |
secrets.providerKeys.awsSecretAccessKey | AWS secret key (Bedrock) | "" |
secrets.ollamaEnabled | Enable Ollama provider | "" |
secrets.ollamaBaseUrl | Ollama base URL | "" |
secrets.bedrockEnabled | Enable Bedrock provider | "" |
secrets.mockProviderEnabled | Enable mock provider | "" |
secrets.gatewayInternalSecret | Shared secret for /internal/* | "" |
secrets.gatewayEncryptionMasterPassword | Master password for ENC: values | "" |
secrets.enterpriseLicenseKey | Enterprise license JWT (signed, required for MCP proxy) | "" |
MCP Proxy Server (Enterprise)
| Parameter | Description | Default |
|---|---|---|
mcpProxyServer.enabled | Enable MCP proxy (requires enterprise license) | false |
mcpProxyServer.replicaCount | Replicas (ignored when HPA enabled) | 2 |
mcpProxyServer.image.repository | Image repository | ghcr.io/kdhrubo/dvara/mcp-proxy-server |
mcpProxyServer.image.tag | Image tag (defaults to chart appVersion) | "" |
mcpProxyServer.javaOpts | JVM options | "" |
mcpProxyServer.gatewayServerUrl | Override auto-discovered server URL | "" (auto) |
mcpProxyServer.resources.requests.cpu | CPU request | 250m |
mcpProxyServer.resources.requests.memory | Memory request | 512Mi |
mcpProxyServer.resources.limits.cpu | CPU limit | 2 |
mcpProxyServer.resources.limits.memory | Memory limit | 1Gi |
mcpProxyServer.service.type | Service type (internal-only) | ClusterIP |
mcpProxyServer.service.port | Service port | 8070 |
mcpProxyServer.autoscaling.enabled | Enable HPA | false |
mcpProxyServer.autoscaling.minReplicas | Minimum replicas | 2 |
mcpProxyServer.autoscaling.maxReplicas | Maximum replicas | 20 |
mcpProxyServer.pdb.enabled | Enable PDB | false |
mcpProxyServer.pdb.minAvailable | Min available pods | 1 |
mcpProxyServer.networkPolicy.enabled | Enable NetworkPolicy (deny external, allow gateway-server) | false |
mcpProxyServer.serviceMonitor.enabled | Enable Prometheus ServiceMonitor | false |
The MCP proxy is deployed as an internal-only ClusterIP service. It should be accessed by the gateway-server, not exposed externally. When networkPolicy.enabled is true, only pods matching the gateway-server selector labels can reach the MCP proxy.
Ingress
| Parameter | Description | Default |
|---|---|---|
ingress.enabled | Enable Ingress | false |
ingress.className | Ingress class (nginx, traefik, alb) | "" |
ingress.annotations | Ingress annotations | {} |
ingress.gatewayServer.hosts | Server host/path rules | [{host: gateway.example.com}] |
ingress.gatewayServer.tls | Server TLS config | [] |
ingress.gatewayUi.hosts | UI host/path rules | [{host: admin.example.com}] |
ingress.gatewayUi.tls | UI TLS config | [] |
Graceful Shutdown & Rolling Updates
| Parameter | Description | Default |
|---|---|---|
gatewayServer.terminationGracePeriodSeconds | Pod termination grace period (must exceed preStop + drain) | 45 |
gatewayServer.preStopSleepSeconds | Sleep before SIGTERM (endpoint de-registration propagation) | 5 |
gatewayServer.rollingUpdate.maxSurge | Max extra pods during rolling update | 1 |
gatewayServer.rollingUpdate.maxUnavailable | Max unavailable pods during rolling update (0 = zero-downtime) | 0 |
gatewayServer.topologySpreadConstraints | Topology spread for cross-zone scheduling | [] |
The default configuration ensures zero-downtime rolling updates: maxSurge: 1 creates one new pod before terminating old ones, and maxUnavailable: 0 ensures at least N pods are always ready. The preStopSleepSeconds delay allows Kubernetes endpoint propagation to complete before the application receives SIGTERM and begins its 30-second graceful drain.
Autoscaling (HPA)
| Parameter | Description | Default |
|---|---|---|
gatewayServer.autoscaling.enabled | Enable HPA | false |
gatewayServer.autoscaling.minReplicas | Minimum replicas | 2 |
gatewayServer.autoscaling.maxReplicas | Maximum replicas | 10 |
gatewayServer.autoscaling.targetCPUUtilizationPercentage | CPU target | 70 |
gatewayServer.autoscaling.targetMemoryUtilizationPercentage | Memory target | 80 |
gatewayServer.autoscaling.behavior.scaleUp.stabilizationWindowSeconds | Wait before scaling up | 30 |
gatewayServer.autoscaling.behavior.scaleDown.stabilizationWindowSeconds | Wait before scaling down | 300 |
The default HPA behavior scales up quickly (50% per minute after 30s stabilization) but scales down conservatively (25% per 2 minutes after 5-minute stabilization) to prevent flapping.
Pod Disruption Budget
| Parameter | Description | Default |
|---|---|---|
gatewayServer.pdb.enabled | Enable PDB | false |
gatewayServer.pdb.minAvailable | Min available pods | 1 |
gatewayServer.pdb.maxUnavailable | Max unavailable pods | "" |
Prometheus ServiceMonitor
| Parameter | Description | Default |
|---|---|---|
gatewayServer.serviceMonitor.enabled | Enable (requires Prometheus Operator) | false |
gatewayServer.serviceMonitor.interval | Scrape interval | 30s |
gatewayServer.serviceMonitor.path | Metrics path | /actuator/prometheus |
gatewayServer.serviceMonitor.additionalLabels | Labels for monitor selection | {} |
Common Deployment Patterns
Standalone Mode (Default)
No external dependencies. Uses in-memory storage and the mock provider for testing:
helm install dvara charts/dvara/ \
--set secrets.mockProviderEnabled=true
Production with OpenAI
# production-values.yaml
gatewayServer:
replicaCount: 3
gatewayMode: standalone
javaOpts: "-Xms512m -Xmx768m"
terminationGracePeriodSeconds: 45
preStopSleepSeconds: 5
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
pdb:
enabled: true
minAvailable: 2
secrets:
providerKeys:
openai: sk-...
ingress:
enabled: true
className: nginx
gatewayServer:
hosts:
- host: gateway.mycompany.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: gateway-tls
hosts:
- gateway.mycompany.com
helm install dvara charts/dvara/ -f production-values.yaml
Enterprise with MCP Proxy
Deploy the gateway with the MCP proxy for agent tool governance:
# enterprise-mcp-values.yaml
gatewayServer:
replicaCount: 3
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
mcpProxyServer:
enabled: true
replicaCount: 2
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 20
networkPolicy:
enabled: true
pdb:
enabled: true
secrets:
providerKeys:
openai: sk-...
enterpriseLicenseKey: eyJhbGci... # signed JWT from license-generator
helm install dvara charts/dvara/ -f enterprise-mcp-values.yaml
Access the MCP proxy via port-forward (internal-only, not exposed via Ingress):
kubectl port-forward svc/dvara-mcp-proxy 8070:8070
curl http://localhost:8070/actuator/health
Inline Gateway Configuration
Pass a gateway.yaml configuration directly via Helm values:
gatewayServer:
gatewayConfig:
routing:
default-strategy: round-robin
rate-limiting:
global:
requests-per-second: 100
This creates a ConfigMap mounted at /etc/dvara/gateway.yaml and sets GATEWAY_BOOTSTRAP_FILE automatically.
Using External Secrets
If you manage secrets with External Secrets Operator, Sealed Secrets, or a vault:
secrets:
create: false
existingSecret: my-external-secret
The existing Secret must contain the same keys: openai-api-key, anthropic-api-key, gemini-api-key, aws-access-key-id, aws-secret-access-key, ollama-enabled, ollama-base-url, bedrock-enabled, mock-provider-enabled, gateway-internal-secret, gateway-encryption-master-password, enterprise-license-key (signed JWT).
AWS Bedrock with IRSA
gatewayServer:
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/dvara-bedrock
secrets:
bedrockEnabled: "true"
Multi-Region Deployment
Deploy an instance in a specific region:
# us-east-values.yaml
gatewayServer:
gatewayMode: full
region:
id: us-east-1
name: US East
secrets:
providerKeys:
openai: sk-...
helm install dvara-us-east charts/dvara/ -f us-east-values.yaml
When region.id is set, the chart adds GATEWAY_REGION_ID and GATEWAY_REGION_NAME environment variables and a dvara.ai/region pod label for topology-aware scheduling.
Prometheus Monitoring
gatewayServer:
serviceMonitor:
enabled: true
interval: 15s
additionalLabels:
release: prometheus-stack
The ServiceMonitor scrapes /actuator/prometheus. Requires the Prometheus Operator CRDs to be installed.
Security
The chart applies security hardening by default:
- Non-root execution — Pods run as UID 1001 (
runAsNonRoot: true) - Read-only filesystem —
readOnlyRootFilesystem: truewith a/tmpemptyDir for JVM temp files - No privilege escalation —
allowPrivilegeEscalation: false - Capabilities dropped — All Linux capabilities dropped
- No service account token —
automountServiceAccountToken: false(no K8s API access needed) - Secret key refs optional — Pods start even if only some provider keys are configured
Upgrading
# From OCI registry
helm upgrade dvara oci://ghcr.io/kdhrubo/dvara/charts/dvara -f my-values.yaml
# From local chart
helm upgrade dvara charts/dvara/ -f my-values.yaml
Pods automatically restart when secrets or ConfigMap content changes (via checksum annotations on the pod template).
Running Helm Tests
helm test dvara
This runs test pods that verify gateway-server, gateway-ui, and (if enabled) mcp-proxy-server services are reachable.
Uninstalling
helm uninstall dvara
Troubleshooting
Pods stuck in CrashLoopBackOff
Check logs for JVM startup errors:
kubectl logs deployment/dvara-server
Common causes:
- Insufficient memory — increase
resources.limits.memory - Missing secret keys — verify the Secret exists:
kubectl get secret dvara -o yaml
Startup probe fails
The startup probe allows 60 seconds (5s initial + 12 retries x 5s) for JVM warmup. If your image is large or the node is slow, increase the startup probe:
gatewayServer:
startupProbe:
failureThreshold: 20
Services not reachable
# Check pod status
kubectl get pods -l app.kubernetes.io/component=gateway-server
# Check service endpoints
kubectl get endpoints dvara-server
# Port-forward to test directly
kubectl port-forward svc/dvara-server 8080:8080
curl http://localhost:8080/status
Providers not registering
Provider keys must be non-empty strings. Check the Secret:
kubectl get secret dvara -o jsonpath='{.data.openai-api-key}' | base64 -d
Empty string = provider disabled (this is expected for unused providers).