Secrets and GitOps
What is generated
Section titled “What is generated”With secrets.generate: true (the default), the chart creates a Secret named
langfuse-secrets:
| Key | Purpose |
|---|---|
salt | Hashes project API keys |
encryptionKey | 64 hex characters — encrypts stored integration credentials |
nextauth-secret | NextAuth session signing key |
postgres-password | PostgreSQL |
redis-password | Redis |
clickhouse-password | ClickHouse |
root-user | MinIO root user (langfuse) |
root-password | MinIO root password |
The template reads any existing Secret with Helm’s lookup and reuses each key it finds,
generating only what is missing. Combined with helm.sh/resource-policy: keep, values
survive helm upgrade and helm uninstall without rotating.
Both the Langfuse app and the four bundled datastore subcharts read from this one Secret, so the credentials on both sides of every connection always agree.
The Argo CD problem
Section titled “The Argo CD problem”The GitOps procedure
Section titled “The GitOps procedure”1. Namespace and label:
kubectl create namespace langfusekubectl label namespace langfuse nebari.dev/managed=true2. Create the Secret once, before the first sync:
kubectl -n langfuse create secret generic langfuse-secrets \ --from-literal=salt=$(openssl rand -hex 16) \ --from-literal=encryptionKey=$(openssl rand -hex 32) \ --from-literal=nextauth-secret=$(openssl rand -hex 32) \ --from-literal=postgres-password=$(openssl rand -hex 16) \ --from-literal=redis-password=$(openssl rand -hex 16) \ --from-literal=clickhouse-password=$(openssl rand -hex 16) \ --from-literal=root-user=langfuse \ --from-literal=root-password=$(openssl rand -hex 16)3. Turn generation off:
secrets: generate: false4. Optionally, stop Argo CD flagging the pre-created Secret as out of sync with an
ignoreDifferences entry for the Secret resource.
A production Helm install should do the same. Generation is a convenience for development, not a secret-management strategy — for anything real, use sealed-secrets, external-secrets, or your cloud’s secret manager.
Rotation
Section titled “Rotation”What rotating each key costs:
| Key | Cost of rotating |
|---|---|
nextauth-secret | Every active session is invalidated; users log in again. |
postgres-password, redis-password, clickhouse-password | Must be changed in the datastore and the Secret, then both sides restarted. |
root-password | Same, for MinIO. |
salt | Invalidates every project API key. All SDK ingestion stops until keys are reissued. |
encryptionKey | Orphans all stored integration credentials. |
Rotating a bundled datastore password means restarting the datastore and both Langfuse workloads:
kubectl -n langfuse rollout restart statefulset/langfuse-postgresqlkubectl -n langfuse rollout restart deployment/langfuse-web deployment/langfuse-workerVerifying
Section titled “Verifying”kubectl -n langfuse get secret langfuse-secrets -o jsonpath='{.data}' | jq 'keys'All eight keys should be present. The one with a hard length requirement:
kubectl -n langfuse get secret langfuse-secrets \ -o jsonpath='{.data.encryptionKey}' | base64 -d | tr -d '\n' | wc -c# 64Anything other than 64 crash-loops langfuse-web with an ENCRYPTION_KEY length error.
Under Argo CD, confirm the Secret is stable across a sync:
kubectl -n langfuse get secret langfuse-secrets -o jsonpath='{.data.salt}' | sha256sum# sync, then run again — the digest must not changeBacking it up
Section titled “Backing it up”The Secret is not derivable from anything else, and without it a restored database is
unreadable: encryptionKey decrypts the integration credentials, and the datastore
passwords are the only copies the bundled instances have.
kubectl -n langfuse get secret langfuse-secrets -o yaml > langfuse-secrets-backup.yamlStore it wherever your other credentials live, not next to the database dump.