Helm
The complete Helm chart reference for Scrydon — a condensed install plus every value the chart exposes, with what each does and why and how to configure it.
This is the configuration reference for the Scrydon Helm chart. It documents a condensed install and then walks every value group the chart exposes — what each key does, why you'd change it, and how. If you just want the fastest path to a running cluster, start from your Location page (On-Premise, Azure (AKS), Air-Gapped) and come back here for the options.
For an air-gapped cluster (no outbound internet), use Air-Gapped Deployment instead — the same chart ships inside a Zarf bundle.
How configuration works
Three layers, lowest to highest precedence:
- Chart defaults — the chart ships sane production defaults for everything. The authoritative list is the chart's own
values.yaml(see Inspect the chart locally). - Your
values.customer.yaml— the file you pass with-f. Override only what you need; everything else inherits the default. --setflags — highest precedence, useful for resolving secrets at install time from a secret manager.
helm install scrydon oci://scrydonops.azurecr.io/scrydon/charts/scrydon \
--version <version> -n scrydon-platform \
-f values.customer.yaml \
--set apiTable.secrets.STARROCKS_PASSWORD="$(vault kv get -field=password kv/scrydon/starrocks)"Secrets live in your values file.
values.customer.yamlandhelm get values <release>both echo every user-supplied value. Keep the file out of source control, or manage it via Sealed Secrets / SOPS / External Secrets Operator, and restrict cluster RBAC on the release Secret.
Inspect the chart locally
The chart is an OCI artifact in scrydonops.azurecr.io — there is no public Git mirror. Pull it to read every template and the full default values.yaml:
helm pull oci://scrydonops.azurecr.io/scrydon/charts/scrydon \
--version <version> --untar
less scrydon/values.yaml # the authoritative default for every key belowQuick install
The minimum to a running cluster. Each step is expanded with environment-specific notes on the Location pages.
# 1. Log in to the registry (username = ACR token name from your account team)
helm registry login scrydonops.azurecr.io --username <acr-token-name>
# 2. Create the release namespace + an image-pull secret in it.
# The chart defaults every service to scrydon-platform; split namespaces
# are opt-in via namespaces.* (create the secret in each one you target).
kubectl create namespace scrydon-platform 2>/dev/null || true
kubectl create secret docker-registry scrydon-registry --namespace scrydon-platform \
--docker-server=scrydonops.azurecr.io \
--docker-username=<acr-token-name> --docker-password=<acr-token-password># 3. values.customer.yaml — the entire minimal install.
global:
imageRegistry: scrydonops.azurecr.io # pull images from the ACR you logged into
imagePullSecrets:
- name: scrydon-registry
storageClass: <your-storage-class> # cloud default class name, or your on-prem provisioner
routing:
host: app.example.com # the hostname your DNS points at
ingress:
tls:
enabled: true # browser reaches Scrydon over HTTPS — see Ingress below
infra:
db:
credentials:
password: REPLACE-WITH-DB-PASSWORD # openssl rand -hex 16
auth:
secrets:
AUTH_SECRET: REPLACE-WITH-AUTH-SECRET # openssl rand -hex 32
apiTable:
secrets:
STARROCKS_PASSWORD: REPLACE-WITH-STARROCKS-PW # openssl rand -hex 24# 4. Install
helm install scrydon oci://scrydonops.azurecr.io/scrydon/charts/scrydon \
--version <version> --namespace scrydon-platform \
-f values.customer.yaml --waitRun the setup wizard
Open https://app.example.com/platform/setup (or /setup if you mounted platform at the root). Five steps:
| # | Step | What it does |
|---|---|---|
| 1 | License | Paste or drop the { jwt, publicKey } JSON bundle. The wizard verifies the JWT signature against the bundled public key, checks expiry, and shows tier / CPU / RAM / VRAM. Stored in platform_config on the next step. The bundle's format is shown on the License Checker. Bare .jwt files are rejected — the public key must travel with the JWT. |
| 2 | Admin account | Create the first administrator (email + password). On submit, the license is persisted and the admin user created. |
| 3 | Organization | Name your organization — the root tenant. |
| 4 | Configure delivery (Resend, SMTP, or skip — configurable later under Settings → Platform → Email). With no real provider, new sign-ups skip email verification; once a provider is set, they receive an OTP. | |
| 5 | Complete | Marks setup_completed = true and redirects to the platform home. |
Pre-seed the license
Skip the wizard's license step by injecting the bundle on first install. The values are read once to seed the DB, after which the license lives in platform_config and is managed in the UI:
auth:
secrets:
AUTH_SECRET: REPLACE-WITH-AUTH-SECRET
LICENSE: | # the bundle's `jwt` field (one line)
eyJhbGciOiJSUzI1NiIs...
LICENSE_PUBLIC_KEY: | # the bundle's `publicKey` field (PEM)
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFA...
-----END PUBLIC KEY-----When both are set, the wizard's License step opens already verified.
Configuration reference
Every group below maps to a top-level key in the chart's values.yaml, in file order. Defaults shown are the chart defaults; override only what you need.
global — registry, images, storage
Cluster-wide settings every service inherits.
| Key | Default | What / why |
|---|---|---|
global.imageRegistry | "" | Container image registry root (e.g. scrydonops.azurecr.io, an internal Harbor, a homelab host:port). Empty emits unprefixed scrydon/<name>:<tag> refs. Set per environment. |
global.imagePullSecrets | [] | Names of pull secrets added to every pod. Set to the secret you created in step 2. |
global.imagePullPolicy | Always | Standard Kubernetes pull policy. |
global.storageClass | "" | The provisioner for every PVC. No cloud default — set it to your cloud's class (managed-csi, gp3, standard-rwo) or on-prem provisioner (ceph-rbd, vsphere-csi, local-path). |
global.nfs.enabled | false | Use NFS-backed static PVs instead of dynamic provisioning. For homelab / NFS-export clusters — set server + basePath too. |
global.azure.enabled | false | Azure Marketplace mode: images resolve from MCR via global.azure.images.* and third-party data-plane (StarRocks/SeaweedFS) auto-disables. Set by the Marketplace package, not by hand. |
global:
imageRegistry: scrydonops.azurecr.io
imagePullSecrets:
- name: scrydon-registry
storageClass: managed-csinamespaces — workload placement
Every service defaults to scrydon-platform. Override per service for multi-namespace isolation; the chart auto-aligns Dapr ACL policies and the secret-reader RBAC.
namespaces:
infra: scrydon-infra
platform: scrydon-platform
agentic: scrydon-agentic
analytics: scrydon-platform
cortex: scrydon-platformingress — exposure and TLS scheme
| Key | Default | What / why |
|---|---|---|
ingress.enabled | true | Render Ingress objects. Disable if you front the cluster with your own ingress/gateway. |
ingress.className | traefik | Ingress class. Any controller works. |
ingress.tls.enabled | true | Load-bearing. This means "is Scrydon reached over HTTPS by the browser?" — it drives the public URL scheme, CORS origins, secure cookies, and the Better-Auth callback origin. Defaults to true (secure by default), including behind a TLS-terminating load balancer (App Gateway / ALB) that forwards plain HTTP to Traefik — the browser is still HTTPS, so keep it true. Set false only for HTTP-only deployments (local dev / kind smoke). Setting it false on an HTTPS-fronted install silently breaks login (Mixed Content + mismatched callback origin). |
ingress.annotations | {} | Controller-specific annotations (cert-manager issuer, etc.). |
ingress.middleware.forceHttps.enabled | true | Traefik redirect-to-HTTPS middleware. |
routing — subpath vs subdomain
Chooses how apps are exposed. Full discussion: Routing Modes.
| Key | Default | What / why |
|---|---|---|
routing.mode | subpath | subpath puts every app under one hostname at a path prefix (one DNS record, one cert — recommended for self-hosted). subdomain puts each app on its own hostname (wildcard DNS + cert — used by our SaaS). |
routing.host | — | The single hostname your DNS points at, e.g. app.example.com. |
routing.paths.* | /cortex, /agentic, … | Per-app path prefixes in subpath mode. Set one to "" to mount that app at the root. Apps consume the resolved prefix via BASE_PATH. |
routing:
mode: subpath
host: app.example.com
paths:
cortex: /scrydon/cortex # customize prefixes if needed
platform: /scrydon/platformdapr — service mesh and identity
Service-to-service calls use Dapr with mTLS; the chart's ACL policies key on the SPIFFE trust domain.
| Key | Default | What / why |
|---|---|---|
dapr.enabled | true | Sidecar injection + crypto components + service accounts. |
dapr.installControlPlane | true | Install the Dapr control plane as a subchart into the release namespace. Set false if Dapr is already installed cluster-wide. |
dapr.controlPlaneNamespace | "" | When installControlPlane: false and Dapr lives elsewhere (e.g. dapr-system), point the chart's secret-reader binding at that namespace. |
dapr.trustDomain | scrydon | SPIFFE trust domain. All rendered ACLs key on this. Override only to match an existing Dapr CA — but that disables cross-cluster identity isolation. |
dapr.global.tag | 1.17.7 | Dapr control-plane + sidecar image tag. Avoid 1.17.3–1.17.6 — a workload-identity regression issues certs under spiffe://public/… and 403s every cross-service call. Must lockstep with the dapr dependency version in Chart.yaml. |
dapr.crypto.masterKey | "" | AES-256 master key. Auto-generated on fresh install and preserved across upgrades. BYO: openssl rand -base64 32. |
dapr.secretStore.enabled | true | Create the Kubernetes-secrets-backed Dapr SecretStore component. |
Bringing your own Dapr requires ≥ 1.17.7, trust domain
scrydon(or match it viadapr.trustDomain), and a running sidecar injector. See Existing Dapr installations.
Database — bundled Postgres or BYO
infra.db controls the bundled Postgres (pgvector). For an external/managed instance, the full recipe — per-provider notes, infra.db.external.* keys, pre-creating databases — is on BYO Database.
| Key | Default | What / why |
|---|---|---|
infra.db.enabled | true | Run bundled in-cluster Postgres. Set false to use a managed instance via infra.db.external.*. |
infra.db.credentials.password | postgres | Change this. openssl rand -hex 16. |
infra.db.storage.size | 5Gi | PVC size for the bundled DB. |
infra.db.tls.enabled | true | In-cluster TLS (auto self-signed cert, ISO 27001 A.8.24). Leave on for the bundled DB; managed-Postgres users disable and use the provider's TLS. |
infra.db.backup.enabled | false | Opt-in pg_dump CronJob (A.8.13). Set schedule, retentionDays, databases. Managed-Postgres users use the provider's backup. |
infra.db.external.existingSecrets.* | "" | BYO Mode A (recommended) — per-app Secret names holding DATABASE_URL (auth/agentic), DATABASE_URL_ANALYTICS, etc. |
infra.db.external.<app>Url | "" | BYO Mode B — inline DSNs (appear in helm get values). sslMode appended as ?sslmode=. |
# Bundled (default) — just set a strong password:
infra:
db:
credentials:
password: REPLACE-WITH-DB-PASSWORD
# BYO managed Postgres:
infra:
db:
enabled: false
external:
existingSecrets:
auth: scrydon-auth-db
agentic: scrydon-agentic-db
sslMode: requireStarRocks — Managed Tables (OLAP)
Single-pod allin1-ubuntu. Backs the Tables UI and the agentic schema-inference fallback. Pairs with apiTable.
| Key | Default | What / why |
|---|---|---|
infra.starrocks.enabled | true | Run bundled StarRocks. Set false (with apiTable.enabled: false, or point apiTable.starrocks.host at an external cluster) to skip it. Auto-disabled in Azure mode. |
infra.starrocks.storage.size | 20Gi | PVC size. |
infra.starrocks.resources | 2–8Gi / 0.5–2 CPU | The heaviest bundled data-plane piece — see Trimming. |
The bundled image ships root with no password. Set apiTable.secrets.STARROCKS_PASSWORD in values, then apply it inside StarRocks after install — see StarRocks credentials. For multi-AZ production, switch to the starrocks-kubernetes-operator (FE/BE split) and point apiTable.starrocks.host at its FE Service.
SeaweedFS — object storage
Single-pod S3-compatible storage for uploads.
| Key | Default | What / why |
|---|---|---|
infra.seaweedfs.enabled | true | Run bundled storage. Disable if you front the platform with managed S3 (AWS S3, Azure Blob via S3 API, GCS, MinIO) configured under https://<host>/settings/platform/storage. Auto-disabled in Azure mode. |
infra.seaweedfs.s3.existingSecret | "" | Blank → chart manages random access/secret keys (preserved across upgrades, kept on uninstall). Set to a pre-created Secret with accessKey/secretKey to BYO. |
infra.seaweedfs.storage.size | 20Gi | PVC size. |
Application services
The product apps — auth (api-platform), platform, cortex, analytics, apiOntology, apiTable, agentic (app + realtime). Each block shares a common shape:
| Key (per app) | What / why |
|---|---|
<app>.enabled | Render the app. Most stay on; analytics.enabled: false drops the analytics UI + marimo (heaviest optional workload). |
<app>.replicas | Horizontal scale. |
<app>.image.tag | Defaults to Chart.AppVersion — leave blank to track the chart. |
<app>.resources | Requests/limits. Defaults are tuned from production OOM incidents (agentic limit is 4Gi, the SSR apps 2Gi); lower only on eval clusters. |
<app>.ingress.* | Per-app hostname (subdomain mode only — ignored in subpath). |
<app>.corsOrigins | Allowed browser origins. |
<app>.secrets | Per-app secrets (below). |
<app>.dapr.appApiToken | Per-app sidecar-to-app token, unique per app. Blank → auto-generated and preserved across upgrades. BYO: openssl rand -base64 32. |
Notable app-specific keys:
auth:
secrets:
AUTH_SECRET: REPLACE-WITH-AUTH-SECRET # session signing — openssl rand -hex 32
auditLog:
enabled: true # SIEM forwarding + retention crons (ADR 2026-04-16)
chain:
enabled: false # tamper-evident hash chain — opt-in, has signing cost
analytics:
marimoSidecar:
enabled: true # notebook editor; disable to trim ~2Gi
secrets:
MARIMO_EDIT_TOKEN: "" # blank → auto-generated on install, reused across upgrades
apiTable:
starrocks:
host: "" # blank → bundled FE Service; set for external StarRocks
user: root
secrets:
STARROCKS_PASSWORD: REPLACE-WITH-STARROCKS-PW
agentic:
realtime:
ingress:
annotations: # sticky cookie for the WebSocket route
traefik.ingress.kubernetes.io/service.sticky.cookie: "true"
passkey:
rpId: app.example.com # MUST match routing.host or passkey registration fails
origin: https://app.example.com
auth.secrets.SERVICE_ADMIN_TOKENis deprecated — production uses Dapr mTLS (SPIFFE). Only set it for local dev without sidecars. Where underscore keys are disallowed (Azure Marketplace protected settings), useauth.authSecretinstead ofauth.secrets.AUTH_SECRET.
opa — authorization decision point
Every workspace/workflow/ontology authorization decision is evaluated by OPA.
| Key | Default | What / why |
|---|---|---|
opa.enabled | true | Keep on. With it off (and no opa.url), those decisions fail closed. |
opa.url | "" | Blank → in-cluster Service DNS. Override for an external OPA or non-default Service. |
opa.logLevel | error | Only debug/info/error are valid — warn crashloops the pod. |
opa.decisionLogForwarder.enabled | false | POST every decision to api-platform's audit endpoint (#967). Console logging stays on regardless. |
license — validation posture
| Key | Default | What / why |
|---|---|---|
license.enabled | true | Online license flow — phones home to license.scrydon.com every 24h. Air-gapped overlays set false / mode: offline. |
license.mode | online | online phones home; offline verifies the local JWT only. |
license.gracePeriod | 2592000 | Seconds of grace when phone-home fails (30 days). |
license.publicKeys | {} | Extra public keys for zero-downtime key rotation. |
auth.secrets.LICENSE_PUBLIC_KEY must be set whenever license.enabled: true so api-platform can verify JWT signatures. See Licensing.
packSources — chart-managed pack distribution
| Key | Default | What / why |
|---|---|---|
packSources.enabled | false | Seed pack sources from chart values on every upgrade. |
packSources.sources[] | [] | List of (organization, name, kind, url, …) entries. Helm-managed rows are read-only in the UI. Credentials are referenced by authSecretRef and provisioned out-of-band. See Pack Sources. |
Pod scheduling
Chart-wide nodeSelector, tolerations, and affinity apply to every Deployment, StatefulSet, and migration Job — so pods can land on tainted or dedicated nodes. Per-component overrides are not exposed.
tolerations:
- { key: workload, operator: Equal, value: scrydon, effect: NoSchedule }
nodeSelector:
workload: scrydon
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- { key: node-role.kubernetes.io/scrydon, operator: Exists }Trimming for low-resource installs
Defaults assume a production-shape cluster (≥ 8 vCPU / ≥ 32 GiB across nodes). On eval clusters (4 vCPU / 16 GiB), disable the heaviest optional pieces:
infra:
starrocks:
enabled: false # ~2–8Gi RAM, 20Gi PVC — only Managed Tables + agentic schema-inference affected
apiTable:
enabled: false
analytics:
enabled: false # drops the analytics UI + marimo sidecar (~2Gi)Common configuration recipes
Customizing routing paths
Mount apps under different prefixes (e.g. everything under /scrydon/...) via routing.paths.* — see the routing table above and Routing Modes.
Existing Dapr installations
If the cluster already runs Dapr, set dapr.installControlPlane: false so the chart doesn't try to manage it, and point dapr.controlPlaneNamespace at where Dapr lives:
dapr:
installControlPlane: false
controlPlaneNamespace: dapr-systemYour Dapr must satisfy three prerequisites or sign-in works but account creation / cross-service calls fail with PermissionDenied:
| Requirement | Why |
|---|---|
Dapr ≥ 1.17.7 (outside 1.17.3–1.17.6) | 1.17.3 issued workload SPIFFE IDs in trust domain public, breaking every ACL. |
Trust domain scrydon on dapr-sentry (or set dapr.trustDomain to match yours) | All ACLs key on it. |
| Sidecar injector running | Without it pods come up 1/1 (no daprd) and service invocation short-circuits. |
kubectl -n <dapr-ns> get deploy dapr-sentry \
-o jsonpath='{.spec.template.spec.containers[0].image}' # expect sentry:1.17.7+Advanced ingress: behind a TLS-terminating load balancer
App Gateway / ALB / GCP LB / F5 setups need Traefik-controller tuning (trustedIPs, externalTrafficPolicy: Local, the ingress.tls.enabled public-scheme rule, private ingress, 502 troubleshooting). Full playbook: TLS Offloading.
StarRocks credentials
The bundled StarRocks ships root with no password. After helm install, apply the password you set in apiTable.secrets.STARROCKS_PASSWORD:
NEW_PW="<your-starrocks-password>" # same value as apiTable.secrets.STARROCKS_PASSWORD
# StarRocks runs in the release namespace by default (namespaces.infra → scrydon-platform).
kubectl -n scrydon-platform exec -it deploy/starrocks-fe -- \
mysql -h127.0.0.1 -P9030 -uroot -e "SET PASSWORD FOR 'root' = PASSWORD('${NEW_PW}');"
kubectl -n scrydon-platform rollout restart deployment/svc-api-tableapi-table CrashLoops between install and this step — expected; it stabilizes after the restart. To bring your own StarRocks instead:
infra:
starrocks:
enabled: false
apiTable:
starrocks:
host: starrocks-fe.starrocks.svc.cluster.local
user: scrydon
secrets:
STARROCKS_PASSWORD: REPLACE-WITH-STARROCKS-PASSWORDVerify the deployment
kubectl get pods -n scrydon-platform # all Running, 2/2 with the Dapr sidecar
kubectl get ingress -n scrydon-platform # each Ingress has an ADDRESS
kubectl get certificates -A # cert-manager Certificates READY=TrueLicense claims (tier, entitlements, days-to-expiry) appear under Platform Settings → License after /setup. Sign in at https://app.example.com/ and switch apps via the product switcher without re-authenticating.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
Pods 1/1 (not 2/2) | Dapr injector not running — see Existing Dapr installations. |
Sign-up fails with PermissionDenied from agentic | Wrong Dapr trust domain. kubectl logs deploy/agentic -n scrydon-platform -c daprd | grep spiffe (or your namespaces.agentic override) — IDs should be spiffe://scrydon/..., not spiffe://public/.... Bump Dapr to ≥ 1.17.7. |
Migration Job CrashLoopBackOff | On BYO Postgres, a database wasn't pre-created — see BYO Database → Pre-create databases. |
Init:ImagePullBackOff with insufficient_scope (403) | Your ACR token's scope-map is missing a third-party mirror repo (scrydon/busybox, scrydon/pgvector, scrydon/opa). Send your account team the exact error + repo path — only the scope-map is updated, no token re-issue. |
| Login silently broken behind a load balancer | ingress.tls.enabled left false — set it true (see Ingress). |
Day-2 operations
- Renew the license — paste the refreshed bundle under Settings → License → Update license; no restart. Licensing.
- Backup & restore — bundled Postgres CronJob (
infra.db.backup) or your managed provider. Backup & Restore. - Upgrades — Upgrades.
- Observability — Observability.