Skip to content

Kubernetes deployment guide

This guide covers deploying Arbitex to Kubernetes using the official Helm charts. It covers the Platform (API + frontend + DLP microservices) and the Outpost (hybrid data plane).


A full Arbitex deployment consists of two independent Helm releases:

┌─────────────────────────────────────────────────────────────┐
│ Kubernetes cluster │
│ │
│ namespace: arbitex │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ api │ │ frontend │ │ ner-gpu │ │ deberta │ │
│ │ :8000 │ │ :8080 │ │ :8200 │ │ :8201 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ GPU pool GPU pool │
│ NGINX Ingress (port 443) │
│ ↓ │
│ api.arbitex.ai │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ namespace: arbitex-outpost (customer cluster) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ arbitex-outpost (replicas: 2) │ │
│ │ proxy :8300 admin :8301 (pod-local only) │ │
│ │ policy cache PVC + audit buffer PVC │ │
│ └──────────────────────────────────────────────────┘ │
│ │ mTLS │
│ ↓ │
│ api.arbitex.ai (management plane sync) │
└─────────────────────────────────────────────────────────────┘

The Platform chart deploys 4 workloads: API, frontend, NER GPU microservice, and DeBERTa validator. The Outpost chart deploys a single proxy workload with HA (2 replicas by default).


  • Kubernetes 1.25+
  • Helm 3.12+
  • kubectl configured for your target cluster
  • NGINX ingress controller installed (helm install ingress-nginx ingress-nginx/ingress-nginx)
  • PostgreSQL 14+ database accessible from the cluster
  • Redis 7+ accessible from the cluster
  • GPU nodes with the NVIDIA device plugin for DLP microservices (see GPU node pools)
  • Container registry access to arbitexacr.azurecr.io (imagePullSecret or registry credential)
  • TLS certificate for api.arbitex.ai stored as a Kubernetes secret
arbitex-platform/deploy/helm/arbitex-platform/
├── Chart.yaml
├── values.yaml # base values — all environments
├── values-dev.yaml # development overrides
├── values-staging.yaml # staging overrides
├── values-prod.yaml # production overrides
└── templates/
├── deployment-api.yaml
├── deployment-frontend.yaml
├── deployment-ner-gpu.yaml
├── deployment-deberta.yaml
├── job-alembic-migrate.yaml
├── ingress.yaml
└── ...

Development disables GPU services and uses relaxed Pod Security Standards:

Terminal window
helm upgrade --install arbitex-dev ./deploy/helm/arbitex-platform \
-f values.yaml \
-f values-dev.yaml \
--set api.env.DATABASE_URL="postgresql+asyncpg://arbitex:arbitex@postgres-dev:5432/arbitex_dev" \
--set api.env.REDIS_URL="redis://redis-dev:6379/0" \
--set api.env.SECRET_KEY="change-me-for-dev-only" \
--namespace arbitex-dev \
--create-namespace

Dev overrides:

  • namespace.podSecurityStandard: baseline (allows debug sidecars)
  • nerGpu.enabled: false — no GPU node required
  • deberta.enabled: false
  • ingress.hosts[0].host: api.arbitex.local
  • ingress.tls: [] (no TLS in dev)
Terminal window
helm upgrade --install arbitex-staging ./deploy/helm/arbitex-platform \
-f values.yaml \
-f values-staging.yaml \
--set api.env.DATABASE_URL="postgresql+asyncpg://..." \
--set api.env.REDIS_URL="redis://..." \
--set api.env.SECRET_KEY="..." \
--namespace arbitex-staging \
--create-namespace

Staging overrides:

  • GPU services enabled (nerGpu.enabled: true, deberta.enabled: true)
  • ingress.hosts[0].host: api-staging.arbitex.ai
  • TLS from secret arbitex-staging-tls

Production uses sealed secrets or External Secrets Operator for credentials — do not pass them via --set in production pipelines:

Terminal window
helm upgrade --install arbitex ./deploy/helm/arbitex-platform \
-f values.yaml \
-f values-prod.yaml \
--namespace arbitex \
--create-namespace

After a CI build, pin the image digests to the promoted artifacts:

