Skip to main content
Version: 1.8.0

Kubernetes Deployment

Deploy DVARA on Kubernetes with the official Helm chart.

Prerequisites​

  • Kubernetes 1.28+
  • Helm 3.x
  • kubectl configured for your cluster
  • A reachable PostgreSQL 14+ instance for Flightdeck (the chart does NOT bundle PostgreSQL — see Give Flightdeck its database below). From 1.8.2 the gateway has no database: only Flightdeck connects to PostgreSQL and migrates it, and gateways get their configuration from Flightdeck as a signed bundle. See How the gateway runs without a database.
  • A DVARA licence envelope (DVARA-… prefix) only for Enterprise production rights and support. You can add it later on Flightdeck's License page; see Licensing.

Quick Start​

Mint the operational secrets first. Add the licence key when the deployment needs Enterprise production rights, and the PII token password when a workspace uses pii.action=TOKENIZE:

# Operational secrets, plus an optional licence envelope and PII token password
export DVARA_LICENSE_KEY="DVARA-…" # optional: copied into Flightdeck once
export ACTUATOR_API_KEY=$(openssl rand -base64 32) # operator Bearer for gateway status and Enterprise licence health
export METRICS_API_KEY=$(openssl rand -base64 32) # DISTINCT Bearer for /actuator/prometheus
export ENCRYPTION_PASSWORD=$(openssl rand -base64 32) # AES-256-GCM key for ENCRYPTED-mode credentials
export AUDIT_HMAC=$(openssl rand -base64 32) # signs audit-chain envelopes
export ENROLMENT_SECRET=$(openssl rand -base64 32) # gateways enrol with Flightdeck by it
export PII_TOKEN_PASSWORD=$(openssl rand -base64 32) # optional: Flightdeck seals PII token keys with it (TOKENIZE)

The chart generates the enrolment secret on install if you leave it empty, and keeps it across upgrades. Set it explicitly when you render with helm template or Argo CD, which cannot read the existing value back.

Give Flightdeck its database​

Put the PostgreSQL connection in a values file. It goes on Flightdeck only: from 1.8.2 a gateway refuses to start with a database configured, and helm upgrade refuses a values file that gives the gateway SPRING_DATASOURCE_* through llmGatewayServer.extraEnv.

# db-values.yaml
flightdeck:
extraEnv:
- name: SPRING_DATASOURCE_URL
value: jdbc:postgresql://<postgres-host>:5432/dvara
- name: SPRING_DATASOURCE_USERNAME
value: dvara
- name: SPRING_DATASOURCE_PASSWORD
valueFrom:
secretKeyRef:
name: dvara-db
key: password

Pre-built images and the Helm chart are published to GitHub Container Registry:

helm install dvara oci://ghcr.io/dvarahq/charts/dvara \
--version 1.8.5 \
--set secrets.enterpriseLicenseKey="$DVARA_LICENSE_KEY" \
--set secrets.llmGatewayServerApiKey="$ACTUATOR_API_KEY" \
--set secrets.gatewayMetricsApiKey="$METRICS_API_KEY" \
--set secrets.gatewayEncryptionMasterPassword="$ENCRYPTION_PASSWORD" \
--set secrets.auditHmacSecret="$AUDIT_HMAC" \
--set secrets.enrolmentSharedSecret="$ENROLMENT_SECRET" \
--set secrets.piiTokenEncryptionMasterPassword="$PII_TOKEN_PASSWORD" \
--set secrets.mockProviderEnabled=true \
-f db-values.yaml

# Wait for pods to be ready
kubectl rollout status deployment/dvara-server
kubectl rollout status deployment/dvara-flightdeck

# Verify
kubectl port-forward svc/dvara-server 8080:8080 &
curl http://localhost:8080/actuator/health
{"status": "UP", "groups": ["liveness", "readiness"]}

That is the whole body, and it is deliberate: management.endpoint.health.show-details is when-authorized, so an anonymous caller — which is what a probe and a curl both are — gets the verdict and nothing else. Per-indicator detail would expose cluster and pool internals. If you need it, authenticate; if you are debugging a pod that will not become ready, use /actuator/health/readiness and the pod's logs rather than expecting more here.

From Local Chart​

Same secret arguments and database values, pointing at a local chart directory:

