Scrydon
DeploymentReference

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:

  1. 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).
  2. Your values.customer.yaml — the file you pass with -f. Override only what you need; everything else inherits the default.
  3. --set flags — 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.yaml and helm 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 below

Quick 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 --wait

Run the setup wizard

Open https://app.example.com/platform/setup (or /setup if you mounted platform at the root). Five steps:

#StepWhat it does
1LicensePaste 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.
2Admin accountCreate the first administrator (email + password). On submit, the license is persisted and the admin user created.
3OrganizationName your organization — the root tenant.
4EmailConfigure 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.
5CompleteMarks 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.

KeyDefaultWhat / 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.imagePullPolicyAlwaysStandard 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.enabledfalseUse NFS-backed static PVs instead of dynamic provisioning. For homelab / NFS-export clusters — set server + basePath too.
global.azure.enabledfalseAzure 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-csi

namespaces — 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-platform

ingress — exposure and TLS scheme

KeyDefaultWhat / why
ingress.enabledtrueRender Ingress objects. Disable if you front the cluster with your own ingress/gateway.
ingress.classNametraefikIngress class. Any controller works.
ingress.tls.enabledtrueLoad-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.enabledtrueTraefik redirect-to-HTTPS middleware.

routing — subpath vs subdomain

Chooses how apps are exposed. Full discussion: Routing Modes.

KeyDefaultWhat / why
routing.modesubpathsubpath 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.hostThe 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/platform

dapr — service mesh and identity

Service-to-service calls use Dapr with mTLS; the chart's ACL policies key on the SPIFFE trust domain.

KeyDefaultWhat / why
dapr.enabledtrueSidecar injection + crypto components + service accounts.
dapr.installControlPlanetrueInstall 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.trustDomainscrydonSPIFFE 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.tag1.17.7Dapr control-plane + sidecar image tag. Avoid 1.17.31.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.enabledtrueCreate the Kubernetes-secrets-backed Dapr SecretStore component.

Bringing your own Dapr requires ≥ 1.17.7, trust domain scrydon (or match it via dapr.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.

KeyDefaultWhat / why
infra.db.enabledtrueRun bundled in-cluster Postgres. Set false to use a managed instance via infra.db.external.*.
infra.db.credentials.passwordpostgresChange this. openssl rand -hex 16.
infra.db.storage.size5GiPVC size for the bundled DB.
infra.db.tls.enabledtrueIn-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.enabledfalseOpt-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: require

StarRocks — Managed Tables (OLAP)

Single-pod allin1-ubuntu. Backs the Tables UI and the agentic schema-inference fallback. Pairs with apiTable.

KeyDefaultWhat / why
infra.starrocks.enabledtrueRun 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.size20GiPVC size.
infra.starrocks.resources2–8Gi / 0.5–2 CPUThe 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.

KeyDefaultWhat / why
infra.seaweedfs.enabledtrueRun 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.size20GiPVC 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>.enabledRender the app. Most stay on; analytics.enabled: false drops the analytics UI + marimo (heaviest optional workload).
<app>.replicasHorizontal scale.
<app>.image.tagDefaults to Chart.AppVersion — leave blank to track the chart.
<app>.resourcesRequests/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>.corsOriginsAllowed browser origins.
<app>.secretsPer-app secrets (below).
<app>.dapr.appApiTokenPer-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_TOKEN is deprecated — production uses Dapr mTLS (SPIFFE). Only set it for local dev without sidecars. Where underscore keys are disallowed (Azure Marketplace protected settings), use auth.authSecret instead of auth.secrets.AUTH_SECRET.

opa — authorization decision point

Every workspace/workflow/ontology authorization decision is evaluated by OPA.

KeyDefaultWhat / why
opa.enabledtrueKeep 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.logLevelerrorOnly debug/info/error are valid — warn crashloops the pod.
opa.decisionLogForwarder.enabledfalsePOST every decision to api-platform's audit endpoint (#967). Console logging stays on regardless.

license — validation posture

KeyDefaultWhat / why
license.enabledtrueOnline license flow — phones home to license.scrydon.com every 24h. Air-gapped overlays set false / mode: offline.
license.modeonlineonline phones home; offline verifies the local JWT only.
license.gracePeriod2592000Seconds 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

KeyDefaultWhat / why
packSources.enabledfalseSeed 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-system

Your Dapr must satisfy three prerequisites or sign-in works but account creation / cross-service calls fail with PermissionDenied:

RequirementWhy
Dapr ≥ 1.17.7 (outside 1.17.31.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 runningWithout 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-table

api-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-PASSWORD

Verify 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=True

License 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

SymptomCause / fix
Pods 1/1 (not 2/2)Dapr injector not running — see Existing Dapr installations.
Sign-up fails with PermissionDenied from agenticWrong 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 CrashLoopBackOffOn 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 balanceringress.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.
  • UpgradesUpgrades.
  • ObservabilityObservability.
On this page

On this page