Helm chart for deploying EfficientAI — the Voice AI Evaluation Platform — on Kubernetes.
charts/efficientai— the Helm chart (umbrella with Postgres + Redis as Bitnami subcharts).examples/— values overlays and manifests; seeexamples/README.mdfor the index.docs/— deployment guides (GKE with GCS and Prometheus/Grafana)..github/workflows/— lint/test on PRs and chart-releaser publish to GitHub Pages onmain.
Effective August 28, 2025, Bitnami restructured its container registry. Most images moved to a paid "Secure Images" tier; older/versioned images live in the "Bitnami Legacy" repository.
This chart's global.security.allowInsecureImages defaults to true so the Bitnami Postgres/Redis subcharts continue to pull from the legacy registry. To use the paid Secure Images instead, set global.security.allowInsecureImages: false and override postgresql.image.registry / redis.image.registry to bitnami.
See Bitnami's announcement for details.
helm repo add efficientai https://efficientai-tech.github.io/efficientai-k8s
helm repo update
helm install efficientai efficientai/efficientai -f values.yamlOr install directly from the source tree:
git clone https://github.com/EfficientAI-tech/efficientai-k8s.git
cd efficientai-k8s
helm dependency update charts/efficientai
helm install efficientai charts/efficientai -n efficientai --create-namespace -f my-values.yamlhelm repo update
helm upgrade efficientai efficientai/efficientai -f values.yamlReview the Bitnami Postgres and Bitnami Redis upgrade notes if the subchart versions in Chart.yaml change between releases.
Provide a SECRET_KEY (generate via openssl rand -hex 32). All other secrets are optional but recommended for production. Each sensitive value accepts either an inline value: (rendered into a chart-managed Secret) or a secretKeyRef: (sourced from a user-managed Secret).
efficientai:
secretKey:
value: "REDACTED-openssl-rand-hex-32"
# or:
# secretKeyRef: { name: efficientai-app, key: SECRET_KEY }
encryptionKey:
value: "" # optional but recommended
postgresql:
auth:
username: efficientai
password: "change-me" # OR existingSecret: ...
redis:
auth:
enabled: falseThe chart produces one config.yml (rendered into a ConfigMap and mounted at /app/config.yml), plus environment variables for every secret and every connection string. Pydantic Settings (app/config.py) reads env vars first, then config.yml overlays whatever's left — so we deliberately keep secrets and connection URLs out of the ConfigMap and rely on env vars for them.
| App setting | Where you set it | How it reaches the pod |
|---|---|---|
Plain config (app.debug, cors.origins, auth.providers, judge_alignment.*, storage.*, diarization.num_speakers, observability.loki.*, ...) |
efficientai.config.* (same nested shape as config.yml) |
Verbatim in the ConfigMap |
DATABASE_URL |
postgresql.* (in-cluster) or postgresql.deploy: false + host (external) |
Env var: built from POSTGRES_USER/PASSWORD/HOST/PORT/DB. Not written to config.yml. |
REDIS_URL, CELERY_BROKER_URL, CELERY_RESULT_BACKEND |
redis.* (in-cluster) or redis.deploy: false + host / cluster.nodes |
Env vars: built from REDIS_HOST/PORT/auth. Not written to config.yml. |
storage.blob_provider |
s3.enabled / gcs.enabled |
Rendered into the ConfigMap (s3 by default, gcs when GCS is enabled) |
s3.bucket_name / region / endpoint_url / prefix |
s3.bucket / region / endpoint / prefix |
Rendered into the ConfigMap |
S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY |
s3.accessKeyId / s3.secretAccessKey (value: OR secretKeyRef:) |
Env vars sourced from the chart-managed Secret (inline value:) or your Secret (secretKeyRef:). Not written to config.yml. |
gcs.bucket_name / project_id / prefix |
gcs.bucket / gcs.projectId / gcs.prefix |
Rendered into the ConfigMap |
BLOB_STORAGE_PROVIDER / GCS_ENABLED / GCS_BUCKET_NAME / GCS_PROJECT_ID / GCS_PREFIX |
gcs.* |
Env vars; use GKE Workload Identity / Application Default Credentials for auth. |
SECRET_KEY |
efficientai.secretKey |
Env var (chart Secret or your Secret) |
ENCRYPTION_KEY |
efficientai.encryptionKey |
Env var (chart Secret or your Secret) |
HUGGINGFACE_TOKEN |
efficientai.huggingfaceToken |
Env var (chart Secret or your Secret) |
EFFICIENTAI_LICENSE |
efficientai.license |
Env var (chart Secret or your Secret) |
Why no ${VAR} placeholders in config.yml? The app's YAML loader (app/config.py:load_config_from_file) assigns values literally — it does not expand ${...} syntax. Writing database: { url: "${DATABASE_URL}" } would clobber the env-derived URL with the literal placeholder string and crash SQLAlchemy. Keeping connection strings and secrets in env vars only is the safe pattern.
Anything you set under efficientai.config.{database,redis,celery,license,app.secret_key,diarization.huggingface_token} is stripped by the ConfigMap template, so you can't accidentally leak a secret into a ConfigMap.
Extending it: to add a new non-secret config field, just add it under efficientai.config.* — it flows through toYaml automatically, no template change required. For a new secret field, see the "Adding a new secret" recipe in charts/efficientai/README.md.
Defaults are minimal for dev / smoke testing. For production, scale per-component:
efficientai:
web:
replicaCount: 3
resources:
limits: { cpu: "2", memory: "4Gi" }
requests: { cpu: "1", memory: "2Gi" }
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
pdb:
enabled: true
minAvailable: 2
worker:
replicaCount: 2
resources:
limits: { cpu: "2", memory: "8Gi" }
requests: { cpu: "1", memory: "4Gi" }
workerImports:
replicaCount: 1
concurrency: 4
resources:
limits: { cpu: "2", memory: "4Gi" }
requests: { cpu: "500m", memory: "2Gi" }
postgresql:
primary:
persistence:
size: 50Gi
resources:
limits: { cpu: "2", memory: "4Gi" }
requests: { cpu: "1", memory: "2Gi" }
redis:
primary:
persistence:
size: 8Gi
resources:
limits: { cpu: "1", memory: "1.5Gi" }
requests: { cpu: "500m", memory: "1Gi" }Every example in examples/ is a values overlay or manifest. See examples/README.md for the full index.
| File | What it demonstrates |
|---|---|
external-postgres.yaml |
postgresql.deploy: false, external host, existingSecret, plus optional TLS / client cert via extraVolumes + additionalEnv |
external-redis.yaml |
External standalone Redis and external Redis Cluster (e.g. AWS ElastiCache config endpoint) |
external-s3.yaml |
External S3 bucket with credentials sourced via secretKeyRef |
gke/values-gcs.yaml |
GKE + GCS via Workload Identity, observability config, GCE Ingress |
ingress-alb.yaml |
AWS Load Balancer Controller with redirect action and custom per-host backend |
sso-oidc.yaml |
External OIDC SSO (Okta-style) wired through efficientai.web.additionalEnv |
topology-spread.yaml |
Zone- and host-aware spread constraints for web, worker, and workerImports |
helm install efficientai charts/efficientai -f examples/external-postgres.yamlSee docs/gke-gcs-observability.md for the full guide. Summary:
make observability-install
make observability-servicemonitor
cp examples/gke/values-gcs.yaml my-gke-values.yaml # customize domains, project, bucket
helm upgrade --install efficientai charts/efficientai -n efficientai -f my-gke-values.yaml --wait
make observability-grafana-expose # optional: public Grafana URL
helm upgrade kube-prometheus prometheus-community/kube-prometheus-stack \
-n observability -f examples/observability/kube-prometheus-stack.yaml
# Import EfficientAI dashboards from the main app repo (observability/grafana/dashboards/)# Global default
global:
defaultStorageClass: "my-storage-class"
# Or per-component
postgresql:
primary:
persistence:
storageClass: "postgres-ssd"
redis:
primary:
persistence:
storageClass: "redis-ssd"efficientai:
web:
deployment:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%See charts/efficientai/README.md for the full list of configurable values.
# Install the plugin once
helm plugin install https://github.com/helm-unittest/helm-unittest.git
# Run unit tests
helm unittest charts/efficientai --colorLint and render the templates with the included Makefile:
make deps # helm dependency update
make lint
make template
make unittest
make install # to a real cluster (uses kubeconfig context)The repo ships two helper scripts under scripts/ for end-to-end local testing on kind using a local Docker registry (so private GHCR / image-pull problems don't get in the way):
make kind-up # one-time: create kind cluster + localhost:5001 registry
make kind-deploy # push local efficientai-api/-worker:1.3.9 images + helm upgrade --install
make kind-down # tear down cluster + registrymake kind-deploy will:
- Tag and push
ghcr.io/efficientai-tech/efficientai-{api,worker}:1.3.9from your local Docker daemon into the kind registry atlocalhost:5001. - Reuse existing in-cluster secrets on upgrade (so
SECRET_KEY,ENCRYPTION_KEY, and the Postgres password don't rotate on every redeploy). - Run
helm upgrade --installpointing atlocalhost:5001for the app images, and wait for all five workloads (postgresql,redis-master,web,worker,worker-imports) to be Ready.
After it finishes:
kubectl -n efficientai port-forward svc/dev-efficientai-web 8000:8000
open http://localhost:8000/docsOverride the defaults via env vars: TAG=1.4.0 NAMESPACE=staging RELEASE=stage make kind-deploy.