helm install dvara charts/dvara/ \
--version 1.8.5 \
--set secrets.enterpriseLicenseKey="$DVARA_LICENSE_KEY" \
--set secrets.llmGatewayServerApiKey="$ACTUATOR_API_KEY" \
--set secrets.gatewayMetricsApiKey="$METRICS_API_KEY" \
--set secrets.gatewayEncryptionMasterPassword="$ENCRYPTION_PASSWORD" \
--set secrets.auditHmacSecret="$AUDIT_HMAC" \
--set secrets.enrolmentSharedSecret="$ENROLMENT_SECRET" \
--set secrets.piiTokenEncryptionMasterPassword="$PII_TOKEN_PASSWORD" \
--set secrets.mockProviderEnabled=true \
-f db-values.yaml

For any deployment past initial smoke-testing, move these into a values file (or an externally-managed Secret via secrets.existingSecret) rather than passing on the command line.

Protect the four operational secrets

The Enterprise applications boot without enterpriseLicenseKey in the Development posture, with every plane serving. The actuator chain returns 401 on authenticated endpoints if its API keys are unset. ENCRYPTED credential storage fails without the encryption master password. The audit chain refuses to write on a production-class profile if its HMAC secret is unset or still uses the public development placeholder.

Installing with Provider Keys​

helm install dvara charts/dvara/ \
--version 1.8.5 \
--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/ --version 1.8.5 -f my-values.yaml

Configuration Reference​

Gateway Server​

ParameterDescriptionDefault
llmGatewayServer.enabledEnable gateway-servertrue
llmGatewayServer.replicaCountReplicas (ignored when HPA enabled)1
llmGatewayServer.image.repositoryImage repositoryghcr.io/dvarahq/dvara-gateway
llmGatewayServer.image.tagImage tag (defaults to chart appVersion)""
llmGatewayServer.region.idRegion identity for multi-region deployments""
llmGatewayServer.region.nameHuman-readable region name""
llmGatewayServer.javaOptsJVM options""
llmGatewayServer.resources.requests.cpuCPU request250m
llmGatewayServer.resources.requests.memoryMemory request512Mi
llmGatewayServer.resources.limits.cpuCPU limit2
llmGatewayServer.resources.limits.memoryMemory limit1Gi
llmGatewayServer.service.typeService typeClusterIP
llmGatewayServer.service.portService port8080
llmGatewayServer.a2a.pushCallbackBaseUrlFrom 1.8.5. The gateway's public URL as peer agents reach it, such as https://api.acme.example. Set on the gateway as A2A_PUSH_CALLBACK_BASE_URL. Needed for A2A push notifications.""

From 1.8.5 A2A push notifications go through the gateway, which gives each peer a callback under llmGatewayServer.a2a.pushCallbackBaseUrl. The chart does not work it out from the ingress: set the URL peers really use. While it is empty, every A2A push config is refused with 503 A2A_PUSH_NOT_AVAILABLE, except in a workspace that keeps direct delivery (a2a.push.delivery=direct). A value you set for A2A_PUSH_CALLBACK_BASE_URL in llmGatewayServer.extraEnv wins, with no duplicate entry. See Receive push notifications through DVARA.

Flightdeck​

The Helm chart's flightdeck.* parameter family configures the DVARA Flightdeck pod; the deployed image is ghcr.io/dvarahq/dvara-flightdeck.

ParameterDescriptionDefault
flightdeck.enabledEnable flightdecktrue
flightdeck.replicaCountNumber of replicas1
flightdeck.image.repositoryImage repositoryghcr.io/dvarahq/dvara-flightdeck
flightdeck.llmGatewayServerUrlOverride auto-discovered server URL"" (auto)
flightdeck.resources.requests.cpuCPU request100m
flightdeck.resources.requests.memoryMemory request256Mi
flightdeck.service.typeService typeClusterIP
flightdeck.email.transportEmail transport — log, smtp, or resend. Unset renders no email config at all""
flightdeck.email.fromSender address on every outbound""
flightdeck.email.hostSMTP server hostname. Required when transport=smtp — the pod refuses to start without it""
flightdeck.email.portSMTP port""
flightdeck.email.usernameSMTP username, when the server requires auth""
flightdeck.email.existingSecretName of an existing Secret holding the SMTP / Resend credential. Preferred over password""
flightdeck.email.existingSecretKeyKey within existingSecretemail-password
flightdeck.email.passwordCredential inline. Lands in helm get values output — prefer existingSecret""
flightdeck.service.portService port8090

Flightdeck Health Probes:

ProbePathPurpose
Liveness/actuator/health/livenessBasic liveness check
Readiness/actuator/health/readinessChecks Flightdeck application readiness and PostgreSQL; Gateway connectivity is excluded
Startup/actuator/health/livenessAllows JVM warmup before liveness kicks in