Terminal window
helm upgrade arbitex ./deploy/helm/arbitex-platform \
-f values.yaml \
-f values-prod.yaml \
--set api.image.digest="sha256:<api-digest>" \
--set frontend.image.digest="sha256:<frontend-digest>" \
--set nerGpu.image.digest="sha256:<ner-digest>" \
--set deberta.image.digest="sha256:<deberta-digest>" \
--namespace arbitex

The base values.yaml documents all supported keys. Key sections:

KeyDefaultDescription
api.replicas1Pod replica count
api.image.repositoryarbitexacr.azurecr.io/platform-apiContainer image
api.image.taglatestImage tag
api.image.digest""SHA256 digest — overrides tag when set
api.resources.requests.cpu250mCPU request
api.resources.requests.memory512MiMemory request
api.resources.limits.cpu1000mCPU limit
api.resources.limits.memory1GiMemory limit
api.env.DATABASE_URL""Async PostgreSQL connection string (required)
api.env.REDIS_URL""Redis connection string (required)
api.env.SECRET_KEY""JWT signing secret (required)
api.env.ENVIRONMENTproductionEnvironment label
api.env.NER_GPU_URLautoNER GPU service URL (auto-resolved from chart name)
api.env.DEBERTA_URLautoDeBERTa service URL (auto-resolved from chart name)
KeyDefaultDescription
nerGpu.enabledtrueEnable NER GPU DLP microservice
nerGpu.replicas1Replica count
nerGpu.resources.requests.nvidia.com/gpu1GPU request
nerGpu.nodeSelector.acceleratornvidiaNode selector for GPU pool
nerGpu.tolerationsnvidia.com/gpu:NoScheduleToleration for GPU taint

Same structure as nerGpu.*. Runs the DeBERTa v3 Tier 3 DLP model on GPU.

KeyDefaultDescription
ingress.classNamenginxIngress class name
ingress.hostsapi.arbitex.aiHostname and path routing
ingress.tls[0].secretNamearbitex-tlsKubernetes TLS secret name
ingress.annotationsssl-redirect, proxy-body-size 50mNGINX annotations
KeyDefaultDescription
alembic.runAsInitContainertrueRun DB migration as init container before API starts
alembic.ttlSecondsAfterFinished300Job TTL after completion

The NER GPU and DeBERTa services require nodes with NVIDIA GPUs. Configure your cluster:

Terminal window
# Create a GPU node pool
az aks nodepool add \
--resource-group myRG \
--cluster-name myAKS \
--name gpupool \
--vm-size Standard_NC6s_v3 \
--node-count 1 \
--node-taints nvidia.com/gpu=present:NoSchedule \
--labels accelerator=nvidia

The chart’s nodeSelector: { accelerator: nvidia } and tolerations target this pool automatically.

  1. Label GPU nodes:
    Terminal window
    kubectl label node <gpu-node> accelerator=nvidia
  2. Taint GPU nodes (optional but recommended to prevent non-GPU workloads on expensive nodes):
    Terminal window
    kubectl taint node <gpu-node> nvidia.com/gpu=present:NoSchedule
  3. Install the NVIDIA device plugin:
    Terminal window
    kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.5/nvidia-device-plugin.yml

If you don’t have GPU nodes, disable both services:

values-nogpu.yaml
nerGpu:
enabled: false
deberta:
enabled: false

The API falls back to CPU-based DLP processing. DeBERTa Tier 3 is unavailable; DLP runs regex + NER (CPU) only.

Never put credentials directly in values files committed to source control. Use one of:

Kubernetes Secrets (minimum viable):

Terminal window
kubectl create secret generic arbitex-secrets \
--from-literal=DATABASE_URL="postgresql+asyncpg://..." \
--from-literal=REDIS_URL="redis://..." \
--from-literal=SECRET_KEY="$(openssl rand -hex 32)" \
--namespace arbitex

Reference in values:

api:
envFrom:
- secretRef:
name: arbitex-secrets

Azure Key Vault (production — CSI driver):

api:
env:
SECRETS_BACKEND: vault
VAULT_URL: "https://my-keyvault.vault.azure.net/"

