Skip to content

Kubernetes & OpenShift Installation

This guide covers deploying the Virtual Data Assistant (VDA) in highly available, production-grade Kubernetes or Red Hat OpenShift clusters.


Installation roadmap

Follow these pages in order:

  1. Install & Launch (this page): prerequisites, configuration, and starting the platform.
  2. Verify the Installation: pod and endpoint health checks with expected output.
  3. First-Time Setup: configure Keycloak SSO and the first administrator.
  4. Connect a Datasource: add your first source in Kubernetes.
  5. LLM & Model Setup: deploy a local engine or use the cloud tier.
  6. Air-Gapped Installation: install on a disconnected cluster.
  7. Backup, Restore & Upgrades: cluster backup, restore, and rolling upgrades.

Production Architecture & Namespaces

VDA services are grouped under a dedicated namespace (vda), isolated with strict Kubernetes NetworkPolicies and resource quotas:

flowchart TD
    subgraph IngressLayer["Ingress & Edge Routing"]
        Ing["TLS Ingress / OpenShift Route<br/>(vda.company.internal)"]
    end

    subgraph StatelessApps["Core Application Deployments"]
        UI["vda-ui (2 Replicas)"]
        Server["vda-server (2 Replicas)"]
        MetaAPI["metadata-api (2 Replicas)"]
        Worker["metadata-worker (2+ Replicas)"]
    end

    subgraph Identity["Enterprise Identity"]
        KC["Keycloak SSO (ClusterIP)"]
    end

    subgraph StatefulEngines["Stateful Data Engines (StatefulSets)"]
        PG[("PostgreSQL (PVC 50Gi)")]
        Surreal[("SurrealDB (PVC 50Gi)")]
        Qdrant[("Qdrant Vector DB (PVC 50Gi)")]
        Temp["Temporal Server (ClusterIP)"]
    end

    Ing -->|/| UI
    Ing -->|/api| Server
    Ing -->|/api/v2| MetaAPI
    Ing -->|/auth| KC

    UI --> Server & MetaAPI
    Server --> PG & KC & Temp
    MetaAPI --> Surreal & Qdrant & PG & Temp
    Worker --> Surreal & Qdrant & PG & Temp

Pod Sizing, Resource Limits & Concurrency Matrix

Configure pod resource requests and limits to guarantee predictable performance and prevent Linux Completely Fair Scheduler (CFS) throttling during peak profiling or vector search jobs:

Deployment / Pod Service Type Port Protocol Network Exposure CPU (Req / Limit) Memory (Req / Limit) Concurrency & Thread Variables
vda-ui ClusterIP 80 HTTP Exposed via Ingress 250m / 500m 256Mi / 512Mi Nginx reverse proxy
vda-server ClusterIP 4000 HTTP Exposed via Ingress (/api) 1000m / 2000m 2Gi / 4Gi NODE_OPTIONS="--max-old-space-size=4096"
metadata-api ClusterIP 8200 HTTP/SSE Exposed via Ingress (/api/v2) 2000m / 4000m 4Gi / 8Gi OMP_NUM_THREADS=4, TORCH_NUM_THREADS=4
metadata-worker None (Worker) None Internal Strictly Private 2000m / 4000m 4Gi / 8Gi GLINER_NUM_THREADS=4, TEMPORAL_QUEUE=all
keycloak ClusterIP 8080 HTTP Exposed via Ingress (/auth) 1000m / 2000m 1Gi / 2Gi Quarkus runtime
postgresql ClusterIP 5432 TCP (PG) Strictly Private (ClusterIP) 1000m / 2000m 2Gi / 4Gi Relational storage
surrealdb ClusterIP 8000 HTTP/WS Strictly Private (ClusterIP) 1000m / 2000m 2Gi / 4Gi RocksDB storage engine
qdrant ClusterIP 6333 HTTP/gRPC Strictly Private (ClusterIP) 1000m / 2000m 2Gi / 4Gi In-memory HNSW index
temporal ClusterIP 7233 gRPC Strictly Private (ClusterIP) 1000m / 2000m 1Gi / 2Gi Distributed workflow engine
temporal-ui ClusterIP 8080 HTTP Admin Port-Forward / VPN 250m / 500m 256Mi / 512Mi Workflow visualizer

Network Exposure Rules

Databases (postgresql, surrealdb, qdrant), workflow gRPC (temporal), and background workers must never have external Ingress rules or NodePort exposure. Only vda-ui, /api (vda-server), /api/v2 (metadata-api), and the Keycloak authentication endpoint should be routed through the Ingress controller.

Kubernetes Manifest Structure (k8s/)

The repository includes declarative Kubernetes manifests organized into modular layers:

k8s/
├── 00-namespace.yaml          # Creates the 'vda' namespace
├── config/
│   ├── configmap.yaml         # Application topology, URLs, and thread bounds
│   └── secret.example.yaml    # Secret template for passwords and tokens
├── storage/
│   ├── postgresql.yaml        # PostgreSQL StatefulSet, Service & 50Gi PVC
│   ├── surrealdb.yaml         # SurrealDB StatefulSet, Service & 50Gi PVC
│   ├── qdrant.yaml            # Qdrant Vector DB StatefulSet, Service & 50Gi PVC
│   └── temporal.yaml          # Temporal Server & Temporal UI Deployments
├── apps/
│   ├── metadata-api.yaml      # Metadata Core Engine Deployment (2 replicas) & Service
│   ├── metadata-worker.yaml   # Background Temporal Worker (2 replicas)
│   ├── vda-server.yaml        # GraphQL Application Server (2 replicas) & Service
│   └── vda-ui.yaml            # Web UI React SPA & Nginx (2 replicas) & Service
└── ingress.yaml               # TLS Ingress with routing rules for UI and APIs

Step-by-Step Deployment Execution

Follow these steps to deploy VDA into your Kubernetes or OpenShift cluster using kubectl.

Step 1: Create Namespace

kubectl apply -f k8s/00-namespace.yaml

Step 2: Apply Configuration & Secrets

Customize credentials in k8s/config/secret.example.yaml and save as k8s/config/secret.yaml:

# Apply ConfigMap
kubectl apply -f k8s/config/configmap.yaml -n vda

# Apply Secrets (ensure passwords are changed from defaults)
kubectl apply -f k8s/config/secret.yaml -n vda

Step 3: Deploy Storage StatefulSets & Wait for Readiness

Deploy PostgreSQL, SurrealDB, Qdrant, and Temporal:

kubectl apply -f k8s/storage/ -n vda

# Wait for datastore pods to report Ready
kubectl wait --for=condition=ready pod -l app=postgresql -n vda --timeout=120s
kubectl wait --for=condition=ready pod -l app=surrealdb -n vda --timeout=120s
kubectl wait --for=condition=ready pod -l app=qdrant -n vda --timeout=120s
kubectl wait --for=condition=ready pod -l app=temporal -n vda --timeout=120s

Step 4: Deploy Core Applications & Workers

kubectl apply -f k8s/apps/ -n vda

# Monitor rollout status
kubectl rollout status deployment/metadata-api -n vda
kubectl rollout status deployment/metadata-worker -n vda
kubectl rollout status deployment/vda-server -n vda
kubectl rollout status deployment/vda-ui -n vda

Step 5: Apply Ingress & TLS Termination

Update the domain name in k8s/ingress.yaml to match your enterprise DNS:

kubectl apply -f k8s/ingress.yaml -n vda

Verify all pods across the namespace:

kubectl get pods -n vda -o wide