All four probe paths (/actuator/health, /actuator/health/liveness, /actuator/health/readiness, /actuator/info) are anonymous by design so Kubernetes probes work without secrets. Do not set management.endpoint.health.show-details=always, or set management.endpoint.health.group.<name>.show-details=always for readiness, liveness, or a custom group. A group value overrides the endpoint value. In either case DVARA publishes REFUSING_TRAFFIC and its readiness probe fails so anonymous callers cannot read per-indicator details. Use when-authorized or never.

Use System Status for Gateway and provider health without coupling Flightdeck's Kubernetes readiness to the Gateway.

Secrets​

ParameterDescriptionDefault
secrets.createCreate the Secret resourcetrue
secrets.existingSecretUse an existing Secret instead""
secrets.providerKeys.openaiOpenAI API key""
secrets.providerKeys.anthropicAnthropic API key""
secrets.providerKeys.geminiGemini API key""
secrets.providerKeys.awsAccessKeyIdAWS access key (Bedrock)""
secrets.providerKeys.awsSecretAccessKeyAWS secret key (Bedrock)""
secrets.ollamaEnabledEnable Ollama provider""
secrets.ollamaBaseUrlOllama base URL""
secrets.bedrockEnabledEnable Bedrock provider""
secrets.mockProviderEnabledEnable mock provider""
secrets.auditHmacSecretHMAC-SHA256 key signing the tamper-evident audit chain. Required on production-class profiles — boot refuses the default-dev-secret-change-in-production placeholder. Generate with openssl rand -base64 32.""
secrets.gatewayEncryptionMasterPasswordAES-256-GCM master password for ENC: values + ENCRYPTED-mode provider credentials. Passed to the gateway and, from 1.8.4, to Flightdeck as DVARA_ENCRYPTION_MASTER_PASSWORD, from the same Secret key (gateway-encryption-master-password). Before 1.8.4 Flightdeck never got it, so it refused to store a BYOK provider credential on a standard chart install. A value you set in flightdeck.extraEnv wins.""
secrets.piiTokenEncryptionMasterPasswordFrom 1.8.4. Master password Flightdeck seals each workspace's PII token key with, for pii.action=TOKENIZE. Passed only to Flightdeck, as DVARA_PII_TOKEN_ENCRYPTION_MASTER_PASSWORD; gateways fetch the keys from Flightdeck and need no copy. Keep it: a lost or changed value makes every PII token unreadable. Empty: TOKENIZE is unavailable and each workspace's pii.degraded-action applies (BLOCK by default); detection, LOG and REDACT need no key. A value you set in flightdeck.extraEnv wins. With secrets.existingSecret, the key is pii-token-encryption-master-password.""
secrets.llmGatewayServerApiKeyOperator Bearer for /actuator/gateway-status, Enterprise /actuator/license, and every other authenticated actuator path except prometheus. Generate with openssl rand -base64 32. Required for those operator surfaces.""
secrets.gatewayMetricsApiKeyDistinct Bearer for /actuator/prometheus only. It must differ from llmGatewayServerApiKey so a leaked scrape token cannot unlock gateway or Enterprise licence metadata.""
secrets.enterpriseLicenseKeyDVARA license envelope (DVARA-… prefix, Ed25519-signed). Passed only to Flightdeck, which copies it into its licence store once, when none is stored; Gateway pods do not read it. A licence confers production rights and removes the 3-workspace and 100,000-call limits. Every pod boots without it, in the Development posture, serving every plane. You can paste the licence on Flightdeck's License page instead. No -ee images are needed — there is one artifact per app.""
Audit HMAC secret

secrets.auditHmacSecret → DVARA_AUDIT_HMAC_SECRET signs every row in the audit chain. Set it. From 1.8.0 DVARA no longer generates or stores this secret. Give every gateway pod and Flightdeck the same value, because pods sign the chain and Flightdeck verifies it without re-signing. On a production Spring profile a pod refuses to start without it. The chart sets no profile, so on the default profile a pod starts with only a warning, and the chain has no key you control. An install upgrading from 1.7 that relied on a generated secret can recover it first; see Upgrade DVARA to 1.8. Set it via --set secrets.auditHmacSecret=… or through secrets.existingSecret (key audit-hmac-secret).

From 1.8.4 the gateway also derives the A2A delegation guard's signing key from this secret when dvara.a2a.delegation.signing-secret is unset. A secret shorter than 32 bytes, or a placeholder DVARA publishes, is then refused: on a production profile the gateway does not start, and on a development profile, including the chart's default, it logs a warning and runs the guard as off. openssl rand -base64 32 gives 44 characters, which is enough.

