Eloquent

Documentation

Deployment, Helm & Secrets

Eloquent is distributed as a Helm chart and can be deployed to any Kubernetes cluster with a single command.

Prerequisites

Before deploying, ensure your cluster has:

RequirementVersionNotes
Kubernetes1.20+AKS, GKE, OKE, or any conformant cluster
Helm3.xFor chart installation
ingress-nginxLatestRequired for HTTP routing
cert-managerLatestRequired only if using platform-managed TLS
GHCR credentialsGitHub Container Registry access for pulling images

Install ingress-nginx

helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx \
  --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz

Install cert-manager (optional — for automatic TLS)

helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager --set crds.enabled=true

Create namespace and registry secret

kubectl create namespace eloquent

kubectl create secret docker-registry ghcr-credentials \
  -n eloquent \
  --docker-server=ghcr.io \
  --docker-username=<github-username> \
  --docker-password=<github-pat-with-read-packages>

One-Command Deploy

helm upgrade --install eloquent \
  oci://ghcr.io/blazi-commerce/eloquent/charts/eloquent \
  -n eloquent \
  -f eloquent-values.yaml \
  --set image.tag=prod

This deploys all frontend apps, backend services, and infrastructure components as configured in your values file.

Values File Structure

Configuration is layered — each level overrides the previous:

values.yaml                    # Chart defaults (all services, dev resources)
└── eloquent-values.yaml       # Customer-specific overrides
    └── eloquent-secrets.yaml  # Secrets (gitignored, never committed)

You only need to override what differs from the defaults.

Core Configuration

Customer Deployment

customerDeployment:
  enabled: true
  domain: "customer.example.com"
  apiGatewayPublicUrl: "https://api.customer.example.com"
  chatPublicUrl: "https://chat.customer.example.com/v1/chat"
  sandboxSubdomain: "sandbox"

appBaseUrl: "https://app.customer.example.com"

Frontend Applications

Enable or disable each frontend app and set its hostname:

frontend:
  eloquentApp:
    enabled: true
    host: app.customer.example.com
  adminApp:
    enabled: true
    host: admin.customer.example.com

Ingress

Two modes are supported:

Platform-managed (cert-manager handles TLS):

ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/proxy-body-size: "100m"

Customer-managed (your own nginx/reverse proxy handles TLS):

ingress:
  enabled: false

When ingress is disabled, your own reverse proxy routes traffic to Kubernetes services via internal DNS.

Secrets

All secrets are configured under the secrets key. Keep these in a separate file that is never committed to version control.

Required Secrets

secrets:
  # JWT — must be identical across all services
  jwtSecret: "<random-256-bit-secret>"

  # Database credentials
  postgresPassword: "<strong-password>"
  redisPassword: "<strong-password>"
  clickhousePassword: "<strong-password>"

  # NextAuth session encryption
  authSecret: "<random-secret>"

  # OAuth (Google SSO)
  authGoogleId: "<google-oauth-client-id>"
  authGoogleSecret: "<google-oauth-client-secret>"

LLM Providers

At least one LLM provider is required for AI agents:

secrets:
  # Azure OpenAI (primary)
  azureOpenaiApiKey: "<key>"
  azureOpenaiEndpoint: "https://<resource>.openai.azure.com"
  azureOpenaiResourceName: "<resource-name>"
  azureOpenaiApiVersion: "2024-12-01-preview"
  azureOpenaiMiniModel: "gpt-4o-mini"
  azureOpenaiNanoModel: "gpt-4o-mini"

  # Anthropic (optional)
  anthropicApiKey: "<key>"
  anthropicApiUrl: "https://<endpoint>"
  anthropicModelMap: "<model-mapping>"

Cloud Storage

File uploads require a storage backend:

secrets:
  # Azure Blob Storage
  azureStorageAccountName: "<account>"
  azureStorageAccountKey: "<key>"
  azureStorageConnectionString: "<connection-string>"
  azureStorageContainerName: "files"

  # OR S3-compatible storage
  s3AccessKeyId: "<key>"
  s3SecretAccessKey: "<secret>"
  s3Bucket: "<bucket>"
  s3Region: "<region>"
  s3Endpoint: "<endpoint-url>"

Document Intelligence

For PDF processing and document analysis:

secrets:
  azureDocIntelligenceKey: "<key>"
  azureDocIntelligenceEndpoint: "https://<resource>.cognitiveservices.azure.com"

Email

For sending invite emails and notifications:

secrets:
  # Azure Communication Services (primary)
  AZURE_EMAIL_ENDPOINT: "https://<resource>.communication.azure.com"
  AZURE_EMAIL_ACCESS_KEY: "<key>"
  AZURE_EMAIL_SENDER: "DoNotReply@yourdomain.com"

  # OR SMTP fallback
  smtp:
    host: "smtp.example.com"
    port: "587"
    user: "<username>"
    pass: "<password>"
    fromEmail: "noreply@example.com"

Integrations (Optional)

secrets:
  # Google Calendar (booking system)
  googleCalendar:
    serviceAccountEmail: "<email>"
    serviceAccountPrivateKey: "<key>"
    calendarId: "<calendar-id>"
    timezone: "Asia/Dubai"

  # WhatsApp Business API
  whatsapp:
    products: "eloquent"
    eloquent:
      enabled: true
      phoneNumberId: "<id>"
      accessToken: "<token>"
      verifyToken: "<token>"
      appSecret: "<secret>"

  # Google AI (image generation)
  googleApiKey: "<key>"

Infrastructure Components

Each infrastructure component can be Helm-managed or external:

# Helm-managed (default)
postgresql:
  enabled: true
  persistence:
    size: 10Gi

clickhouse:
  enabled: true
  persistence:
    size: 20Gi

redis:
  enabled: true
  persistence:
    size: 5Gi

nats:
  enabled: true    # Always internal, cannot use external

To use external databases, disable the component and provide connection strings in the service environment variables.

External Database Access

For backup tools or analytics access, expose databases via internal LoadBalancer:

postgresql:
  externalAccess:
    enabled: true
    type: LoadBalancer
    annotations:
      service.beta.kubernetes.io/azure-load-balancer-internal: "true"

clickhouse:
  externalAccess:
    enabled: true
    annotations:
      service.beta.kubernetes.io/azure-load-balancer-internal: "true"

Per-Service Configuration

Each service can be individually configured:

backend:
  agentsService:
    enabled: true
    replicas: 2
    resources:
      requests:
        cpu: 100m
        memory: 256Mi
      limits:
        cpu: 500m
        memory: 512Mi

Initial Organization Seeding

After deployment, seed the first organization and admin user:

kubectl exec -n eloquent deployment/admin-service -- \
  curl -X POST http://localhost/internal/seed \
  -H "Content-Type: application/json" \
  -d '{
    "org": {
      "title": "My Organization",
      "mainDomain": "example.com",
      "product": "eloquent"
    },
    "admin": {
      "email": "admin@example.com",
      "firstName": "Admin",
      "lastName": "User"
    }
  }'

The seed endpoint creates the organization, admin user, and provisions all artifacts (databases, entities, agents, skills, storage) — 15 artifacts total.

Upgrading

To upgrade to a new version:

helm upgrade eloquent \
  oci://ghcr.io/blazi-commerce/eloquent/charts/eloquent \
  -n eloquent \
  -f eloquent-values.yaml \
  --set image.tag=<new-version>

To roll back a deployment:

kubectl rollout undo deployment/<service-name> -n eloquent

Real-World Example

A production deployment on Azure AKS with customer-managed nginx:

customerDeployment:
  enabled: true
  domain: "customer.gov.qa"
  apiGatewayPublicUrl: "https://ai-api.customer.gov.qa"
  chatPublicUrl: "https://ai-chat.customer.gov.qa/v1/chat"
  sandboxSubdomain: "ai-sandbox"

appBaseUrl: "https://ai-hub.customer.gov.qa"

ingress:
  enabled: false    # Customer nginx handles routing

frontend:
  eloquentApp:
    enabled: true
    host: ai-hub.customer.gov.qa
  adminApp:
    enabled: true
    host: ai-admin.customer.gov.qa

postgresql:
  externalAccess:
    enabled: true
    annotations:
      service.beta.kubernetes.io/azure-load-balancer-internal: "true"

Services are accessed from the customer namespace via Kubernetes internal DNS (e.g., api-gateway-service.eloquent.svc.cluster.local).