Skip to content

Search these docs from your AI tool

  • Antigravity
    {
      "mcpServers": {
        "edisyl-docs": {
          "serverUrl": "https://docs.edisyl.com/mcp"
        }
      }
    }
  • Claude CodeCLI
    claude mcp add edisyl-docs https://docs.edisyl.com/mcp --transport http --scope user
  • CodexCLI
    codex mcp add --url https://docs.edisyl.com/mcp edisyl-docs
  • Cursor
    {
      "mcpServers": {
        "edisyl-docs": {
          "type": "http",
          "url": "https://docs.edisyl.com/mcp"
        }
      }
    }
  • Gemini CLI
    gemini mcp add --transport http edisyl-docs https://docs.edisyl.com/mcp
  • Goose
    {
      "extensions": {
        "edisyl-docs": {
          "enabled": true,
          "name": "edisyl-docs",
          "type": "streamable_http",
          "uri": "https://docs.edisyl.com/mcp",
          "envs": {},
          "env_keys": [],
          "headers": {},
          "description": "",
          "timeout": 300,
          "bundled": null,
          "available_tools": []
        }
      }
    }
  • JetBrains AI Assistant
    {
      "mcpServers": {
        "edisyl-docs": {
          "type": "http",
          "url": "https://docs.edisyl.com/mcp"
        }
      }
    }

    Paste into Settings → Tools → AI Assistant → Model Context Protocol → Add → As JSON. Direct file writing isn’t supported — JetBrains stores this per-version as XML.

  • Junie (JetBrains)
    {
      "mcpServers": {
        "edisyl-docs": {
          "url": "https://docs.edisyl.com/mcp"
        }
      }
    }
  • OpenCode
    {
      "mcp": {
        "edisyl-docs": {
          "type": "remote",
          "url": "https://docs.edisyl.com/mcp"
        }
      }
    }
  • VS CodeCLI
    code --add-mcp '{"name":"edisyl-docs","type":"http","url":"https://docs.edisyl.com/mcp"}'
  • Windsurf
    {
      "mcpServers": {
        "edisyl-docs": {
          "serverUrl": "https://docs.edisyl.com/mcp"
        }
      }
    }

    Supports both stdio and native HTTP connections.

Values reference

Edisyl umbrella chart — defaults. This file is the schema + sane defaults; an operator's own values file (see examples/) overrides per environment. Structure: images — shared registry + global tag (per-app override allowed) secrets — runtime backend: k8s-native (default) | external-secrets edisyl — the 5 first-party services (templates/apps/<name>.yaml, sharing the "edisyl.app" helper) config — non-secret env (edisyl-config) cnpg — CloudNativePG Cluster CR inngest/minio/redis — optional infra (subchart deps + our glue) Namespace: every object this chart renders (first-party or subchart) lands in `.Release.Namespace` — whatever `helm install -n <ns>` targets. There is no separate namespaces.app/namespaces.create knob; create the namespace yourself, or pass `--create-namespace` to `helm install` (see the README's Installing section). `edisyl.ns` in _helpers.tpl is the single accessor.

Generated from the chart's own values.yaml at chart 0.52.0 — 312 documented paths out of 697 total. Per-app duplicates and upstream subchart internals are folded; see each section’s note. The same file ships inside the chart you pull, sohelm show values gives you the raw form.

Images

imagesobject{5 keys}

No description in values.yaml.

images.registrystring944311261080.dkr.ecr.us-east-1.amazonaws.com/edisyl-prd

The hub ECR registry (account 944311261080), `edisyl-prd` namespace. This is the registry a self-hosted client is actually granted — per-client IAM roles give read-only pull on `edisyl-prd/mono-*` and on the chart itself (terraform-client-access). It replaces the private Depot registry, which was the authoritative internal one and which no customer could pull from. The registry and the tag MOVE TOGETHER. These tags exist under this namespace and nowhere else, so overriding one without the other is an ImagePullBackOff rather than a clean error. ECR never allows anonymous pull and its tokens expire after 12 hours, so images.pullSecret is required in practice here — see ecrRefresh below, which keeps that secret alive.

images.tagstringprd-c255727

FALLBACK ONLY, and nothing uses it today: every first-party app states its own edisyl.<app>.image.tag below, and a per-app tag wins over this one. It stays as the answer for an app added later that forgets to — and tests/render.sh fails if any app relies on it, so that is a loud state rather than a silent inheritance of whatever number happens to sit here. PINNED, not rolling. `dev` used to be the default, which meant a customer install silently tracked whatever had last been built — and no `dev` tag exists under edisyl-prd anyway, so it was an ImagePullBackOff rather than a clean error. The IDP reads none of this: it sets env-keyed full references (edisyl.<app>.image.{dev,stg,prd}) which win outright over the composed default — see edisyl.image. These are the CUSTOMER defaults only.

images.pullPolicystringIfNotPresent

No description in values.yaml.

images.pullSecretstring""

optional regcred name

images.pullSecretAuthobject{4 keys}