The dataPlane block and the gateway's volume​

From 1.8.2 the gateway has no database. It enrols with Flightdeck, reads its configuration from the signed bundle Flightdeck publishes, and spools audit, usage and cost to its data volume, which it ships to Flightdeck. The chart runs all of this by default; none of the values below needs to be set. See How the gateway runs without a database.

valuewhat it doesdefault
dataPlane.controlPlane.baseUrlFlightdeck's /internal address. Empty means the <release>-flightdeck-internal Service on port 8443 in this release. Set it when Flightdeck runs elsewhere.""
dataPlane.controlPlane.publicKeyThe Ed25519 key that verifies a bundle. Optional: an enrolled gateway learns it from Flightdeck. A key you set always wins.""
dataPlane.enrolment.enabledGateways enrol with Flightdeck using secrets.enrolmentSharedSecret and get their /internal certificate from it, renewed automatically. Turn it off only to supply certificates yourself through dataPlane.tls.true
dataPlane.tls.secretNameAn operator-made /internal client certificate, used with dataPlane.enrolment.enabled: false.""
flightdeck.enrolment.portThe port Flightdeck serves enrolment and /internal on, through its -internal Service.8443
secrets.enrolmentSharedSecretThe secret gateways enrol by, at least 32 characters. Empty: generated on install and kept across upgrades. With secrets.existingSecret, that Secret must carry the key enrolment-shared-secret.""
llmGatewayServer.persistence.enabledGive each gateway pod its own volume claim. Turns the gateway from a Deployment into a StatefulSet. Recommended for production.false
llmGatewayServer.persistence.mountPathWhere the data volume is mounted. The spool, the bundle snapshot and the enrolled certificate live under it. With persistence off it is an emptyDir./var/lib/dvara
llmGatewayServer.persistence.sizeVolume size per replica10Gi
llmGatewayServer.persistence.shutdownDrainSecondsSeconds a pod spends flushing its spool on SIGTERM20

dataPlane.tls.secretName sets both SSL properties, which is the part that is easy to get wrong by hand: one carries the client certificate and the other only checks the CN. Setting the second alone gives a pod that starts and then fails every fetch. The chart removes that choice.

dataPlane.serveFromBundle and dataPlane.dbMigrate were removed in 1.8.2. A values file that still sets either one fails helm upgrade with the setting named. Database credentials go on Flightdeck only, through flightdeck.extraEnv.

The bundled cache carries a licensing choice​

The chart can deploy a single-node cache for evaluation. Both are disabled by default, and which one you enable is a licensing decision, not a preference:

valueimagelicence
valkey.enabledvalkey/valkey-bundleBSD-3
redis.enabledredis:8tri-licensed RSALv2 / SSPLv1 / AGPLv3

Valkey is the recommended choice when you select an external cache. Neither cache is enabled by default. BSD-3 raises no distribution question. Redis Open Source 8+ is AGPLv3 among its options — OSI-approved open source, and copyleft with a network clause, which is a materially different position from the BSD licence Redis carried before March 2024. Enabling redis pulls an AGPL program from its publisher's registry into your cluster; DVARA ships a template naming an image, not the image itself.

Enabling valkey.enabled or redis.enabled only deploys the evaluation cache. It does not point the Gateway at it. Set DVARA_LLM_GATEWAY_RATE_LIMIT_SHARED_CACHE_URL separately through llmGatewayServer.extraEnv; without that URL, the cache pod is idle and the normal limiter remains active. See Control request and token throughput.

If your organisation restricts AGPL software, use Valkey or an external managed instance. This is not legal advice — consult your own counsel.

Single-node, for evaluation

Neither profile is a production topology. Point production at a managed offering — ElastiCache for Valkey, Memorystore, Azure Cache.

MCP and A2A planes​