See Security hardening guide for the full Key Vault CSI driver setup.

Terminal window
kubectl get pods -n arbitex
# Expected: api, frontend, ner-gpu, deberta all Running
kubectl get ingress -n arbitex
# Expected: ADDRESS shows the external IP
curl https://api.arbitex.ai/healthz
# Expected: 200 OK

Check migration ran:

Terminal window
kubectl logs -n arbitex -l job-name=arbitex-alembic-migrate --tail=20
# Expected: "INFO [alembic.runtime.migration] Running upgrade ..."

  • Kubernetes 1.25+ (separate cluster from Platform, or separate namespace)
  • Helm 3.12+
  • Outpost registered in the Arbitex admin console — you need the outpost UUID
  • mTLS certificates issued by Arbitex (outpost.pem, outpost.key, ca.pem)
  • GPU node (optional — for DeBERTa Tier 3 DLP)
arbitex-outpost/charts/arbitex-outpost/
├── Chart.yaml
├── values.yaml # all configuration keys with comments
├── values.schema.json # JSON schema validation
└── templates/
├── deployment.yaml
├── service.yaml
├── configmap.yaml
├── secret.yaml
├── pvc.yaml
├── hpa.yaml
└── poddisruptionbudget.yaml

Step 1: Create the mTLS certificate secret

Section titled “Step 1: Create the mTLS certificate secret”

The outpost requires three certificate files issued by Arbitex:

Terminal window
kubectl create secret generic outpost-certs \
--from-file=outpost.pem=./certs/outpost.pem \
--from-file=outpost.key=./certs/outpost.key \
--from-file=ca.pem=./certs/ca.pem \
--namespace arbitex-outpost
Terminal window
kubectl create secret generic outpost-secrets \
--from-literal=OUTPOST_AUDIT_HMAC_KEY="$(openssl rand -base64 32)" \
--from-literal=OUTPOST_ADMIN_API_KEY="$(openssl rand -hex 32)" \
--namespace arbitex-outpost

Minimal install (no GPU):

Terminal window
helm upgrade --install arbitex-outpost ./charts/arbitex-outpost \
--set outpost.id="your-outpost-uuid" \
--set outpost.platformManagementUrl="https://api.arbitex.ai" \
--set outpost.orgId="your-org-uuid" \
--set certs.existingSecret="outpost-certs" \
--set outpost.auditHmacKey="$(kubectl get secret outpost-secrets -o jsonpath='{.data.OUTPOST_AUDIT_HMAC_KEY}' | base64 -d)" \
--set outpost.adminApiKey="$(kubectl get secret outpost-secrets -o jsonpath='{.data.OUTPOST_ADMIN_API_KEY}' | base64 -d)" \
--namespace arbitex-outpost \
--create-namespace

GPU-enabled install (DeBERTa Tier 3):

