Langfuse and OpenBao
AI Services and the products built on it rely on two utility services that are not part of the ai-services chart, nor of any other Mia Platform chart:
| Service | What it is used for | Used by |
|---|---|---|
| Langfuse | Trace store for AI observability: model calls, agent runs and their costs. | AI Foundry, which reads traces for its observability screens and its agents' trace tools. Traces reach it over OTLP. |
| OpenBao | Secret store behind every secretStoreService. | AI Services (gateway-keys, the tenants' AI gateway keys), AI Foundry and Catalog, each with its own secretStoreService instance. |
Mia Platform does not repackage these services: install them with the tools their maintainers provide.
- On Kubernetes, use the official Helm charts:
- Langfuse: Kubernetes (Helm) deployment guide, chart repository langfuse/langfuse-k8s.
- OpenBao: OpenBao on Kubernetes and its Helm chart.
- Any other way the product supports, for example a virtual machine, Docker Compose, a cloud provider or an instance your organization already runs, by following the product's official guides:
- Langfuse: self-hosting overview, which lists every supported deployment option.
- OpenBao: installing OpenBao. An existing HashiCorp Vault with the KV v2 secrets engine works as well.
Mia Platform components only need to reach these services over the network and to hold a credential for them. The rest of this page describes what each one must provide and how to connect it, whatever the deployment method.
Sizing, high availability, backup, upgrades and security hardening of Langfuse and OpenBao follow the official documentation of each product and are the responsibility of whoever operates them.
Langfuse
What Mia Platform needs from Langfuse
- A project and its API key pair (
pk-lf-...andsk-lf-...). Traces are written to that project, and AI Foundry reads them with the same pair. - The OTLP traces endpoint,
<LANGFUSE_URL>/api/public/otel/v1/traces, reachable from the cluster where telemetry is exported. Langfuse authenticates OTLP requests with HTTP Basic authentication built from the project key pair. See Langfuse OpenTelemetry integration. - The Langfuse API, reachable by AI Foundry's
ai-foundry-beandadk-be-app.
Langfuse stores traces only, not metrics.
Install Langfuse on Kubernetes
Follow the Kubernetes (Helm) deployment guide for prerequisites, sizing and the full list of values. In short:
helm repo add langfuse https://langfuse.github.io/langfuse-k8s
helm repo update
helm install langfuse langfuse/langfuse \
--namespace langfuse \
--create-namespace \
--values langfuse-values.yaml
Things to keep in mind when you prepare langfuse-values.yaml:
-
By default the chart deploys PostgreSQL, ClickHouse, Redis and an S3-compatible blob store next to Langfuse. To use external or managed instances, set
deploy: falseon each of them (postgresql,clickhouse,redis,s3) and provide their connection details, as described in the chart's documentation. -
For the blob store, prefer a bucket of your cloud provider (see blob storage). On GKE, Langfuse can use a Cloud Storage bucket through Workload Identity, with no static key: grant a Google service account
roles/storage.objectAdminon the bucket, allow the Kubernetes ServiceAccount of Langfuse to impersonate it (roles/iam.workloadIdentityUserfor<PROJECT_ID>.svc.id.goog[langfuse/langfuse]with the release namelangfusein thelangfusenamespace), then set:langfuse:serviceAccount:annotations:iam.gke.io/gcp-service-account: <GSA_NAME>@<PROJECT_ID>.iam.gserviceaccount.coms3:deploy: falsestorageProvider: gcsbucket: <BUCKET_NAME> -
Set
langfuse.salt,langfuse.encryptionKeyandlangfuse.nextauth.secretexplicitly, inline or throughsecretKeyRef. The chart generates them on the first install when they are empty, but tools that render the chart withhelm template(GitOps pipelines) generate new values on every sync. Never changeencryptionKeyonce data is stored. -
Set
langfuse.nextauth.urlto the URL users open Langfuse at, and expose the web UI with an Ingress (or an equivalent route) on a host of its own: Langfuse does not support being served under a path prefix. -
To avoid creating the project and copying its keys by hand after every fresh install, use headless initialization: the
LANGFUSE_INIT_*environment variables, set throughlangfuse.additionalEnv, create the organization, the project and its key pair on the first start.
With the release name langfuse in the langfuse namespace, the web service is reachable inside the cluster at http://langfuse-web.langfuse.svc.cluster.local:3000.
Connect Mia Platform to Langfuse
| Where | Value |
|---|---|
AI Services: otelCollector.config.langfuse.tracesEndpoint | <LANGFUSE_URL>/api/public/otel/v1/traces |
AI Services: secrets.otelCollectorKeys.langfuseOtlpBasic | <base64 of pk-lf-...:sk-lf-...> |
| Any other OTLP exporter or collector that sends AI traces | <LANGFUSE_URL>/api/public/otel/v1/traces, with the header Authorization: Basic <base64 of pk-lf-...:sk-lf-...> |
AI Foundry: aiFoundryBe.config.langfuseBaseUrl and adkBeApp.config.langfuseBaseUrl | The Langfuse base URL |
AI Foundry: secrets.aiFoundryBeKeys.langfuseAuthHeader and secrets.adkBeAppKeys.langfuseAuthHeader | Basic <base64 of pk-lf-...:sk-lf-...> |
See the AI Services Helm values and the AI Foundry Helm values for the details of each key.
OpenBao
What Mia Platform needs from OpenBao
Each secretStoreService instance needs:
- The store address (
secretStoreService.config.storeAddr), for examplehttp://openbao.openbao.svc.cluster.local:8200. - A KV v2 secrets engine mounted at the path named by
secretStoreService.config.storeKvMount(secretby default in AI Services and Catalog: check the Helm values of each product). - The OpenBao namespace that holds that mount, if you use namespaces (
secretStoreService.config.storeNamespace). - A credential, depending on
secretStoreService.config.auth.method:jwt(recommended on Kubernetes): the pod's own ServiceAccount token, validated by a JWT auth mount configured with Kubernetes as the OIDC provider. No static credential is created, stored or rotated.approle(the chart default): an AppRolerole-idandsecret-id, stored insecrets.secretStoreServiceKeys.storeRoleIdandstoreSecretId. Use it when OpenBao cannot validate the tokens of the cluster wheresecretStoreServiceruns.
- A policy attached to that credential that grants only the secrets of that product.
secretStoreService stores every secret at <kv-mount>/data/<organization>/<tenant>/<collection>/<key>.
Reference layout
This is how Mia Platform runs OpenBao for its own environments, and the layout we recommend:
- One OpenBao instance shared by every product, with one namespace per environment and product, for example
console/production/ai-services,console/production/ai-foundryandconsole/production/catalog. Each namespace has its own KV v2 mount atsecretand its own policies and auth mounts, so a product's credential never reaches another product's secrets. - JWT auth for workloads: in each namespace, one JWT auth mount per Kubernetes cluster that runs the product (for example
jwt-<cluster>), trusting that cluster's OIDC issuer, and one role per Kubernetes namespace of the product, bound to its ServiceAccounts (system:serviceaccount:<k8s-namespace>:*) and to theopenbaoaudience, with short-lived tokens. - Auto-unseal with a cloud KMS, so that restarted pods unseal on their own (see below).
- Configuration as code: mounts, namespaces, auth methods and policies are managed with the Terraform Vault provider, which is compatible with OpenBao, rather than by hand. The commands in Configure OpenBao for AI Services show the equivalent operations.
Install OpenBao on Kubernetes
Follow OpenBao on Kubernetes and the Helm chart documentation. In short:
helm repo add openbao https://openbao.github.io/openbao-helm
helm repo update
helm install openbao openbao/openbao \
--namespace openbao \
--create-namespace \
--values openbao-values.yaml
By default the chart runs a single server in standalone mode, which the OpenBao documentation does not consider suitable for production. Choose the run mode, storage backend and seal configuration following Run OpenBao on Kubernetes and its production checklist.
After installing, OpenBao must be initialized (bao operator init) as described in the OpenBao documentation. Keep the keys and the root token it returns outside the cluster. Without auto-unseal, every restarted OpenBao pod comes back sealed and must be unsealed by hand before any secretStoreService can read or write secrets.
With the release name openbao in the openbao namespace, OpenBao is reachable inside the cluster at http://openbao.openbao.svc.cluster.local:8200.
Auto-unseal with a cloud KMS
OpenBao can unseal itself with a key held by a cloud KMS, configured with a seal stanza in the server configuration (server.standalone.config or server.ha.config in the chart values). With auto-unseal, bao operator init returns recovery keys instead of unseal keys.
On GKE, use Cloud KMS with Workload Identity, so that no credential file is needed:
-
Create a key ring and an
ENCRYPT_DECRYPTkey in Cloud KMS. -
Create a Google service account and grant it, on that key,
cloudkms.cryptoKeyVersions.useToEncrypt,cloudkms.cryptoKeyVersions.useToDecryptandcloudkms.cryptoKeys.get. -
Allow the Kubernetes ServiceAccount of OpenBao to impersonate it:
roles/iam.workloadIdentityUserfor<PROJECT_ID>.svc.id.goog[openbao/openbao], with the release nameopenbaoin theopenbaonamespace. -
Annotate the ServiceAccount and add the
sealstanza to the server configuration:server:serviceAccount:annotations:iam.gke.io/gcp-service-account: <GSA_NAME>@<PROJECT_ID>.iam.gserviceaccount.comseal "gcpckms" {project = "<PROJECT_ID>"region = "<KEY_RING_LOCATION>"key_ring = "<KEY_RING_NAME>"crypto_key = "<CRYPTO_KEY_NAME>"}
Other cloud providers have equivalent seals: see the seal configuration of OpenBao.
Configure OpenBao for AI Services
The following commands prepare OpenBao for the secretStoreService of AI Services, which stores only the gateway-keys collection. Run them with the bao CLI, logged in with a token allowed to manage namespaces, mounts, auth methods and policies. The examples use the console/production/ai-services namespace of the reference layout and a Kubernetes namespace ai-services: adapt both, and skip the namespace steps if you do not use OpenBao namespaces.
-
Create the namespace, one level at a time, and select it for the following commands:
bao namespace create consoleBAO_NAMESPACE=console bao namespace create productionBAO_NAMESPACE=console/production bao namespace create ai-servicesexport BAO_NAMESPACE=console/production/ai-services -
Enable the KV v2 secrets engine at the mount set in
secretStoreService.config.storeKvMount:bao secrets enable -path=secret kv-v2 -
Write a policy that grants the
gateway-keyscollection and nothing else:bao policy write ai-services-gateway-keys - <<'EOF'path "secret/data/+/+/gateway-keys/*" { capabilities = ["create", "read", "update"] }path "secret/metadata/+/+/gateway-keys/*" { capabilities = ["read", "delete"] }EOF -
Create the credential, with one of the two methods.
JWT (recommended). Enable a JWT auth mount that trusts the OIDC issuer of the cluster where AI Services runs, and a role for its Kubernetes namespace:
bao auth enable -path=jwt-<cluster> jwt# On GKE the issuer is https://container.googleapis.com/v1/projects/<PROJECT_ID>/locations/<LOCATION>/clusters/<CLUSTER>.# On other clusters, read it with: kubectl get --raw /.well-known/openid-configurationbao write auth/jwt-<cluster>/config oidc_discovery_url="<CLUSTER_OIDC_ISSUER>"bao write auth/jwt-<cluster>/role/ai-services - <<'EOF'{"role_type": "jwt","user_claim": "sub","bound_audiences": ["openbao"],"bound_claims_type": "glob","bound_claims": { "sub": "system:serviceaccount:ai-services:*" },"token_policies": ["ai-services-gateway-keys"],"token_ttl": "15m"}EOFOpenBao must be able to reach the issuer's discovery document. If it cannot, configure the mount with the cluster's public keys instead, as described in Kubernetes as an OIDC provider.
AppRole. Enable AppRole, create a role with the policy, then read its
role-idand generate asecret-id:bao auth enable approlebao write auth/approle/role/ai-services \token_policies="ai-services-gateway-keys" \token_ttl=1h \token_max_ttl=24hbao read -field=role_id auth/approle/role/ai-services/role-idbao write -f -field=secret_id auth/approle/role/ai-services/secret-id -
Configure AI Services. With JWT:
secretStoreService:enabled: trueconfig:storeAddr: "https://openbao.your-domain.com"storeKvMount: "secret"storeNamespace: "console/production/ai-services"auth:method: jwtjwt:mount: "jwt-<cluster>"role: "ai-services"With AppRole, leave
auth.methodtoapproleand pass the two values asstoreRoleIdandstoreSecretId(thesecret-store-service-keysSecret, orsecrets.secretStoreServiceKeys). See the Helm values reference.
For the secretStoreService instances of AI Foundry and Catalog, repeat these steps in a namespace of their own (for example console/production/ai-foundry), with the KV mount set in their storeKvMount, their Kubernetes namespace in the role and a policy covering the collections they use.
If you deploy OpenBao in another way, the same configuration applies: only the store address changes.