There are no mcpGatewayServer or a2aGatewayServer values to set. Both planes run inside the LLM gateway and are served by it, with or without a license, on the same port as /v1/*. The separate dvara-mcp-gateway and dvara-a2a-gateway images are no longer published and their workload templates are gone.

The chart refuses to render if you enable either — a hard failure naming the replacement, rather than silently giving you a smaller cluster than you asked for with the MCP plane quietly not served.

To use them: address /mcp/* and /a2a/* on the gateway Service.

Ingress​

ParameterDescriptionDefault
ingress.enabledEnable Ingressfalse
ingress.classNameIngress class (nginx, traefik, alb)""
ingress.annotationsIngress annotations{}
ingress.llmGatewayServer.hostsServer host/path rules[{host: gateway.example.com}]
ingress.llmGatewayServer.tlsServer TLS config[]
ingress.flightdeck.hostsUI host/path rules[{host: admin.example.com}]
ingress.flightdeck.tlsUI TLS config[]

Graceful Shutdown & Rolling Updates​

ParameterDescriptionDefault
llmGatewayServer.terminationGracePeriodSecondsPod termination grace period (must exceed preStop + drain)45
llmGatewayServer.preStopSleepSecondsSleep before SIGTERM (endpoint de-registration propagation)5
llmGatewayServer.rollingUpdate.maxSurgeMax extra pods during rolling update1
llmGatewayServer.rollingUpdate.maxUnavailableMax unavailable pods during rolling update (0 = zero-downtime)0
llmGatewayServer.topologySpreadConstraintsTopology 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)​

ParameterDescriptionDefault
llmGatewayServer.autoscaling.enabledEnable HPAfalse
llmGatewayServer.autoscaling.minReplicasMinimum replicas2
llmGatewayServer.autoscaling.maxReplicasMaximum replicas10
llmGatewayServer.autoscaling.targetCPUUtilizationPercentageCPU target70
llmGatewayServer.autoscaling.targetMemoryUtilizationPercentageMemory target80
llmGatewayServer.autoscaling.behavior.scaleUp.stabilizationWindowSecondsWait before scaling up30
llmGatewayServer.autoscaling.behavior.scaleDown.stabilizationWindowSecondsWait before scaling down300

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​

ParameterDescriptionDefault
llmGatewayServer.pdb.enabledEnable PDBfalse
llmGatewayServer.pdb.minAvailableMin available pods1
llmGatewayServer.pdb.maxUnavailableMax unavailable pods""

Prometheus ServiceMonitor​

ParameterDescriptionDefault
llmGatewayServer.serviceMonitor.enabledEnable (requires Prometheus Operator)false
llmGatewayServer.serviceMonitor.intervalScrape interval30s
llmGatewayServer.serviceMonitor.pathMetrics path/actuator/prometheus
llmGatewayServer.serviceMonitor.additionalLabelsLabels for monitor selection{}

When llmGatewayServer.serviceMonitor.enabled=true, configure the ServiceMonitor's bearerTokenFile (or the Helm equivalent) to point at a file containing the DVARA_ACTUATOR_METRICS_API_KEY value. /actuator/prometheus is authenticated — without the token, every scrape returns 401 and the time series goes dark. The metrics secret is intentionally distinct from DVARA_ACTUATOR_API_KEY so a leaked scrape token can't unlock the rich gateway status surface.

Clustering on Kubernetes​

The Enterprise embedded cluster shares rate-limit counters, response-cache entries, and API-key lookups across Gateway pods. On Kubernetes, those pods must discover each other through a headless Service because multicast is unavailable in most clusters. The Open Source distribution does not gain shared rate limits from this Service; each Open Source Gateway process keeps its own token bucket.

When KUBERNETES_NAMESPACE is set (the downward API auto-injects this), set CACHE_SERVICE_DNS to the DNS name of the headless Service fronting the gateway pods. The official chart sets it automatically. CACHE_SERVICE_NAME remains the lower-level service-name input for deployments that construct the DNS name themselves.

This DNS discovery mode does not query the Kubernetes API, so it needs no Role or RoleBinding. The headless Service must expose the cluster port 5701. After rollout, confirm gateway_hazelcast_cluster_members equals the Gateway replica count on every pod.

Embedded-cluster discovery is not configured; this pod will use its local member only.
Rate-limit and cache state will not be shared across replicas.

Missing discovery configuration produces a warning rather than stopping the process. Treat that warning as a deployment failure when you run more than one replica: each pod otherwise admits its own rate limit. You do not need the manual YAML below unless you bypass the chart. Outside Kubernetes, the Enterprise embedded cluster uses multicast discovery.

Headless Service pattern (reference)​

apiVersion: v1
kind: Service
metadata:
name: dvara-server-cluster
labels:
app.kubernetes.io/name: dvara-server
spec:
clusterIP: None # headless — each pod gets a DNS A record
publishNotReadyAddresses: true
selector:
app.kubernetes.io/name: dvara-server
ports:
- name: cluster
port: 5701
targetPort: 5701

Deployment environment variables (reference)​

apiVersion: apps/v1
kind: Deployment
metadata:
name: dvara-server
spec:
template:
spec:
containers:
- name: gateway-server
env:
- name: KUBERNETES_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: CACHE_SERVICE_NAME
value: dvara-server-cluster

Common Deployment Patterns​

Minimal Mode (Testing)​

Flightdeck needs PostgreSQL even for minimal deployments — there is no in-memory fallback. The gateway needs none. For quick testing, point Flightdeck at an external Postgres or deploy a small Postgres StatefulSet alongside it, and pass the connection in db-values.yaml:

helm install dvara charts/dvara/ \
--version 1.8.5 \
--set secrets.auditHmacSecret="$AUDIT_HMAC" \
--set secrets.mockProviderEnabled=true \
-f db-values.yaml

Production with OpenAI​

# production-values.yaml
llmGatewayServer:
replicaCount: 3
# Match heap to container limits below — leave ~25% of the limit for
# off-heap (Metaspace, native code, JIT, kernel buffers). With memory
# limit = 2Gi, -Xmx1500m is a safe upper bound; reserve more if you
# see Metaspace pressure in JFR.
javaOpts: "-Xms1g -Xmx1500m"
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: "2"
memory: 2Gi
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
llmGatewayServer:
hosts:
- host: gateway.mycompany.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: gateway-tls
hosts:
- gateway.mycompany.com
helm install dvara charts/dvara/ --version 1.8.5 -f production-values.yaml

MCP and A2A governance​

No extra values are needed. The planes are served by the gateway with or without a license. For production, supply a license:

# licensed-values.yaml
secrets:
enterpriseLicenseKey: "DVARA-..." # production rights; copied into Flightdeck once

llmGatewayServer:
replicaCount: 3
helm install dvara oci://ghcr.io/dvarahq/charts/dvara --version 1.8.5 \
-n dvara --create-namespace -f licensed-values.yaml

/mcp/* and /a2a/* are then served on the gateway Service alongside /v1/*.

Inline Gateway Configuration​

Pass a gateway.yaml configuration directly via Helm values:

llmGatewayServer:
gatewayConfig:
routing:
default-strategy: round-robin
rate-limit:
enabled: true
per-key:
requests-per-minute: 100

This creates a ConfigMap mounted into the gateway pod and applied as Spring Boot externalized configuration.

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-encryption-master-password, pii-token-encryption-master-password (optional, from 1.8.4), gateway-server-api-key, gateway-metrics-api-key, audit-hmac-secret, enterprise-license-key (signed).

AWS Bedrock with explicit credentials​

secrets:
bedrockEnabled: "true"
providerKeys:
awsAccessKeyId: <aws-access-key-id>
awsSecretAccessKey: <aws-secret-access-key>

DVARA 1.8 signs Bedrock requests with the explicit access-key and secret-key pair. It does not use IRSA, an instance profile, container credentials, or the AWS SDK default credential chain. Use secrets.existingSecret to keep the pair in an externally managed Kubernetes Secret instead of Helm values.

Multi-Region Deployment​

Deploy an instance in a specific region:

# us-east-values.yaml
llmGatewayServer:
region:
id: us-east-1
name: US East

secrets:
providerKeys:
openai: sk-...
helm install dvara-us-east charts/dvara/ --version 1.8.5 -f us-east-values.yaml

When region.id is set, the chart adds DVARA_REGION_ID and DVARA_REGION_NAME environment variables (which bind to the region identity configuration) and a dvara.ai/region pod label for topology-aware scheduling.

Prometheus Monitoring​

llmGatewayServer:
serviceMonitor:
enabled: true
interval: 15s
additionalLabels:
release: prometheus-stack

The ServiceMonitor scrapes /actuator/prometheus. Requires the Prometheus Operator CRDs to be installed and a bearerTokenFile (or Helm-managed equivalent) pointing at the DVARA_ACTUATOR_METRICS_API_KEY value — see the ServiceMonitor parameter table above.

Security​

The chart applies security hardening by default:

  • Non-root execution — Pods run as UID 1001 (runAsNonRoot: true)
  • Read-only filesystem — readOnlyRootFilesystem: true with a /tmp emptyDir 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/dvarahq/charts/dvara --version 1.8.5 -f my-values.yaml

# From local chart
helm upgrade dvara charts/dvara/ --version 1.8.5 -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 the gateway-server and flightdeck services are reachable. The LLM, MCP, and A2A planes share the gateway service.

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:

llmGatewayServer:
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/actuator/health

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).