Terminal window
helm upgrade --install arbitex-outpost ./charts/arbitex-outpost \
--set outpost.id="your-outpost-uuid" \
--set outpost.platformManagementUrl="https://api.arbitex.ai" \
--set outpost.orgId="your-org-uuid" \
--set certs.existingSecret="outpost-certs" \
--set outpost.auditHmacKey="..." \
--set outpost.adminApiKey="..." \
--set outpost.gpuEnabled=true \
--set outpost.dlpDebertaEnabled=true \
--set outpost.debertaModelPath="/app/models/deberta/model.onnx" \
--namespace arbitex-outpost \
--create-namespace
KeyRequiredDefaultDescription
outpost.idYes""Outpost UUID from cloud.arbitex.ai
outpost.platformManagementUrlYes""Platform management plane URL
outpost.orgIdYes""Organization UUID
outpost.auditHmacKeyYes""Base64-encoded HMAC key (min 32 bytes)
outpost.adminApiKeyYes""Emergency admin API key
certs.existingSecretYes""K8s secret with outpost.pem, outpost.key, ca.pem
KeyDefaultDescription
outpost.policyCacheSize1GiPVC size for policy cache snapshots
outpost.auditBufferSize5GiPVC size for offline audit buffer (~1KB/event, 5Gi ≈ 5M events)
outpost.maxAuditBufferEntries100000Ring buffer limit before oldest events are pruned
KeyDefaultDescription
outpost.auditSyncIntervalSeconds30How often audit events push to Platform
outpost.policySyncIntervalSeconds60Policy cache refresh interval
KeyDefaultDescription
outpost.dlpEnabledtrueEnable DLP pipeline
outpost.dlpNerDeviceautoNER inference device: auto, cpu, or cuda
outpost.dlpDebertaEnabledfalseEnable DeBERTa Tier 3 (requires GPU + model path)
outpost.debertaModelPath""Path to ONNX model inside the container
outpost.gpuEnabledfalseSwitch resource profile to GPU (nvidia.com/gpu: 1)
KeyDefaultDescription
replicaCount2Pod replica count (min 2 for PDB)
autoscaling.enabledfalseEnable HPA
autoscaling.minReplicas2HPA min replicas
autoscaling.maxReplicas10HPA max replicas
autoscaling.targetCPUUtilizationPercentage70HPA CPU target
podDisruptionBudget.minAvailable1PDB minimum available during rollouts
replicaCount: 1
outpost:
id: "dev-outpost-uuid"
platformManagementUrl: "https://api-staging.arbitex.ai"
orgId: "dev-org-uuid"
auditHmacKey: "" # set via --set-string
adminApiKey: "" # set via --set-string
logLevel: "debug"
policyCacheSize: "256Mi"
auditBufferSize: "512Mi"
dlpEnabled: true
dlpNerDevice: "cpu"
dlpDebertaEnabled: false
gpuEnabled: false
certs:
existingSecret: "outpost-certs-dev"
autoscaling:
enabled: false
podDisruptionBudget:
minAvailable: 0 # single-node dev — PDB would block rollouts
replicaCount: 2
outpost:
id: "" # set via CI --set
platformManagementUrl: "https://api.arbitex.ai"
orgId: "" # set via CI --set
auditHmacKey: "" # set via external-secrets
adminApiKey: "" # set via external-secrets
logLevel: "info"
policyCacheSize: "1Gi"
auditBufferSize: "5Gi"
maxAuditBufferEntries: 100000
auditSyncIntervalSeconds: 30
policySyncIntervalSeconds: 60
dlpEnabled: true
dlpNerDevice: "auto"
dlpDebertaEnabled: true
debertaModelPath: "/app/models/deberta/model.onnx"
gpuEnabled: true
certs:
existingSecret: "outpost-certs"
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "2Gi"
cpu: "2"
gpuResources:
requests:
memory: "2Gi"
cpu: "500m"
nvidia.com/gpu: "1"
limits:
memory: "8Gi"
cpu: "4"
nvidia.com/gpu: "1"
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
minAvailable: 1

The admin interface runs on port 8301 and is intentionally not exposed via a Kubernetes Service. Access it via port-forward:

Terminal window
kubectl port-forward \
-n arbitex-outpost \
deployment/arbitex-outpost \
8301:8301

Then access the admin API locally:

Terminal window
curl http://localhost:8301/admin/api/health

Before going live:

Platform:

  • Image digests pinned in values-prod.yaml (no latest tags in production)
  • DATABASE_URL, REDIS_URL, SECRET_KEY stored in Key Vault or Sealed Secrets — not in ConfigMaps
  • TLS certificate in arbitex-tls secret, ingress TLS configured
  • alembic.runAsInitContainer: true (database migration runs before API starts)
  • podSecurityStandard: restricted in production namespace
  • GPU nodes available and labeled/tainted for NER GPU + DeBERTa pods
  • OTEL_EXPORTER_OTLP_ENDPOINT set for distributed tracing (see Distributed tracing guide)

Outpost:

  • mTLS certificate secret created before chart install
  • outpost.id and outpost.orgId set to correct production values
  • auditHmacKey and adminApiKey stored in external secret manager
  • replicaCount: 2 with PDB minAvailable: 1
  • Persistent volumes provisioned for policy cache and audit buffer
  • Outpost reachable from client workloads on port 8300