Create that secret FROM VALUES instead of by hand (default off). With this off, the imagePullSecret is a manual kubectl step that must happen before the first install — the pre-install migrate hook pulls a first-party image. With it on, the password lives in your own gitignored values file (the same secrets.local.yaml pattern the rest of this chart's credentials use) and `helm install -f` seeds the secret for you. The IDP does not need this: the platform supplies pull credentials to its spokes outside the chart. Left off, nothing renders. For ECR, this is a SEED, not a steady state: the password is an authorization token that expires in 12 hours, so the chart refuses an ECR registry here unless ecrRefresh is also on, and hands the secret over to the refresher on install rather than re-pasting a dead token on every upgrade.

images.pullSecretAuth.enabledbooleanfalse

No description in values.yaml.

images.pullSecretAuth.registrystring""

defaults to images.registry

images.pullSecretAuth.usernamestring""

AWS for ECR

images.pullSecretAuth.passwordstring""

keep in a gitignored values file, never a committed one

ECR pull-secret refresher (OFF by default)

ecrRefreshobject{11 keys}

ECR authorization tokens expire after 12 HOURS, so an imagePullSecret holding one stops working overnight. The failure is delayed, not immediate: running pods keep running, and the first sign is an ImagePullBackOff on the next restart, rollout or scale-up — hours after the cause. Enable this only when pulling first-party images from ECR with a secret. A registry with static credentials, or one the nodes already reach through an instance role, needs none of it. THIS DOES NOT CREATE THE SECRET FOR THE FIRST TIME. The initial imagePullSecret is a prerequisite of installing at all — the pre-install migrate hook pulls a first-party image before anything here could run. The README carries the one-off command, and the AWS-side binding it needs. Consequently the CronJob's RBAC is get/patch/update on that ONE secret name, with no create verb at all: it never legitimately creates a Secret, and the namespace it runs in also holds the database credentials and the JWT signing keys.

ecrRefresh.enabledbooleanfalse

No description in values.yaml.

ecrRefresh.secretNamestring""

The secret to keep alive. Defaults to images.pullSecret, which is the one every app actually mounts; set this only to maintain a differently-named secret.

ecrRefresh.registrystring""

The registry the token authenticates against. Defaults to images.registry, and is refused at render if that is not an ECR host — an ECR token authenticates against nothing else. Set explicitly for a private mirror or an interface endpoint.

ecrRefresh.regionstring""

REQUIRED. The registry's own region: an authorization token is regional.

ecrRefresh.roleArnstring""

Cross-account pull, as Edisyl-hosted customers use: the role in the registry owner's account that this assumes. Leave empty when the pod's own identity already holds ECR read in the same account. The pod's identity comes from IRSA (serviceAccountAnnotations below) or EKS Pod Identity (an association created outside Helm). With Pod Identity, make that association with --disable-session-tags: Pod Identity attaches TRANSITIVE session tags, and a transitive tag carried into this second AssumeRole requires the destination trust policy to allow sts:TagSession. A client role that allows only sts:AssumeRole fails with an AccessDenied naming neither tags nor the reason.

ecrRefresh.externalIdstring""

A confused-deputy guard on that role, not a secret — it is useless unless the role's own trust policy also requires it.

ecrRefresh.schedulestring0 */4 * * *

Every FOUR hours against a twelve-hour token: a missed run still leaves four hours of margin. At six, a single missed run lands the next attempt exactly on the expiry.

ecrRefresh.serviceAccountstringecr-refresh

No description in values.yaml.

ecrRefresh.serviceAccountAnnotationsobject{}

EKS IRSA names the role here (eks.amazonaws.com/role-arn); Pod Identity leaves it empty.

ecrRefresh.imagestringalpine/k8s:1.31.1

Must be pullable WITHOUT the secret this maintains, or an expired secret leaves you unable to run the thing that fixes it.

ecrRefresh.resourcesobject{2 keys}

No description in values.yaml.

ecrRefresh.resources.requestsobject{"cpu":"50m","memory":"128Mi"}

No description in values.yaml.

ecrRefresh.resources.requests.cpustring50m

No description in values.yaml.

ecrRefresh.resources.requests.memorystring128Mi

No description in values.yaml.

ecrRefresh.resources.limitsobject{"memory":"256Mi"}

No description in values.yaml.

ecrRefresh.resources.limits.memorystring256Mi

No description in values.yaml.

Labels

envstring""

platform.edisyl.com/env label (local|qa|...)

commonLabelsobject{}

Applied to every resource this chart renders, including pod templates. NOT applied to subchart resources (Inngest, MinIO): those take inngest.commonLabels and minio.additionalLabels. The chart's own label keys (app, app.kubernetes.io/*, helm.sh/chart, platform.edisyl.com/env, edisyl.com/team) cannot be overridden and the render fails if one is set here. Selectors never include these, so adding or changing them later is a normal rolling update.

Secrets backend

secretsobject{4 keys}

No description in values.yaml.

secrets.providerstringk8s-native

k8s-native — plain Secrets populated from the values below. The default, because we cannot know what secret manager a self-hosting customer runs. external-secrets — ExternalSecret CRs from a store the operator names.

secrets.externalSecretsobject{5 keys}

No description in values.yaml.

secrets.externalSecrets.storeNamestring""

Name an EXISTING store — or leave empty and set createStore.project to have the chart render one (IDP posture: the platform secret-manager project + the platform-injected env config + the per-env reader token; see templates/secretstore.yaml). storeName then defaults to <namespace>-doppler.

secrets.externalSecrets.createStoreobject{"project":""}

No description in values.yaml.

secrets.externalSecrets.createStore.projectstring""

No description in values.yaml.

secrets.externalSecrets.storeKindstringClusterSecretStore

No description in values.yaml.

secrets.externalSecrets.refreshIntervalstring15m

How often ESO re-reads each key from the store to catch a changed value. Each key is its own request, so this sets the load on the store: at 1m the per-key reads exceed what Doppler serves for three environments. A new or edited ExternalSecret syncs at once regardless; this only bounds how long a value changed in the store takes to arrive (force sooner with the force-sync annotation).

secrets.externalSecrets.syncWavestring-3

No description in values.yaml.

secrets.managedobject{26 keys}

The logical secrets the apps consume, grouped per integration so each app receives only the families it uses. `keys` share the local/remote name; `remoteKeys` maps local → remote; `templateData` adds literal keys.

secrets.managed.edisyl-databaseobject{2 keys}

No description in values.yaml.

secrets.managed.edisyl-database.keysarray["DATABASE_URL"]

Rendered only with database.source: managed. The pool size is config (config.databasePoolSize), not part of the credential.

secrets.managed.edisyl-redisobject{2 keys}

No description in values.yaml.

secrets.managed.edisyl-redis.keysarray["REDIS_PASSWORD"]

The Redis password, and nothing else: host, port, user and TLS are config (config.redis*, or derived from the bundled server) and render into edisyl-config. Blank with bundled Redis, it is filled from the single generated $redisPw so it cannot diverge from what Redis enforces.

secrets.managed.edisyl-redis-latticeobject{2 keys}

No description in values.yaml.

secrets.managed.edisyl-redis-lattice.keysarray["LATTICE_REDIS_URL"]

lattice reads Redis under a namespaced var (aws/ecs.tf remaps it too).

secrets.managed.edisyl-redis-authobject{2 keys}

No description in values.yaml.

secrets.managed.edisyl-redis-inngestobject{2 keys}

No description in values.yaml.

secrets.managed.edisyl-redis-inngest.keysarray["INNGEST_REDIS_URI"]

Inngest's queue/live-run backing Redis. A DEDICATED secret, not a key bolted onto edisyl-redis: edisyl-redis is already in api's envFrom, so adding a key there would leak an unused INNGEST_REDIS_URI into api's env too. Consumed only via secretKeyRef (inngest.inngest.extraEnv), never envFrom — same treatment as edisyl-redis-auth's REDIS_PASSWORD.

secrets.managed.edisyl-auth-clientobject{3 keys}

No description in values.yaml.

secrets.managed.edisyl-auth-client.keysarray[]

Where the fleet's auth service is, and the keys it verifies against — the CONSUMER side. edisyl-auth is the other end: what the auth service signs WITH, mounted on that pod alone. No URL here. Where the auth service is, is derived into edisyl-config from the host this chart publishes for it, so the address and the service cannot disagree — and it is a public hostname with no business in a credential bundle in the first place. remoteKeys, because the store's name for the key (AUTH_SELFHOSTED_*) is not the name the apps read.

secrets.managed.edisyl-auth-adminobject{3 keys}

No description in values.yaml.

secrets.managed.edisyl-auth-admin.keysarray[]

The credential for the auth service's ADMIN API. Its own secret because it mounts on the api and on the edisyl-idp-reconcile Job alone — that Job uses it only to register the deployment owner's IdP. edisyl reaches the same auth service but never its admin surface, and a browser-facing server has no business holding a credential that can register an identity provider. Where the api dials is NOT here: AUTH_INTERNAL_URL is derived into edisyl-config from the auth Service this chart renders, so it cannot disagree with the service the credential is for — a disagreement that 401s every admin call with no other symptom.

secrets.managed.edisyl-inngestobject{2 keys}

No description in values.yaml.

secrets.managed.edisyl-authobject{2 keys}

No description in values.yaml.

secrets.managed.edisyl-auth.keysarray[3 items]

The standalone auth service's signing material. GOTRUE_JWT_KEYS is what governs verification; GOTRUE_JWT_SECRET is only required for the process to start. Both are refused empty by the image rather than falling back — upstream's defaults are Supabase's PUBLISHED demo values, so an unset key is not a degraded mode but a signing key everyone already has.

secrets.managed.edisyl-auth.data.GOTRUE_SAML_PRIVATE_KEYstring""

DER, base64. Signs the AuthnRequests an identity provider verifies. Independent of the JWT signing key, so rotating it ends no session.

secrets.managed.edisyl-encryptionobject{2 keys}

The api's AES-256 master key: 32 random bytes, base64-encoded (`openssl rand -base64 32`). It encrypts each organization's data key, which encrypts that organization's data source credentials, so it can never be rotated without re-encrypting them. Generated when left blank.

secrets.managed.edisyl-mono-api-keyobject{2 keys}

No description in values.yaml.

secrets.managed.edisyl-token-hmacobject{2 keys}

No description in values.yaml.

secrets.managed.edisyl-llm-gatewayobject{2 keys}

No description in values.yaml.

secrets.managed.edisyl-llm-gateway.keysarray["LLM_GATEWAY_API_KEY"]

The virtual key api and worker send every generation and embedding call to Bifrost with. Where the gateway is, LLM_GATEWAY_URL, is config derived from bifrost.enabled, else bifrost.external.url, and renders into edisyl-config.

secrets.managed.edisyl-bifrostobject{2 keys}

No description in values.yaml.

secrets.managed.edisyl-bifrost.keysarray[3 items]

The bundled gateway's MANAGEMENT credential — the one that can read and write provider configuration, and therefore reach every upstream vendor key the deployment holds. Mounted in exactly two places: the gateway itself, which enforces it, and the api, which is the only thing that administers it. Never the worker and never edisyl — a browser-facing server has no business holding a credential that can enumerate provider secrets, the same rule edisyl-auth-admin follows. Deliberately NOT the same credential as LLM_GATEWAY_API_KEY above. That one is a virtual key scoped to inference and cannot read provider configuration; keeping them apart is what stops a leaked runtime key from becoming a vendor-credential leak. Renders only where bifrost.enabled is true (templates/secrets.yaml): under external-secrets an unreferenced ExternalSecret demands keys the store has no reason to carry and never syncs. BIFROST_RUNTIME_KEY is the third key and is NOT a management credential: it is the virtual key templates/bifrost.yaml seeds, scoped to inference. The applications infer with it, and the administration surface presents the same key to prove a provider it just saved actually answers, since a test is an inference request with the credential under test. Generated with the `sk-bf-` prefix Bifrost needs to read a virtual key out of an `Authorization: Bearer` header — without it the key only travels in Bifrost's own x-bf-vk header, which an OpenAI-shaped client never sends.

secrets.managed.edisyl-bifrost-encryptionobject{2 keys}

No description in values.yaml.

secrets.managed.edisyl-bifrost-encryption.keysarray["BIFROST_ENCRYPTION_KEY"]

Argon2id-derived AES key for the provider credentials Bifrost stores in its Postgres. Its OWN secret, mounted on the gateway alone: the api administers providers over the management API and has no reason to hold the key their ciphertext is under. ROTATING THIS ORPHANS EVERY STORED CREDENTIAL — Bifrost cannot re-encrypt them, so the providers have to be entered again. That is why it is generated once and read back (edisyl.generatedSecret), and why an Argo CD install must supply it explicitly rather than let the chart generate it: Argo renders with `helm template`, where the read-back finds nothing and a fresh key appears on every sync.

secrets.managed.edisyl-voyageobject{2 keys}

No description in values.yaml.

secrets.managed.edisyl-voyage.keysarray["VOYAGE_API_KEY"]

api only: the direct Voyage reranker. Optional — mono boots without it and only a rerank call fails. On external-secrets, set `keys: []` to leave it out of the store; the Secret then does not render and api mounts it as optional.

secrets.managed.edisyl-slackobject{2 keys}

No description in values.yaml.

secrets.managed.edisyl-slack.keysarray["SLACK_BUG_REPORTS_BOT_TOKEN"]

api only: POST /v1/bug-reports relays an internal bug report into the engineering Slack channel with this bot token (chat:write, files:write, users:read.email). Absent or blank, that one route answers 503 and nothing else in the image cares — mono declares it optional, so the api still boots.

secrets.managed.edisyl-perplexityobject{2 keys}

No description in values.yaml.

secrets.managed.edisyl-perplexity.keysarray["PERPLEXITY_API_KEY"]

api only: the search_web tool calls Perplexity's Agent API directly with this key. Absent or blank, the api does not register search_web and nothing else in the image cares — mono declares it optional, so the api still boots.

secrets.managed.edisyl-typesafeobject{2 keys}

No description in values.yaml.

secrets.managed.edisyl-typesafe.keysarray["TYPESAFE_API_KEY","TYPESAFE_BASE_URL"]

api only: the known stage's rewording rung asks TypeSafe's hosted JEV which retained wording a missed question rewords. Key and base URL both or neither. Absent or blank, the api does not build the rung and every rewording goes to the reader. mono declares them optional, so the api still boots. Outside APP_ENV=local the api refuses a base URL that is not https:, since the key rides every call as a bearer token.

secrets.managed.edisyl-githubobject{2 keys}

No description in values.yaml.

secrets.managed.edisyl-github.keysarray["GITHUB_APP_PRIVATE_KEY"]

api only: the GitHub App identity the api signs installation tokens with. Id and key both or neither — the api builds its GitHub credentials from the pair and holds none when either is blank, so every GitHub path then answers github_not_configured: creating or testing a github connection, and every read a pack makes against GitHub. mono declares them optional, so the api still boots without them. An installation is bound on the create path, which asks the App which installations it holds. A deployment without these credentials cannot bind one at all, so a pack bundle naming a github connection has no way to be completed there. The App's id is public and is config.githubAppId; only its private key is here.

secrets.managed.edisyl-latticeobject{2 keys}

No description in values.yaml.

secrets.managed.edisyl-object-storageobject{2 keys}

No description in values.yaml.

secrets.managed.edisyl-object-storage.keysarray[2 items]

The object store's credentials, and nothing else: provider, bucket, region and URL are config (config.objectStorage*) and render into edisyl-config. Left blank with bundled MinIO, they are filled by templates/secrets.yaml with the SAME generated root credentials MinIO itself enforces. On AWS S3, leave them blank to use a pod IAM role instead.

secrets.managed.edisyl-query-results-s3object{2 keys}

No description in values.yaml.

secrets.managed.edisyl-query-results-s3.keysarray["AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY"]

AWS_ACCESS_KEY_ID/SECRET are left blank here on purpose: templates/ secrets.yaml fills them in with the SAME generated rootUser/ rootPassword as edisyl-minio-root (below). An operator pointing this at an external S3-compatible store instead sets these two explicitly. These two are the ONLY keys here. mono#1462 removed QUERY_RESULTS_S3_BUCKET/QUERY_RESULTS_S3_REGION from api's env schema: query results now live under a `query-results/` prefix in the tier's own OBJECT_STORAGE_BUCKET, so there is no second bucket to name. The pair below stays because the AWS SDK reads it out of the process environment as one step of the ambient credential chain, which is how S3ResultStorage and the shared object-storage provider now both resolve. STILL BROKEN AGAINST BUNDLED MinIO, and still not fixable in this chart. mono's S3ResultStorage (apps/api/src/services/queries/s3-result-storage.ts) constructs `new S3Client({ region })` with no endpoint and no forcePathStyle, so these credentials are presented to REAL AWS and every query-result write fails on InvalidAccessKeyId. Env vars cannot rescue it: AWS_ENDPOINT_URL_S3 would reach the host but leaves virtual-host addressing, and `<bucket>.edisyl-minio.<ns>.svc` does not resolve. The fix is in mono (endpoint + forcePathStyle on that client).

secrets.managed.edisyl-cloudflareobject{2 keys}

No description in values.yaml.

secrets.managed.edisyl-rudderstackobject{2 keys}

No description in values.yaml.

secrets.managed.edisyl-minio-rootobject{"keys":["rootUser","rootPassword"],"data":{}}

Bundled MinIO's root credentials. Generated (never a literal default), and reused verbatim — never regenerated independently — as edisyl-query-results-s3's AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY below, so the app can actually authenticate against the bundled bucket with MinIO's own root user.

secrets.literalobject{}

Plain Secrets rendered as-is regardless of provider. Shape: <name>: { data: {k: v} }

Probe defaults (overridable per app)

probeDefaultsobject{2 keys}

Promoted from the template so envs/services can tune without editing helpers. A service overrides by setting the matching keys under its probe:. No initContainer mechanism here (Task 13 removed it along with api's own prisma-migrate initContainer): migrations run in exactly one place, the edisyl-migrate pre-install/pre-upgrade bootstrap hook. Re-adding a per-app initContainer would resurrect the exact race this task fixed.

probeDefaults.readinessobject{"initialDelaySeconds":5,"periodSeconds":10}

No description in values.yaml.

probeDefaults.readiness.initialDelaySecondsnumber5

No description in values.yaml.

probeDefaults.readiness.periodSecondsnumber10

No description in values.yaml.

probeDefaults.livenessobject{"initialDelaySeconds":30,"periodSeconds":30}

No description in values.yaml.

probeDefaults.liveness.initialDelaySecondsnumber30

No description in values.yaml.

probeDefaults.liveness.periodSecondsnumber30

No description in values.yaml.

First-party apps (edisyl.<service>)

All 5 first-party apps (api, auth, edisyl, worker, lattice) share the schema below, shown once as `edisyl.<app>`. Set a key on one app to override it for that app only.

edisylobject{5 keys}

Each entry → Deployment (+ Service/Ingress/PDB/SA as configured), rendered by the shared "edisyl.app" helper via templates/apps/<name>.yaml. A `tmp` emptyDir is always mounted (restricted PSS); extraVolumes/Mounts add more. Per-service probe overrides: probe.readiness.* / probe.liveness.* / timeoutSeconds. SCHEDULING AND SCALING (0.9.0) — two per-service knobs, both optional: spreadPods (default TRUE, no key needed) Renders a soft topologySpreadConstraint over kubernetes.io/hostname so a service's replicas prefer different nodes. On before you ask for it, because the alternative was the observed default: every replica of every user-facing service on ONE node, where `replicas: 2` bought nothing against node loss. ScheduleAnyway, so it can never make a pod unschedulable. Set false to opt out, or set topologySpreadConstraints to replace it wholesale. autoscaling (default OFF) A CPU-target HPA. When enabled the Deployment omits `replicas` so the HPA owns pod count — see the helper for why that matters under Argo CD. maxReplicas is REQUIRED when enabled: an unbounded HPA can exhaust the cluster. Read the sizing trap in the helper before picking a target — utilization is measured against requests.cpu, so a single-threaded service with requests.cpu "2" tops out near 50% and a 70% target never fires. autoscaling: enabled: true minReplicas: 2 maxReplicas: 6 targetCPUUtilizationPercentage: 60 rollout (default OFF; 0.19.0, PLT-59) Renders the service as an Argo Rollout instead of a Deployment. The pod template, selector, labels, PDB and sync-wave are untouched; only the kind, the strategy and — when autoscaling is on — the HPA's target move, and they move together (an HPA still aimed at the Deployment would fight the Rollout controller over a workload it had scaled to zero). strategy is REQUIRED when enabled and is passed through verbatim. It is a Rollout strategy (canary or blueGreen), so it never collides with the Deployment-only `strategy:` key, which is ignored on this path. The chart ships no default steps: weights, pauses, traffic routing and analysis are an environment's decision, not a chart's. Needs the Argo Rollouts CRDs and controller in the cluster. Off, nothing Rollout-shaped renders and no CRD is required — which is why a self-host install never sees this. progressDeadlineAbort (default TRUE; 0.20.0) is what turns a failed update into a rollback: when the canary has not progressed within progressDeadlineSeconds (default 600), the controller aborts and returns traffic to the stable ReplicaSet on its own. Set false and a bad image leaves the Rollout Degraded with stable still serving and a human expected to press abort — safe, but it is exactly the manual-cleanup posture this feature replaces. rollout: enabled: true progressDeadlineSeconds: 600 # optional progressDeadlineAbort: true # optional; the default strategy: canary: steps: - setWeight: 20 - pause: { duration: 5m } Needs a metrics.k8s.io source (metrics-server) in the cluster; without one the HPA reports <unknown> and never scales.

edisyl.apiobject{16 keys}

No description in values.yaml.

edisyl.<app>.enabledbooleantrue

No description in values.yaml.

edisyl.<app>.replicasnumbervaries by app

No description in values.yaml.

edisyl.<app>.imageobjectvaries by app

No description in values.yaml.

edisyl.<app>.image.namestringvaries by app

No description in values.yaml.

edisyl.<app>.image.tagstringvaries by app

No description in values.yaml.

edisyl.<app>.portnumbervaries by app

No command override → use the image's own ENTRYPOINT/CMD. Current images are node-based (`node --import ./dist/instrumentation.js dist/index.js`); don't hardcode a runtime (`bun`) here — it drifts when the image changes.

edisyl.<app>.serviceAccountstringvaries by app

No description in values.yaml.

edisyl.<app>.needsDatabasebooleantrue

Matches aws/ecs.tf's infra_routed_secrets: only api and worker get a live DB credential. edisyl/lattice are internet-facing and must not carry one in their env even inertly — see edisyl.app in _helpers.tpl.

edisyl.<app>.readOnlyRootFilesystembooleantrue

No description in values.yaml.

edisyl.<app>.podAnnotationsobject{"prometheus.io/scrape":"false"}

No description in values.yaml.

edisyl.<app>.podAnnotations.prometheus.io/scrapestringfalse

No description in values.yaml.

edisyl.<app>.probeobjectvaries by app

No description in values.yaml.

edisyl.<app>.probe.kindstringvaries by app

No description in values.yaml.

edisyl.<app>.probe.pathstring/health

No description in values.yaml.

edisyl.<app>.probe.timeoutSecondsnumber3

No description in values.yaml.

edisyl.<app>.resourcesobjectvaries by app

No description in values.yaml.

edisyl.<app>.resources.requestsobjectvaries by app

No description in values.yaml.

edisyl.<app>.resources.requests.cpustringvaries by app

No description in values.yaml.

edisyl.<app>.resources.requests.memorystringvaries by app

No description in values.yaml.

edisyl.<app>.resources.limitsobjectvaries by app

No description in values.yaml.

edisyl.<app>.resources.limits.memorystringvaries by app

No description in values.yaml.

edisyl.<app>.envFromobjectvaries by app

No initContainer: migrations run in exactly one place — the edisyl-migrate pre-install/pre-upgrade bootstrap hook (bootstrap.yaml). An initContainer here would race the hook to migrate the same database.

edisyl.<app>.envFrom.configMapstringedisyl-config

No description in values.yaml.

edisyl.<app>.envFrom.secretsarrayvaries by app

No description in values.yaml.

edisyl.<app>.serviceobjectvaries by app

No description in values.yaml.

edisyl.<app>.service.enabledbooleantrue

No description in values.yaml.

edisyl.<app>.service.portnumbervaries by app

No description in values.yaml.

edisyl.<app>.ingressobjectvaries by app

No description in values.yaml.

edisyl.<app>.ingress.enabledbooleanvaries by app

No description in values.yaml.

edisyl.<app>.ingress.hoststring""

No description in values.yaml.

edisyl.<app>.ingress.tlsSecretstringvaries by app

No description in values.yaml.

edisyl.<app>.ingress.annotationsobjectvaries by app

No description in values.yaml.

edisyl.<app>.ingress.annotations.cert-manager.io/cluster-issuerstringletsencrypt-prod

No description in values.yaml.

edisyl.<app>.ingress.annotations.kubernetes.io/tls-acmestringtrue

No description in values.yaml.

edisyl.<app>.ingress.annotations.nginx.ingress.kubernetes.io/proxy-body-sizestring100m

No description in values.yaml.

edisyl.<app>.ingress.annotations.nginx.ingress.kubernetes.io/proxy-read-timeoutstringvaries by app

No description in values.yaml.

edisyl.<app>.ingress.annotations.nginx.ingress.kubernetes.io/proxy-send-timeoutstring600

No description in values.yaml.

edisyl.<app>.ingress.annotations.nginx.ingress.kubernetes.io/proxy-bufferingstringoff

/mcp always answers over an unbounded chunked text/event-stream with no Content-Length (api's StreamableHTTPTransport never sets enableJsonResponse) — a long-lived response an ingress should forward as it arrives rather than buffer. Both forms ship since a consumer's controller flavor (community ingress-nginx vs. F5's nginx.org) is unknown to this chart; each ignores the other's key.

edisyl.<app>.ingress.annotations.nginx.org/proxy-bufferingstringfalse

No description in values.yaml.

edisyl.<app>.pdbobjectvaries by app

No description in values.yaml.

edisyl.<app>.pdb.enabledbooleanvaries by app

No description in values.yaml.

edisyl.<app>.pdb.minAvailablenumber1

No description in values.yaml.

edisyl.<app>.autoscalingobject{4 keys}

OFF by default; the IDP enables it per environment. maxReplicas is required when enabled. Target 60 (not the usual 70) because api's event loop is single-threaded: against requests.cpu "2" the utilization ceiling is ~50-75%, so a higher target would never fire.

edisyl.<app>.autoscaling.enabledbooleanfalse

No description in values.yaml.

edisyl.<app>.autoscaling.minReplicasnumber2

No description in values.yaml.

edisyl.<app>.autoscaling.maxReplicasnumber6

No description in values.yaml.

edisyl.<app>.autoscaling.targetCPUUtilizationPercentagenumber60

No description in values.yaml.

edisyl.<app>.rolloutobject{"enabled":false}

OFF by default (0.19.0, PLT-59); the IDP enables it per environment, with that environment's strategy. Spelled out on this service in particular because it is the one that autoscales, and keeping the HPA's target in step with the workload kind is the whole point of the gate — see the rollout note in the schema comment above.

edisyl.<app>.rollout.enabledbooleanfalse

No description in values.yaml.

edisyl.authobject{15 keys}

The authentication service (GoTrue) — the only auth service the fleet uses. api and edisyl find it through AUTH_PUBLIC_URL and AUTH_INTERNAL_URL, which edisyl-config derives from this service's own host and Service, and verify against the keys in edisyl-auth-client and edisyl-auth-admin. Turning it off leaves nothing to sign anyone in, so the chart refuses.

edisyl.auth.extraRedirectUrlsarray[]

Redirect targets for apps served from this release's namespace that this chart does NOT deploy. Appended verbatim to GOTRUE_URI_ALLOW_LIST, which is chart-owned and therefore unreachable through edisyl.auth.env — this list is the only way in. Entries must be absolute and end in `**` (GoTrue globs the whole redirect URL, query string included); the template refuses anything else rather than let a sign-in fail silently. extraRedirectUrls: - https://admin.example.com/auth/callback**

edisyl.auth.samlobject{"enabled":true}

Its own instance, since GoTrue owns the whole database and migrates it on boot. Named once here and read by both the Deployment and the bootstrap Job: two settings could point them at different databases, which is the boots-and-serves-errors state auth-bootstrap.yaml documents. The secret carries a complete, correctly-encoded URI. It is injected by the template rather than listed in `env` below, because Helm replaces that list wholesale — an environment overriding one variable would otherwise drop the database connection entirely. WHERE THE SECRET COMES FROM is `create` (below the two names). Unset — the default — it follows cnpg.enabled: with the bundled cluster running, this chart provisions everything itself (templates/auth-db.yaml: a mono_auth login role via a CNPG managed role, a declarative Database it owns, and this connection Secret); without one, the Secret must arrive from outside — the IDP posture, a Postgres claim's connection secret. Set `create: false` to bring your own database even alongside bundled CNPG; `create: true` without CNPG refuses to render (platform-guards). SAML through deploymentIdp is the only sign-in path: email, phone and anonymous sign-in are pinned off in the floor and no social provider is configured, so turning SAML off leaves no way in and the chart refuses.

edisyl.auth.saml.enabledbooleantrue

No description in values.yaml.

edisyl.auth.databaseobject{"secretName":"mono-auth-db","secretKey":"uri"}

No description in values.yaml.

edisyl.auth.database.secretNamestringmono-auth-db

No description in values.yaml.

edisyl.auth.database.secretKeystringuri

No description in values.yaml.

edisyl.auth.ingress.pathstring/auth/v1/

The client SDK builds every route as new URL("auth/v1", baseUrl) while GoTrue serves at root. The path routes; the annotation strips. Both are required — the path alone would forward /auth/v1/token unchanged and every call would 404. The path ends at a slash, and has to. The controller emits `rewrite ^<path>(.*)$ <rewrite>$1 break;`, so the capture holds everything after the path verbatim and the separator must be contributed exactly once — by the path or by the rewrite target, never both and never neither. `/auth/v1` against `rewrite=/` doubles it into `//token`; `/auth/v1/` against `rewrite=/api` elides it into `/apitoken`. Either way the request reaches the right pod and 404s on every route, so the service, the ingress and the rewrite all read as correct while nothing signs in. Setting this path is the whole declaration: the template derives BOTH controller vendors' rewrites from it — F5's nginx.org/rewrites against this literal prefix, and the community controller's use-regex plus rewrite-target against a second, capture-carrying path. Neither vendor is named here, because a chart cannot tell which one is installed: both are conventionally class "nginx". This used to carry nginx.org/rewrites alone, which made an F5 NGINX controller an unstated requirement of this service. On the community controller that key is silently inert, so the prefix was forwarded intact and every auth route 404d — with the pod Ready, the Service endpointed and the Ingress admitted.

edisyl.auth.envarray[6 items]

No description in values.yaml.

edisyl.edisylobject{12 keys}

No description in values.yaml.

edisyl.workerobject{15 keys}

No description in values.yaml.

edisyl.worker.serviceAccountAnnotationsobject{}

Customer-owned cloud identity binding. EKS IRSA uses eks.amazonaws.com/role-arn here; EKS Pod Identity leaves this empty and creates its association outside Helm.

edisyl.worker.envarray[1 items]

No description in values.yaml.

edisyl.latticeobject{14 keys}

No description in values.yaml.

edisyl.lattice.argsarray[]

distroless Go entrypoint

edisyl.lattice.envarray[4 items]

No description in values.yaml.

Non-secret config (edisyl-config)

configobject{34 keys}

No description in values.yaml.

config.appEnvstringproduction

Must be development | staging | production — the frontend throws otherwise.

config.nodeEnvstringproduction

No description in values.yaml.

config.logLevelstringinfo

No description in values.yaml.

config.webPublicUrlstring""

Derived from config.domain; a default here would be our host, not theirs.

config.domainstring""

Empty on purpose. Every public URL derives from this, so a default would hand an operator who never set one a working-looking render pointed at somebody else's domain. Absent, the required-key failures below name what to set.

config.apiPublicUrlstring""

In-cluster URLs use SHORT service names (no namespace segment): every consumer pod shares the release namespace, so DNS search paths resolve them in any install namespace — no per-namespace overrides to forget. No safe default: see the comment beside API_PUBLIC_URL in templates/configmap.yaml. Set per environment (e.g. "https://api.stg.edisyl.com").

config.defaultDatasourceIdstring00000000-0000-0000-0000-000000000000

No description in values.yaml.

config.databasePoolSizenumber25

Connections per api pod. api requires it and carries no fallback, so this default is what an install gets when it names nothing; a deployment sizes it against its own instance and replica count.

config.inngestDevstring0

No description in values.yaml.

config.inngestBaseUrlstringhttp://inngest:8288

No description in values.yaml.

config.otelobject{3 keys}

Empty = telemetry off. Set serviceName + endpoint to enable OTLP export.

config.otel.serviceNamestring""

No description in values.yaml.

config.otel.endpointstring""

No description in values.yaml.

config.otel.resourceAttributesstring""

No description in values.yaml.

config.sentryEnabledbooleanfalse

When true, each enabled app's DSN below is required. Blank DSNs are left out of edisyl-config rather than rendered empty, as is every other blank optional config key below.

config.sentryDsnApistring""

No description in values.yaml.

config.sentryDsnWorkerstring""

No description in values.yaml.

config.sentryDsnEdisylstring""

No description in values.yaml.

config.rudderstackEnabledbooleanfalse

No description in values.yaml.

config.rudderstackUrlstring""

The RudderStack data plane's URL. Required when rudderstackEnabled; the write key is secrets.managed.edisyl-rudderstack.

config.deploymentTenancystringdedicated

Whether this deployment serves one organization or many: dedicated (one organization's own install) or shared (Edisyl SaaS). dedicated lets every organization owner set and replace the LLM gateway's provider API keys, bind its models and see deployment health, and serves the organization model list. Only shared serves the pack catalogue. Under shared or blank, provider administration and health stay with the deployment owner and the model list is refused; blank renders nothing and also withholds the catalogue. An install that runs several organizations sets shared or blank.

config.objectStorageProviderstring""

Everything below is config, not a secret: it renders into edisyl-config, and the store behind secrets.managed holds only credentials. Where the chart runs the service itself it derives the address, and the value here is ignored. Object storage. Provider is mono's enum, s3 | s3compatible — `s3` means real AWS, where the URL must stay empty; s3compatible (MinIO, Ceph, R2's S3 endpoint) needs the URL. The bucket is always required; with bundled MinIO it must be one minio.buckets creates. With bundled MinIO the URL is the in-cluster Service, the provider defaults to s3compatible and path-style addressing to true. The credentials are secrets.managed.edisyl-object-storage. objectStorageUrl renders as OBJECT_STORAGE_URL.

config.objectStorageBucketstring""

No description in values.yaml.

config.objectStorageRegionstring""

No description in values.yaml.

config.objectStoragePublicUrlstring""

No description in values.yaml.

config.objectStorageForcePathStylestring""

No description in values.yaml.

config.objectStorageUrlstring""

No description in values.yaml.

config.redisHoststring""

External Redis. Required when redis.enabled is false (redisTls optional); derived from the bundled server otherwise. The password is secrets.managed.edisyl-redis.

config.redisPortstring""

No description in values.yaml.

config.redisUserstring""

No description in values.yaml.

config.redisTlsstring""

No description in values.yaml.

config.redisSentinelsstring""

External Redis behind Sentinel, with no proxy in front. Set both and api asks the Sentinels which node is the primary and follows it through a failover; redisHost and redisPort are then optional and ignored. redisSentinels is comma-separated host:port pairs (redis-sentinel-0.redis:26379,redis-sentinel-1.redis:26379), and redisSentinelMaster is the master set name the Sentinels monitor. api gives the Sentinels the same user, password and TLS setting as the data nodes. lattice reads its own URL: give LATTICE_REDIS_URL the go-redis Sentinel form (redis://user:pass@sentinel-0:26379?master_name=<set>&addr= sentinel-1:26379), whose credentials reach both the Sentinels and the data nodes. Inngest's client dials Sentinels with no password and no TLS, so its Sentinel form (redis://user:pass@sentinel-0:26379?master_set=<set> &addr=sentinel-1:26379) only works with open Sentinels; otherwise point INNGEST_REDIS_URI at one address that follows the primary. Needs mono images that read REDIS_SENTINELS; older api images ignore it.

config.redisSentinelMasterstring""

No description in values.yaml.

config.emailEnabledbooleanfalse

Outbound email. emailFrom is required in every install — the api reads the sender at boot — and renders as both EMAIL_FROM and SES_FROM_EMAIL. Blank optional keys in this block are left out of edisyl-config, so the apps apply their own defaults.

config.emailFromstring""

No description in values.yaml.

config.githubAppIdstring""

The GitHub App's id (Edisyl's hosted service). Its private key is secrets.managed.edisyl-github.

config.usageExportobject{6 keys}

Self-hosted only. Hosted environments leave this disabled, so the worker never assumes a client role.

config.usageExport.enabledbooleanfalse

No description in values.yaml.

config.usageExport.clientKeystring""

No description in values.yaml.

config.usageExport.bucketstring""

No description in values.yaml.

config.usageExport.regionstringus-east-1

No description in values.yaml.

config.usageExport.assumeRoleArnstring""

No description in values.yaml.

config.usageExport.externalIdstring""

No description in values.yaml.

Database source

databaseobject{2 keys}

How api/worker and the bootstrap hook Jobs obtain DATABASE_URL. Deployment postures diverge HERE, in one value, rather than in per-template conditionals: cnpg — bundled CNPG below (self-host default). Requires cnpg.enabled: true; the render fails otherwise. existing-secret — a connection Secret that already exists in the release namespace (the IDP posture: a platform Postgres claim writes it, key `uri` — the same key CNPG uses). The credential never passes through values. managed — DATABASE_URL from the edisyl-database managed Secret (set its value under secrets.managed, or deliver it via secrets.provider: external-secrets). The render fails if it would be blank.

database.sourcestringmanaged

managed, not cnpg. The bundled database is OFF by default (see cnpg.enabled below), so the default posture is a Postgres you already run and name here. managed — DATABASE_URL supplied in secrets.managed.edisyl-database.data, i.e. your own gitignored values file. The self-host default. existing-secret — a connection Secret already in the namespace, e.g. what a platform Postgres claim writes. The IDP posture. cnpg — the bundled cluster, which you must also enable. Requirements on an external Postgres, none of which the chart can check: Postgres 16, the `vector` extension AVAILABLE (mono's first migration runs CREATE EXTENSION vector), and a second database and role for the auth service, since only the bundled cluster can create those itself.

database.existingSecretobject{"name":"","key":"uri"}

No description in values.yaml.

database.existingSecret.namestring""

No description in values.yaml.

database.existingSecret.keystringuri

No description in values.yaml.

CNPG Cluster

cnpgobject{13 keys}

No description in values.yaml.

cnpg.enabledbooleanfalse

OFF by default. A chart that ships a database unless told otherwise hands an operator a production datastore they never chose, on the cluster's default StorageClass, holding the only copy of their data — and the three-instance default below provisions 348Gi to do it. Bringing your own Postgres is the expected posture; the bundled cluster is for a local or evaluation install. Enabling this is not the only step: set database.source: cnpg as well. The two are separate because the cluster existing and the apps being pointed at it are separate decisions, and the chart refuses the combinations that disagree.

cnpg.namestringedisyl-pg

No description in values.yaml.

cnpg.appSecretNamestringedisyl-pg-app

CNPG generates a `<name>-app` Secret (username/password/dbname/host/port/ uri/jdbc-uri) at cluster-bootstrap time. edisyl.app injects DATABASE_URL from its `uri` key — see _helpers.tpl "edisyl.app".

cnpg.instancesnumber3

No description in values.yaml.

cnpg.imageNamestringghcr.io/cloudnative-pg/postgresql:16-standard-bookworm

Must be a CNPG operand image WITH pgvector: mono's first migration runs CREATE EXTENSION vector, and CNPG starts happily on an image that lacks it, so the failure lands minutes later in the migrate hook rather than here. CloudNativePG's own `standard` flavor carries pgvector (plus pgaudit and pg-failover-slots) and is public and multi-arch — nothing to build or push. The `minimal` flavor does NOT; neither does the plain `:16` tag. What this flavor drops is the barman-cloud binaries, which only the legacy in-Cluster `barmanObjectStore` backup path uses — this chart configures none. If you need extensions beyond these, build your own from chart/images/cnpg-pgvector.Dockerfile and point this at it. UPGRADING an install made before chart 0.26.0 (when this defaulted to a self-built bullseye image): read the README's "Upgrading an existing cluster onto this default" — a glibc change can reorder non-C collations.

cnpg.storageClassstring""

Empty = use the cluster's default StorageClass. Never default this to a legacy-cluster-specific class name (e.g. "ceph-block") — a customer cluster doesn't have it, and the PVCs stay Pending forever. Set this explicitly if your cluster's default class isn't what you want CNPG's volumes on.

cnpg.storageSizestring100Gi

No description in values.yaml.

cnpg.walStorageSizestring16Gi

No description in values.yaml.

cnpg.enableSuperuserAccessbooleanfalse

No chart component reads the CNPG-generated superuser credential. It existed for the bundled Supabase prep-db hook, which granted roles and created the auth schema as superuser; that hook is gone as of 0.28.0, so CNPG no longer generates a cluster superuser password Secret at all.

cnpg.managedRolesarray[]

All six roles this used to declare (anon, authenticated, service_role, authenticator, supabase_auth_admin, supabase_admin) were bundled-Supabase constructs. mono-auth's own login role is appended by cnpg.yaml rather than listed here, so this is empty rather than removed.

cnpg.extraDatabasesarray["auth","inngest"]

No description in values.yaml.

cnpg.extensionsarray["vector"]

Extensions created in the app DB at init (as superuser) — pgvector isn't a "trusted" extension, so the app user can't CREATE it during migrations.

cnpg.parametersobject{3 keys}

No description in values.yaml.

cnpg.parameters.max_connectionsstring200

No description in values.yaml.

cnpg.parameters.shared_buffersstring2GB

No description in values.yaml.

cnpg.parameters.effective_cache_sizestring6GB

No description in values.yaml.

Database bootstrap

bootstrapobject{5 keys}

enabled flags drive Chart.yaml conditions. Database bootstrap, mirroring shared/docker-compose.yml's migrate/seed.

bootstrap.enabledbooleantrue

No description in values.yaml.

bootstrap.seedbooleantrue

db:seed on/off. Self-host default ON (helm post-install = first install only). Argo CD reruns hooks on EVERY sync — no first-install-only phase exists — and db:seed is not rerun-safe by contract, so IDP deployments set this false. Owner seeding (adminEmails) is unaffected: it lives in sync-system-content.

bootstrap.backoffLimitnumber3

No description in values.yaml.

bootstrap.adminEmailsarray[]

Emails made OWNERS of the system org (idempotent; pre-creates the user row so first sign-in links by email). Leave empty and no owner exists — nothing owner-gated can be created. Set at least one for a usable install.

bootstrap.resourcesobject{2 keys}

No description in values.yaml.

bootstrap.resources.requestsobject{"cpu":"200m","memory":"512Mi"}

No description in values.yaml.

bootstrap.resources.requests.cpustring200m

No description in values.yaml.

bootstrap.resources.requests.memorystring512Mi

No description in values.yaml.

bootstrap.resources.limitsobject{"memory":"1Gi"}

No description in values.yaml.

bootstrap.resources.limits.memorystring1Gi

No description in values.yaml.

Deployment owner IdP (SAML sign-in)

deploymentIdpobject{"metadataXml":"","metadataUrl":"","domains":[]}

The SAML identity provider this deployment's owners sign in through. Required when SAML is enabled — and edisyl.auth.saml.enabled defaults to true, so that is every install unless SAML is turned off. It is the only sign-in the api trusts for deployment owners. The edisyl-idp-reconcile Job registers it with the auth service whenever the Job's spec changes (this block, the api image, the chart version, or the Job's labels, resources and wiring), updating the existing registration in place so its provider id survives, and logs that id. The Job also records that provider id in the database, and the api trusts owner sessions only from the provider recorded there — so declaring the IdP here is the whole setup, with no id to copy back into values.

deploymentIdp.metadataXmlstring""

Exactly one of these. Google Workspace publishes XML only. An HTTPS metadataUrl lets the auth service pick up an IdP's overlapping certificate rotation without a redeploy; a hard cutover still needs one. Whoever serves that URL can change this IdP's signing certificates at any time, so use a URL only when it is the IdP vendor's own.

deploymentIdp.metadataUrlstring""

No description in values.yaml.

deploymentIdp.domainsarray[]

Email domains that sign in through this IdP — exactly this list. A domain removed here is released from the provider on the next deploy, so when adopting an existing registration list all of its domains. Everyone the IdP signs in on these domains joins the deployment owner's organization.

Optional infra (conditional subchart deps + our glue)

These are upstream subcharts. Only the switches and glue this chart owns are documented here — for the rest, see each project’s own values documentation.

inngestobject{10 keys}

Self-hosted Inngest. Runs `inngest start` (durable single-node), which signs and validates traffic exactly like Inngest Cloud — hence INNGEST_DEV=0 in the ConfigMap and the shared keys from edisyl-inngest. Chart 0.3.1 is the latest published inngest-helm; only the image is bumped. DURABILITY, stated plainly: - Queue state and in-flight/live run state DO survive an Inngest pod restart: INNGEST_REDIS_URI (below, via extraEnv/secretKeyRef) points it at our bundled edisyl-redis, reusing the SAME generated password as REDIS_PASSWORD and LATTICE_REDIS_URL (never a second, divergent one). - Function RUN HISTORY (past events, runs, step results) does NOT survive a restart. INNGEST_POSTGRES_URI is deliberately left unset: this subchart hardcodes its data volume to an emptyDir with no PVC option, so without an external Postgres it falls back to SQLite on that same ephemeral storage — see INNGEST_SQLITE_DIR in extraEnv below, without which that fallback does not merely lose history, it fails to start at all. We do not compose one from CNPG's edisyl-pg-app ourselves — its `uri` key percent-encodes the password, which is direct evidence a CNPG-generated password CAN contain URI-reserved characters (@ : / ? # [ ] %), and a hand-built $(VAR) string substitution has no escaping, so it would corrupt the URI and fail at connect time in a way that looks like a wrong password. The clean fix (a CNPG managed role for `inngest` with its own generated password) needs bootstrap/managed-roles ordering this chart doesn't solve yet: postInitApplicationSQL runs at cluster bootstrap, before managed roles reconcile, so `CREATE DATABASE inngest OWNER inngest` would fail against a role that doesn't exist yet. An operator who needs durable run history must set inngest.inngest.postgres.uri themselves. NOTE: this subchart hardcodes its resource names to `inngest` — there is no nameOverride/fullnameOverride. The Service is therefore `inngest`, which is what config.inngestBaseUrl must point at.

inngest.enabledbooleantrue

No description in values.yaml.

inngest.imageobject{3 keys}

No description in values.yaml.

inngest.namespaceobject{"create":false,"name":"edisyl"}

The subchart's OWN namespace.name (default "inngest") and namespace.create (default true) are independent of this chart's namespace (every first-party object follows .Release.Namespace — see edisyl.ns in _helpers.tpl) — left alone, it creates a SECOND Namespace object and puts every resource there instead of alongside the rest of this release — so `inngest` short-name DNS (config.inngestBaseUrl) would resolve nowhere. `name: edisyl` matches this chart's default install namespace; if you `helm install -n <other-namespace>`, override this ONE value to match (the only namespace-coupled value left — the subchart offers no way around it).

inngest.commonLabelsobject{}

The subchart renders its own resources and never sees the top-level commonLabels. Repeat that map here to label them too — and on the platform this MUST carry edisyl.com/team matching platform.team, or tenant admission rejects the subchart's workloads (templates/platform-guards.yaml).

inngest.serviceobject{"type":"ClusterIP","port":8288,"connPort":8289}

No description in values.yaml.

inngest.ingressobject{"enabled":false}

No description in values.yaml.

inngest.postgresqlobject{"enabled":false}

Disable the subchart's OWN internal Postgres/Redis. These two flags default to TRUE upstream — leaving them alone silently starts a SECOND Postgres and a SECOND Redis alongside CNPG and our bundled Redis (verified: `inngest-postgresql` and `inngest-redis` Services appear). See the DURABILITY note above for what actually backs Inngest instead.

inngest.redisobject{2 keys}

enabled:false here is the chart default; consumers may turn it on (mono does). The sizing below only applies when they do.

inngest.resourcesobject{2 keys}

The upstream chart sets NO memory limit on inngest (limits: {}). Tenant namespaces on the platform reject that (bound-tenant-memory, PLT-182).

inngest.inngestobject{5 keys}

No description in values.yaml.

inngest.inngest.sdkUrlarray["http://worker:6002/inngest"]

Inngest syncs functions FROM the worker and re-syncs every 60s to pick up redeploys. Must be in-cluster DNS, not a public hostname. SINGULAR key.

inngest.inngest.extraEnvarray[4 items]

eventKey/signingKey/redis.uri are plain-string values upstream — a chart-GENERATED value (a password, a URL embedding one) can never go there, since values are static data, not templates. Inject all three from chart-rendered Secrets via extraEnv instead.

minioobject{13 keys}

Bundled object storage, wired end to end when enabled: edisyl-config gets this MinIO's in-cluster URL, path-style addressing and, unless config.objectStorageProvider says otherwise, provider s3compatible; and secrets.managed.edisyl-object-storage gets the same generated root credentials MinIO itself enforces. config.objectStorageBucket still names the bucket, and must be one minio.buckets creates. `existingSecret` points at edisyl-minio-root (in secrets.managed, above) instead of letting the subchart generate its own — so THIS chart owns the value and can mirror it into edisyl-query-results-s3's AWS_ACCESS_KEY_ID/ AWS_SECRET_ACCESS_KEY. Both rootUser/rootPassword are chart-generated (never a literal default) via the same generate-once machinery as ENCRYPTION_KEY etc. — see templates/secrets.yaml's $minioRootUser/$minioRootPassword. For AWS S3 or another S3-compatible store instead, set enabled: false, configure config.objectStorage* and put the store's credentials (or none, for an AWS pod role) in secrets.managed.edisyl-object-storage. That is also the IDP posture (examples/idp.values.yaml): with this false, NOTHING below renders, and the credentials come from the platform store.

minio.enabledbooleanfalse

OFF by default. Same reasoning as cnpg above: an object store nobody asked for, holding the only copy of uploaded files. Point the chart at S3, R2 or your own S3-compatible store instead — see config.objectStorage*, which the chart REFUSES to leave half-set when this is off. Enable this for a local or evaluation install. Note before turning it on for anything real: MinIO's server images are AGPL-relicensed and upstream is archived, and this pins subchart 5.4.0.

minio.modestringstandalone

No description in values.yaml.

minio.replicasnumber1

No description in values.yaml.

minio.additionalLabelsobject{}

Same as inngest.commonLabels: the subchart's own labels knobs, because the top-level commonLabels stops at this chart's resources. Two of them, because the MinIO chart reads additionalLabels on its Deployment or StatefulSet metadata only and podLabels on the pod templates — set both to the same map or the pods go unlabelled. Same platform rule about edisyl.com/team, too.

minio.podLabelsobject{}

No description in values.yaml.

minio.existingSecretstringedisyl-minio-root

No description in values.yaml.

minio.fullnameOverridestringedisyl-minio

Pins every subchart resource to "edisyl-minio" regardless of the Helm release name. templates/configmap.yaml derives OBJECT_STORAGE_URL as "http://<this name>.<ns>.svc:9000"; WITHOUT this the subchart derives its Service name from the RELEASE name instead ("myrelease-minio"), so that endpoint resolves nowhere and every write fails — silently, since the apps start fine and only fail on first use. ⚠ MIGRATION — READ BEFORE UPGRADING an existing install whose release is NOT named "edisyl". This renames every MinIO resource, the PVC included. The old PVC is named in no manifest after the rename, and the subchart sets no `helm.sh/resource-policy: keep` on it, so HELM DELETES IT on upgrade — and if its StorageClass reclaims with Delete (the common default), the underlying volume and its contents go with it. This is data loss, not an orphaned volume to reclaim later. In most such installs the bucket is empty anyway: OBJECT_STORAGE_URL pointed at a name that never resolved, so nothing could write. The exception is real — an operator who noticed and overrode the endpoint to the release-derived name has a working MinIO with live data, and is exactly the person this rename hurts. If that is you: back the buckets up (`mc mirror`), or set `minio.fullnameOverride` to your CURRENT MinIO name — the derived URL follows it — which keeps your volume and still fixes the release-name coupling.

minio.bucketsarray[2 items]

Bucket names here MUST match what the apps ask for — config.objectStorageBucket. Nothing at runtime couples them: a rename on either side renders cleanly, starts healthy, and 404s on first write. templates/platform-guards.yaml fails the render instead. `query-results` is no longer asked for by any app — mono#1462 moved query results into OBJECT_STORAGE_BUCKET under a `query-results/` prefix. It stays created because existing installs hold data in it (purge: false) and dropping it from this list would not delete it anyway, only orphan it.

minio.persistenceobject{"enabled":true,"size":"50Gi"}

A real install must not disable this — bucket contents would not survive a pod restart otherwise.

minio.ingressobject{"enabled":false}

No description in values.yaml.

minio.consoleIngressobject{"enabled":false}

No description in values.yaml.

minio.postJobobject{1 keys}

Restricted-PSS hardening for the post-install bucket Job. The subchart ships NO securityContext on it at all, so in a namespace labelled `pod-security.kubernetes.io/enforce: restricted` admission rejects the Job outright — MinIO comes up healthy and the buckets simply never exist, which reads as "MinIO is broken" rather than "the Job was denied". postJob.securityContext is the POD level (the subchart templates it as `omit ... "enabled"`, so arbitrary keys pass straight through); makeBucketJob.containerSecurityContext is the mc container. readOnly root is safe here: mc writes only to /etc/minio/mc and /tmp, both emptyDirs the subchart already mounts. NOTE this covers the JOB only. The MinIO SERVER container still ships `containerSecurityContext.readOnlyRootFilesystem: false` upstream, so the namespace as a whole still cannot enforce `restricted` — see the README's Pod Security Standards section.

minio.makeBucketJobobject{2 keys}

No description in values.yaml.

Bundled Redis

redisobject{5 keys}

Bundled Redis. One redis:8 StatefulSet — see templates/redis.yaml for why no replicas knob exists. Set enabled: false and supply edisyl-redis's REDIS_* fields, LATTICE_REDIS_URL and INNGEST_REDIS_URI to use a managed Redis (the only HA option today).

redis.enabledbooleantrue

No description in values.yaml.

redis.imagestringredis:8

No description in values.yaml.

redis.storageClassstring""

No description in values.yaml.

redis.storageSizestring8Gi

No description in values.yaml.

redis.resourcesobject{2 keys}

No description in values.yaml.

redis.resources.requestsobject{"cpu":"200m","memory":"1Gi"}

No description in values.yaml.

redis.resources.requests.cpustring200m

No description in values.yaml.

redis.resources.requests.memorystring1Gi

No description in values.yaml.

redis.resources.limitsobject{"memory":"2Gi"}

No description in values.yaml.

redis.resources.limits.memorystring2Gi

No description in values.yaml.

Bifrost LLM gateway

bifrostobject{13 keys}

No description in values.yaml.

bifrost.enabledbooleantrue

ON by default, because every deployment infers through Bifrost. It runs the gateway, lets the api administer it through edisyl-bifrost's management credential, and sends inference to it: LLM_GATEWAY_URL is derived from this block (the Service here, or external.url below).

bifrost.externalobject{"url":""}

A gateway another deployment runs. Set `url` to its management address and set `enabled` false: the api gets BIFROST_MANAGEMENT_URL and the edisyl-bifrost admin credential, and no gateway pods, database or encryption key render. The two are exclusive — a deployment either runs its gateway or points at someone else's. Providers belong to the deployment that runs the gateway. Here the api gets BIFROST_PROVIDER_ADMIN=read: its administration page lists the gateway's providers and catalogue and binds this deployment's own model tiers, and refuses to save or remove a provider credential. With chart-managed Secrets, all three edisyl-bifrost keys must be supplied: they are the external gateway's own admin credential and one of its virtual keys, so generated values would never match it. Inference goes to this gateway too: LLM_GATEWAY_URL is this address plus /v1.

bifrost.external.urlstring""

No description in values.yaml.

bifrost.imagestringmaximhq/bifrost:v2.2.0@sha256:12d6bbab32b44e3bb88869c6f5ca9304b53a9c43cb55c57a29f851037bcc19e1

DIGEST-pinned, not a bare tag. This process holds every upstream provider credential the deployment has (encrypted at rest with the key below, which it receives in its environment), so the usual "a Docker Hub tag is conventionally immutable" is not good enough — the same standard the auth-prep-db hook's postgres image is held to. OCI index digest, so it stays multi-arch (linux/amd64 and linux/arm64, verified with `crane`). Pinned to v2.2.0 because that is the release whose Voyage embedding wire format was verified live against a real provider. Do not move it to a newer release without re-running that check.

bifrost.serviceobject{"port":8080}

No description in values.yaml.

bifrost.service.portnumber8080

No description in values.yaml.

bifrost.storageClassstring""

A StatefulSet with ONE replica, and no replicas value is exposed — deliberately, and for a different reason than bundled Redis. Bifrost's OSS build does not synchronize live state between processes that share a Postgres configuration store: provider configuration, budgets and rate-limit counters are held per-process and reconciled from the database only at startup. Two replicas are therefore two gateways that disagree about what a virtual key is allowed to spend. Upstream says so explicitly (https://docs.getbifrost.ai/deployment-guides/how-to/multinode). A StatefulSet is also what makes the UPGRADE safe: its rolling update terminates the old pod before creating the new one, where a Deployment's default surge would briefly run two. That brief overlap is exactly the unsupported state, so it must not be reachable by an ordinary `helm upgrade`. The cost is a short window with no gateway on every upgrade, and with inference routed here that window is an inference outage.

bifrost.storageSizestring8Gi

Holds the request/usage log database only — the configuration and the provider credentials live in Postgres. Sized for logs, not for config.

bifrost.resourcesobject{2 keys}

No description in values.yaml.

bifrost.resources.requestsobject{"cpu":"200m","memory":"1Gi"}

No description in values.yaml.

bifrost.resources.requests.cpustring200m

No description in values.yaml.

bifrost.resources.requests.memorystring1Gi

No description in values.yaml.

bifrost.resources.limitsobject{"memory":"2Gi"}

No description in values.yaml.

bifrost.resources.limits.memorystring2Gi

No description in values.yaml.

bifrost.logsobject{3 keys}

Request logs, in sqlite on the volume above. Each inference call writes one row: model, tokens, cost, latency and status. Bifrost's management UI reads them, and they are how a gateway is verified before traffic moves to it. `content` adds the request and response bodies to each row. Off by default: the bodies are most of the volume, and they are every prompt and answer the deployment sends, kept for the retention period. Turn it on to debug a specific request, then off again. Bifrost deletes rows older than `retentionDays` once a day; 1 is the minimum. Deleted rows free space inside the database file for new rows, but the file does not shrink. `enabled: false` stops writing logs; the retention still clears the rows already written. The chart owns these. When any of them changes, the gateway's next start overwrites its client settings with the chart's, including a change made in Bifrost's UI.

bifrost.logs.enabledbooleantrue

No description in values.yaml.

bifrost.logs.contentbooleanfalse

No description in values.yaml.

bifrost.logs.retentionDaysnumber30

No description in values.yaml.

bifrost.podAnnotationsobject{}

Extra annotations on the gateway pod, merged beside the chart's own checksum/config (which wins a collision). On a Karpenter fleet, `karpenter.sh/do-not-disrupt: "true"` keeps consolidation from moving the single replica, which costs a gateway outage and a volume reattach each time. The chart sets none, because not every fleet runs Karpenter.

bifrost.serviceAccountobject{"name":"edisyl-bifrost","annotations":{}}

The gateway runs as its own ServiceAccount, so a cloud workload identity can be bound to the gateway alone rather than to every pod in the namespace. EKS Pod Identity binds by namespace and ServiceAccount name and needs no annotation; IRSA and GKE Workload Identity bind through `annotations`. With that identity in place, a Bedrock provider configured with blank access keys authenticates as the pod. The ServiceAccount token is never mounted: the gateway does not talk to the Kubernetes API.

bifrost.serviceAccount.namestringedisyl-bifrost

No description in values.yaml.

bifrost.serviceAccount.annotationsobject{}

No description in values.yaml.

bifrost.envLabelstring""

Short label shown in Bifrost's own management UI sidebar, so an operator with two of these open can tell them apart. Derived from the environment when the platform injects one; state it here for a self-host install. Bifrost caps it at 10 characters.

bifrost.consoleobject{4 keys}

── The management console ───────────────────────────────────────────────── Bifrost's dashboard and its management API answer on the same port as inference, so anything that publishes this Service publishes provider administration with it. That is why the chart renders no Ingress by default: the api reaches the gateway by cluster DNS and nothing else needs to. An operator who wants the console reachable names a PRIVATE ingress class here. Edisyl uses `tailscale`, which gives the operator's own Ingress controller a tailnet device and serves it at https://<host>.<tailnet>.ts.net with tailnet certificates, so the console is reachable by employees and by nobody on the public internet. Bifrost's own admin credential still applies on top of that. Two gates, and both are deliberate. Network membership is the first, and it is the one that cannot be brute-forced; the shared admin credential is the second. Pointing this at a public ingress class would leave only the second, guarding every vendor credential the deployment holds — so do not. This publishes the WHOLE gateway, inference included. That is harmless on a private network and is not a way to give another environment an inference endpoint: that needs a route that exposes /v1 and nothing else, which is a separate decision.

bifrost.console.enabledbooleanfalse

No description in values.yaml.

bifrost.console.ingressClassNamestring""

No default. A class name is deployment-specific, and guessing one would either render an Ingress no controller claims or, worse, hand it to whichever controller answers for an empty class — which on this fleet is the public one.

bifrost.console.hoststring""

The single DNS label the class turns into a hostname. Under the Tailscale operator it becomes the device name and the MagicDNS name, so it has to be unique in the tailnet — `bifrost-spoke-prd`, not `bifrost`.

bifrost.console.annotationsobject{}

No description in values.yaml.

bifrost.databaseobject{3 keys}

── The gateway's own Postgres ───────────────────────────────────────────── Bifrost owns its schema and migrates it on boot, so it gets its own database and role — never the application's. The chart never puts it in mono's database: a gateway that can read and write application tables is a boundary this exists to draw, and mono's migrations own that schema. cnpg — the bundled cluster provisions a `bifrost` database and role (templates/bifrost-db.yaml). The self-host and evaluation posture. existing-secret — a Secret already in the namespace carries the connection, e.g. what a platform Postgres claim writes. The IDP posture. Discrete keys rather than a URI, unlike database.existingSecret above: Bifrost's configuration takes host/port/user/password/db_name as separate fields and has no DSN input, so a URI would have to be taken apart at runtime by something. The defaults below are the key names a platform Postgres claim's connection Secret already writes.

bifrost.database.sourcestringcnpg

No description in values.yaml.

bifrost.database.existingSecretobject{2 keys}

No description in values.yaml.

bifrost.database.existingSecret.namestring""

No description in values.yaml.

bifrost.database.existingSecret.keysobject{5 keys}

No description in values.yaml.

bifrost.database.sslModestringrequire

`require` for a platform-provisioned Postgres reached over the network; the bundled in-cluster cluster is reached over the pod network and the chart overrides this to `prefer` there. Bifrost requires the field.

Report incorrect code

Please provide a detailed description of the incorrect code.