Deploys streaming-hub — the Lerian service that consumes
lib-streaming CloudEvents from
Kafka/Redpanda and fans them out to per-tenant SaaS/BYOC subscribers.
This is a multi-component chart built on one image and one binary, selected
at runtime into a role via STREAMING_HUB_ROLE. There are no database,
broker, or OTEL subcharts — Kafka/Redpanda and PostgreSQL are shared external
infra, and OTEL is env-wired (see External dependencies).
multi-componentsecrets.STREAMING_HUB_POSTGRES_DSN (the DSN carries the DB password); secrets.STREAMING_HUB_DASHBOARD_CURSOR_HMAC_KEY (32 bytes base64, mandatory in every mode from appVersion 2.x on — see Requirements of the 2.x line); secrets.LICENSE_KEY (the Lerian license, mandatory in every environment on a license-gated image — see License); secrets.STREAMING_HUB_KAFKA_SCRAM_PASSWORD (when the broker requires SASL/SCRAM; the matching configmap.STREAMING_HUB_KAFKA_SCRAM_USERNAME is NOT a credential and lives in the ConfigMap); secrets.STREAMING_HUB_DEV_KEK (dev-only KEK material — the pointer configmap.STREAMING_HUB_KEK_REF names it); secrets.STREAMING_HUB_REDIS_PASSWORD (when the rate-limiter Redis/Valkey requires AUTH — the endpoint itself is not a credential and lives in configmap.STREAMING_HUB_REDIS_ADDRESS). Multi-tenant needs exactly one extra secret: secrets.MULTI_TENANT_SERVICE_API_KEY, which authenticates the hub to the tenant-manager it reads the control-plane allowlist from — from the 2.x line on, an empty one refuses the boot alongside a missing configmap.MULTI_TENANT_URL. Each TENANT’s own credentials still need no secret here: the hub sweeps them from AWS Secrets Manager with its own IAM identity. All blank by default; the DSN is always emitted (an empty DSN fails the app fast at boot rather than failing the render). Provide sensitive values inline through a protected values source or set streamingHub.useExistingSecret + streamingHub.existingSecretName (the path GitOps uses, with a Vault-injected Secret).configmap.STREAMING_HUB_KAFKA_BROKERS, secrets.STREAMING_HUB_POSTGRES_DSN and, for control-plane rate limiting, configmap.STREAMING_HUB_REDIS_ADDRESS (see Rate limiting). OTEL is env-wired to a node-local collector (HOST_IP:4317, gated on streamingHub.telemetry.enabled) — there is no OTEL collector subchart. The optional bootstrap-postgres Job provisions the hub’s single database when global.externalPostgresDefinitions.enabled=true.streamingHub.migrations.enabled=true (default off). It is a PreSync hook that runs the stock migrate/migrate toolchain (image ghcr.io/lerianstudio/streaming-hub-migrations) BEFORE the app rolls out. The hook chain is bootstrap-postgres (sync-wave -10, role+db) → migration-secret (-5, the DSN) → migrations Job (-1, the schema) → app Deployment (main Sync). The Job’s only env is STREAMING_HUB_POSTGRES_DSN (from the migration-secret, or migrations.existingSecretName when migrations.useExistingSecret=true). Without it, ingest/dispatcher/partition workers crash on relation "event_inbox"/"delivery_jobs" does not exist (42P01). When migrations.useExistingSecret=true, the named secret must already exist in the namespace before the PreSync phase runs — do not point it at the chart-managed application Secret (streamingHub.existingSecretName / inline secrets.*), which is created later in the main Sync phase and is therefore absent when the hook fires; the chart provisions its own PreSync migration-secret precisely to close that ordering gap.streamingHub.mode (all vs split); size per-role replicaCount / autoscaling / resources and the Postgres pool (poolMaxOpenConns / poolMaxIdleConns) honoring Σ(replicas × poolMaxOpenConns) ≤ Postgres max_connections; set image.tag, ingress, and the secrets (inline or useExistingSecret).github.com/LerianStudio/helm; license is Elastic License 2.0.appVersion 2.1.0 asks for four things the 1.x line did not. They do not
all behave the same way when missing, and the difference matters when you are
reading a pod that will not start — or one that started and is enforcing
nothing. All four are environment wiring the chart exposes but does not invent.
| Requirement | Where | Without it |
|---|---|---|
STREAMING_HUB_DASHBOARD_CURSOR_HMAC_KEY, 32 bytes, base64 |
Secret (always emitted; pinned via secretKeyRef) |
Mandatory in EVERY mode. Absent or short, the app refuses to start. The value is never echoed — supply it through the gitops/Vault path (useExistingSecret), never in a values file in git. |
A DSN that verifies the server certificate: sslmode=verify-full plus sslrootcert=/etc/streaming-hub/postgres-ca/bundle.pem |
Secret (the DSN) + postgres.caBundle (the file) |
In staging and production the hardened boot rejects sslmode=require with no CA. Turn on postgres.caBundle.enabled so the bundle exists at that path — see Postgres TLS and the CA bundle. |
RATE_LIMIT_ENABLED=true together with a Redis address that works |
ConfigMap (streamingHub.rateLimit / configmap.RATE_LIMIT_ENABLED) |
With enforcement asked for and an address set, the connection is validated at boot: an address the hub cannot use stops it rather than serving unlimited. |
| Situation | What actually happens |
|---|---|
STREAMING_HUB_REDIS_ADDRESS left empty |
No crash. lib-commons builds a pass-through limiter and every request is served unlimited, silently — see Rate limiting. This is the failure mode to watch for, because the pod looks healthy. |
| The address points at the tenant-manager’s Redis | Also no crash: nothing at boot detects this. Giving the hub its own instance is a Lerian decision about blast radius and quota, not something the app enforces — the control-plane store is a different tenant of the same technology, not a shared cache. Getting it wrong is silent, so it is the operator’s job to point STREAMING_HUB_REDIS_ADDRESS at an instance dedicated to the hub. |
The license-gated streaming-hub release (streaming-hub#153 and later) refuses to
boot without a valid Lerian license, in every environment. Three
unprefixed variables carry the contract. They follow the platform’s
lib-license-go convention, so a STREAMING_HUB_-prefixed spelling reads empty.
| Variable | Where | Default | Without it / wrong |
|---|---|---|---|
LICENSE_KEY |
Secret: secrets.LICENSE_KEY (always emitted, even empty), or the Secret named by existingSecretName, where the Deployments pin it via secretKeyRef |
"" |
The app refuses to start (ErrMissingLicenseKey). On the useExistingSecret path a Secret without the key fails container creation (CreateContainerConfigError, “couldn’t find key LICENSE_KEY”). Supply it through Vault/ExternalSecret, never in a values file in git. |
ORGANIZATION_IDS |
ConfigMap: configmap.ORGANIZATION_IDS |
global |
Must be exactly global: the hub supports global license mode only. Any other value (GLOBAL, an organization id, a comma list) fails the render, and the app would refuse it at boot (ErrLicenseOrganizationIDsInvalid). |
IS_DEVELOPMENT |
ConfigMap: configmap.IS_DEVELOPMENT (emit-when-set) |
unset | Read by the library. Exactly "true" validates against the development license server (license.dev.lerian.io), which a development license key needs; anything else uses the production server. "true" together with STREAMING_HUB_ENV=production fails the render, and the app would refuse it at boot (ErrLicenseDevServerInProduction). |
streamingHub.extraEnvVars wins over the ConfigMap, so the render guard judges
the effective value there too. It also refuses extraEnvVars.LICENSE_KEY,
because that would put the license in plaintext on the Deployment spec.
Rollout order on the
useExistingSecretpath. From this chart version on, every Deployment pinsLICENSE_KEYby name, whatever the image tag. Add the key to the Vault/ExternalSecret source before you roll the chart out. That includes environments still pinned to a pre-license image, which ignores the value but cannot start without the key being present.
global.scheduling.spread)Every component Deployment (all, ingest, delivery) renders
topologySpreadConstraints from the lerian-common spread preset. The default is
soft (ScheduleAnyway) across nodes, so replicas spread across nodes (e.g. spot
nodes) whenever possible, but a pod is never left Pending because of it. Zone
spreading is off by default (zone: ""): on clusters without the
topology.kubernetes.io/zone label a zone constraint silently cancels the node spread.
Precedence: streamingHub.<component>.topologySpreadConstraints (non-empty) >
streamingHub.topologySpreadConstraints > the preset, whose fields resolve
streamingHub.<component>.spread.<field> > streamingHub.spread.<field> >
global.scheduling.spread.<field>. Each constraint counts only the component’s own
pods of the same ReplicaSet (matchLabelKeys: [pod-template-hash]).
On EKS + Karpenter use a hard node spread:
global:
scheduling:
spread: { hostname: DoNotSchedule, zone: ScheduleAnyway, maxSkew: 1, minDomains: 2, nodeTaintsPolicy: Honor }
minDomains: 2 is required with DoNotSchedule: skew is measured only against
existing nodes, so a single eligible node would otherwise take both replicas.
helm install streaming-hub oci://registry-1.docker.io/lerianstudio/streaming-hub-helm --version <version> -n streaming-hub --create-namespace
With a custom values file:
helm install streaming-hub oci://registry-1.docker.io/lerianstudio/streaming-hub-helm --version <version> -n streaming-hub -f my-values.yaml
The chart is mirrored to oci://ghcr.io/lerianstudio/streaming-hub-helm as well.
helm uninstall streaming-hub -n streaming-hub
mode switch (read this first)streamingHub.mode decides the topology. It is an either/or:
mode |
Renders | STREAMING_HUB_ROLE |
When |
|---|---|---|---|
all (default) |
ONE Deployment + Service (+ HPA/PDB) | all |
Single co-resident deployment, for single-instance setups. Byte-equivalent to the historical single binary. |
split |
TWO Deployments + Services (ingest + delivery), each with its own HPA/PDB | ingest / delivery |
Ingest and delivery scaled independently as N + M replicas. |
NEVER run a
mode: allrelease AND amode: splitrelease against the same Kafka/Redpanda cluster. Both consuming roles (allandingest) join the one ingest consumer group. Anallpod and aningestpod consuming together means every event is double-consumed and double-delivered. Themodeswitch enforces either/or within a single release — do not defeat it by deploying two releases that overlap.
mode: allstreamingHub:
mode: all
all:
replicaCount: 1
poolMaxOpenConns: 25 # role=all holds both planes' connections
poolMaxIdleConns: 12
Renders: streaming-hub-all Deployment + streaming-hub-all Service, plus the
shared streaming-hub ConfigMap + Secret + ServiceAccount. One consumer-group
member set — no double-consume possible.
mode: split (independent scaling)streamingHub:
mode: split
ingest:
replicaCount: 3
poolMaxOpenConns: 8 # consume-poll + inbox tx + partition-cron
poolMaxIdleConns: 4
autoscaling: { enabled: true, minReplicas: 3, maxReplicas: 4 }
delivery:
replicaCount: 2
poolMaxOpenConns: 16 # 8 dispatch workers + reclaim + dlq + delivery /readyz
poolMaxIdleConns: 10
autoscaling: { enabled: true, minReplicas: 2, maxReplicas: 4 }
Renders streaming-hub-ingest and streaming-hub-delivery Deployments +
Services + HPAs, sharing the one ConfigMap + Secret. Each Deployment gets its
own STREAMING_HUB_ROLE and pool sizing as explicit per-Deployment env
(which wins over the shared envFrom).
streaming-hub owns ONE shared PostgreSQL database. Every open connection on
every pod of every role draws from that single max_connections budget.
Σ over all running pods of
(replicas × poolMaxOpenConns)+ headroom ≤ PostgreSQLmax_connections. Under HPA, usemaxReplicas(notreplicaCount) in the sum.
Worked example (max_connections = 100, mode: split):
| Role | replicas | poolMaxOpenConns | connections |
|---|---|---|---|
| ingest | 3 | 8 | 24 |
| delivery | 4 | 16 | 64 |
| total | 88 (leaves 12 for admin/migrations) ✅ |
Adding an all pod (25) → 88 + 25 = 113 > 100 ❌. The mode either/or already
forbids that combination; the budget math is why it also matters operationally.
See streaming-hub/.env.reference (STREAMING_HUB_POSTGRES_MAX_OPEN_CONNS) for
the per-role rationale (all 25/12, ingest 8/4, delivery 16/10).
http port 8080):
livenessProbe: GET /healthz — stays 200 throughout drain.readinessProbe: GET /readyz — flips NotReady (503) first on SIGTERM so the endpoints controller drains the pod from the Service.preStop hook. The hub self-drains on SIGTERM: the exec-form
ENTRYPOINT delivers SIGTERM straight to PID 1, and the lib-commons Launcher
runs the HTTP → consumer → dispatcher teardown.terminationGracePeriodSeconds defaults to 80 — the hub’s derived SIGTERM
drain ceiling — and MUST stay at or above it. At the shipped
STREAMING_HUB_SHUTDOWN_TIMEOUT=30s + STREAMING_HUB_PRE_STOP_DRAIN_TIMEOUT=5s
the ceiling is 30s (HTTP) + 5s (consumer final-commit) + 30s (dispatcher,
min(ShutdownTimeout, 55s)) + 10s slack = 75s, plus the 5s pre-stop wait
= 80s. If you tune those knobs up, recompute and raise this knob to match,
or the orchestrator SIGKILLs a still-draining replica (risking a duplicate
delivery). See .env.reference.Two mutually-exclusive paths:
useExistingSecret |
Behavior |
|---|---|
false (default) |
The chart renders templates/secrets.yaml from streamingHub.secrets (base64-encoded). STREAMING_HUB_POSTGRES_DSN is ALWAYS emitted (even empty) so a bare helm template succeeds; an empty DSN fails the app fast at boot. Empty optional keys are skipped. |
true |
No Secret is rendered. Deployments reference existingSecretName. This is the gitops/Vault path (an external secret is projected into the named Secret) and is the production default. Key contract: the named Secret MUST carry STREAMING_HUB_POSTGRES_DSN, STREAMING_HUB_DASHBOARD_CURSOR_HMAC_KEY and LICENSE_KEY — each Deployment pins all three via explicit secretKeyRefs, so a Secret missing any of them fails container creation (CreateContainerConfigError, “couldn’t find key”) instead of CrashLooping the app. |
Sensitive keys (all in streamingHub.secrets, all default ""):
STREAMING_HUB_POSTGRES_DSN, STREAMING_HUB_KAFKA_CA_CERT,
STREAMING_HUB_DEV_KEK (dev only) and
STREAMING_HUB_DASHBOARD_CURSOR_HMAC_KEY (32 bytes base64, mandatory from 2.x) and
LICENSE_KEY (mandatory in every environment on a license-gated image — see
License)
— these five are ALWAYS emitted (even empty)
so a bare render succeeds and their non-credential-shaped names are still counted
by the coverage check; plus STREAMING_HUB_KAFKA_SCRAM_PASSWORD,
STREAMING_HUB_REDIS_PASSWORD (rate-limiter AUTH; may legitimately stay empty)
and POSTGRES_PASSWORD (migration-toolchain parity; no in-cluster consumer), all
emit-when-set. Not secrets (live in the ConfigMap): ORGANIZATION_IDS /
IS_DEVELOPMENT (the license mode and server selector), STREAMING_HUB_KAFKA_SCRAM_USERNAME
(username, not a credential — and emit-when-set there too: the Deployment reads
the Secret first and the ConfigMap second, so an unconditional empty value here
would overwrite a username projected from Vault into the same key; set it in one
place, never both), STREAMING_HUB_KEK_REF (a POINTER — the NAME of
the env var holding the KEK material) and STREAMING_HUB_REDIS_ADDRESS /
STREAMING_HUB_REDIS_TLS / STREAMING_HUB_REDIS_CA_CERT (an endpoint, a flag,
and a public CA cert — the credential travels in its own key).
This chart provisions none of the following — they live outside it:
STREAMING_HUB_KAFKA_BROKERS at the cluster; supply SASL/SCRAM creds via the
Secret when STREAMING_HUB_KAFKA_SCRAM_MECHANISM is set.tenant_id is a column, not a
per-tenant DB). Either point STREAMING_HUB_POSTGRES_DSN at a pre-provisioned
managed host, or enable global.externalPostgresDefinitions.enabled to run
the bootstrap Job that creates the hub’s one DB + role on a shared host.lib-commons ratelimit, fixed window). Point
configmap.STREAMING_HUB_REDIS_ADDRESS at it (host:port) and put its
password, if it takes one, in secrets.STREAMING_HUB_REDIS_PASSWORD. See
Rate limiting — the chart ships no default address, so an
unconfigured control plane enforces nothing.helm lint/template). When streamingHub.telemetry.enabled=true
(a chart-level toggle, not an app env var), the Deployment injects HOST_IP via
the downward API and sets OTEL_EXPORTER_OTLP_ENDPOINT=$(HOST_IP):4317
(node-local DaemonSet collector).The DSN decides whether the server certificate is verified; the chart only makes
the certificate authority available to verify it against. Set
postgres.caBundle.enabled=true and the bundle is projected read-only at
/etc/streaming-hub/postgres-ca/bundle.pem
in all three roles (all / ingest / delivery) and in the migrations
Job — migrate/migrate dials the same DSN, so it needs the same file. Point the
DSN’s sslrootcert at that path (sslmode=verify-full&sslrootcert=/etc/streaming-hub/postgres-ca/bundle.pem).
Two mutually-exclusive sources, and enabling with neither fails the render on
purpose (a volume naming a ConfigMap that does not exist hangs every pod in
ContainerCreating with no useful message):
| Value | Behavior |
|---|---|
postgres.caBundle.pem |
The chart renders <fullname>-postgres-ca holding the PEM under the key bundle.pem. For AWS RDS this is the PUBLIC global-bundle.pem, which is why a ConfigMap — not a Secret — is the right home. |
postgres.caBundle.existingConfigMap |
An operator-managed ConfigMap is mounted instead. Key contract: it MUST carry bundle.pem; the volume pins that single key, so a ConfigMap keyed otherwise blocks pod creation with a clear kubelet event instead of booting the app with no certificate at the path. |
Setting both fails the render: the chart would build a ConfigMap from pem that
nothing ever mounts while every workload silently verified against the other one.
With existingConfigMap, that ConfigMap has to exist BEFORE the sync starts.
The migrations Job is a PreSync hook, so a ConfigMap the same Application creates
at wave 0 does not exist yet when the Job’s pod is scheduled and the Job sits in
ContainerCreating until it times out. Create it outside the chart (the estate
does), or use pem and let the chart render the PreSync copy for you.
On the chart-managed (pem) path with migrations.enabled=true, a PreSync copy
of the ConfigMap (<fullname>-migrations-postgres-ca, sync-wave -5) is rendered
alongside it. The migrations Job is a PreSync hook at wave -1 and Helm applies
normal resources only after every pre-install hook has run, so the main-Sync
ConfigMap does not exist yet when the Job’s pod is created — the same ordering
gap migration-secret closes for the DSN, with the same
before-hook-creation-only delete policy (a hook-succeeded policy would delete
it at the end of wave -5, before the Job at wave -1 mounts it).
Live from the 2.x line.
appVersion 2.1.0readsSTREAMING_HUB_REDIS_ADDRESSand expectsRATE_LIMIT_ENABLED=true. The store has to be an instance dedicated to the hub, and nothing at boot checks that — point it at the tenant-manager’s Redis and it just works, wrongly. An empty address does not crash either; it serves every request unlimited. See Requirements of the 2.x line. (On1.7.0and earlier these keys were inert.)
The control plane rate-limits its HTTP API through the lib-commons three-tier
contract, backed by Redis/Valkey, which this chart does not provision. The
whole contract is rendered by lerian-common.rateLimit.env, so streaming-hub
exposes the same keys, defaults and productized knobs as every other Lerian
product. The ConfigMap is shared by all roles, so the keys reach every pod.
The connection (streamingHub.datastores.redis, or global.datastores.redis
for the shared tier; configmap.<KEY> overrides either):
| Key | Mask field | Default | What it does |
|---|---|---|---|
STREAMING_HUB_REDIS_ADDRESS |
host |
"" |
Full host:port. Required for enforcement. |
STREAMING_HUB_REDIS_TLS |
tls |
"true" |
TLS to Redis. |
STREAMING_HUB_REDIS_CA_CERT |
caCert |
"" |
Base64 PEM CA bundle. Empty trusts the container’s system roots. |
STREAMING_HUB_REDIS_PASSWORD |
— (secrets) |
"" |
AUTH password. Emit-when-set — leave empty for an instance that takes none. |
The limits (streamingHub.rateLimit.<field>; configmap.<KEY> overrides):
| Key | Field | Default |
|---|---|---|
RATE_LIMIT_ENABLED |
enabled |
"true" |
ALLOW_RATELIMIT_DISABLED |
allowDisabled |
"" |
ALLOW_RATELIMIT_FAIL_OPEN |
allowFailOpen |
"true" (chart sets it — see below) |
RATE_LIMIT_MAX / RATE_LIMIT_WINDOW_SEC |
max / windowSec |
500 / 60 |
AGGRESSIVE_RATE_LIMIT_MAX / ..._WINDOW_SEC |
aggressiveMax / aggressiveWindowSec |
100 / 60 |
RELAXED_RATE_LIMIT_MAX / ..._WINDOW_SEC |
relaxedMax / relaxedWindowSec |
1000 / 60 |
RATE_LIMIT_REDIS_TIMEOUT_MS |
redisTimeoutMs |
500 |
ALLOW_RATELIMIT_FAIL_OPENis a security bypass, andlib-commonssays so. The library forbids fail-open by default and, when permitted, logssecurity bypass activefor the life of the process. This chart setsstreamingHub.rateLimit.allowFailOpen: "true"because the hub’s intended posture is that a Redis outage degrades enforcement rather than taking the control plane down. Unset it for fail-closed, where an unreachable limiter refuses the request.
Three reasonable defaults compose into silent allow-all. An empty
STREAMING_HUB_REDIS_ADDRESSmakeslib-commonsbuild a pass-through limiter; so doesTLS="true"against a private-CA Redis with noCA_CERT, because the handshake fails and fail-open then allows everything. Neither fails the render nor crashes the pod. If an environment must actually enforce a limit, set the address, and set the CA when the chain is private.
This Redis is not the multi-tenant registry, and the two must be separate
instances. MULTI_TENANT_REDIS_* points at the tenant-manager’s Valkey, which
the hub subscribes to for tenant-lifecycle events; STREAMING_HUB_REDIS_* is the
hub’s own rate-limiter store. Nothing at boot checks that they differ, so pointing
one at the other just works, wrongly. Enabling rate limiting does not turn on the
tenant-lifecycle bus, and vice versa.
STREAMING_HUB_PULL_RATE is unrelated and unchanged: it is the app’s own
inbound pull gate on GET /v1/events, not part of this contract.
STREAMING_HUB_PULL_BURST was its burst half and is retired on 2.x — the
fixed window has no separate burst allowance, so the chart ships no default for it
and a stale value earns one WARN per boot.
| Key | Default | Description |
|---|---|---|
streamingHub.mode |
all |
Topology switch: all | split. Schema-enforced enum. |
streamingHub.image.repository |
ghcr.io/lerianstudio/streaming-hub |
Image repo. |
streamingHub.image.tag |
"" |
Empty falls back to Chart.appVersion. |
streamingHub.image.pullPolicy |
IfNotPresent |
|
streamingHub.imagePullSecrets |
[] |
Private registry pull secrets. |
streamingHub.service.type |
ClusterIP |
Lerian convention (Ingress fronts external). |
streamingHub.service.port |
8080 |
Control-plane HTTP port. |
streamingHub.ingress.enabled |
false |
Control-plane API ingress (opt-in per env). |
streamingHub.serviceAccount.create |
true |
|
streamingHub.terminationGracePeriodSeconds |
80 |
Defaults to the ~80s drain ceiling; keep at or above it. |
streamingHub.securityContext |
nonroot 65532, drop ALL, RO rootfs, RuntimeDefault | Distroless:nonroot. |
streamingHub.useExistingSecret |
false |
true = Vault/gitops path. |
streamingHub.existingSecretName |
"" |
Required when useExistingSecret. |
streamingHub.configmap |
{} |
Non-sensitive env override hatch (defaults live in templates/configmap.yaml). configmap.<KEY> overrides an enumerated (allowlisted) key — the schema propertyNames.enum rejects a key outside the allowlist; use extraEnvVars to inject one that is not enumerated. See README.params.md. |
streamingHub.datastores |
{} |
Dedicated datastore masks: postgres (host/port/user/name/ssl) and redis (host carries host:port, tls, caCert). global.datastores.<type> is the shared tier. |
streamingHub.rateLimit |
{allowFailOpen: "true"} |
lib-commons rate-limit contract (10 keys) via lerian-common.rateLimit.env. See Rate limiting. |
streamingHub.extraEnvVars |
{} |
Per-Deployment env hatch (key → value map). Never credentials. |
streamingHub.secrets |
(all "") |
Shared sensitive env. DSN always emitted; other empty values skipped. |
streamingHub.secrets.STREAMING_HUB_DASHBOARD_CURSOR_HMAC_KEY |
"" |
Dashboard cursor signing key, 32 bytes base64. Mandatory from appVersion 2.x; always emitted and pinned via secretKeyRef. |
streamingHub.secrets.LICENSE_KEY |
"" |
Lerian license key (lib-license-go, GLOBAL mode). Mandatory in every environment on a license-gated image; always emitted and pinned via secretKeyRef. See License. |
streamingHub.configmap.ORGANIZATION_IDS |
global (template default) |
License mode. Must be exactly global; any other value fails the render. |
streamingHub.configmap.IS_DEVELOPMENT |
unset | Emit-when-set. "true" selects the development license server; refused with STREAMING_HUB_ENV=production. |
streamingHub.secrets.STREAMING_HUB_REDIS_PASSWORD |
"" |
Rate-limiter Redis AUTH password. Emit-when-set; may stay empty. |
streamingHub.<role>.replicaCount |
1 |
Per role: all / ingest / delivery. |
streamingHub.<role>.poolMaxOpenConns |
all 25 / ingest 8 / delivery 16 |
Postgres pool (connection-budget invariant). |
streamingHub.<role>.poolMaxIdleConns |
all 12 / ingest 4 / delivery 10 |
|
streamingHub.<role>.autoscaling.enabled |
false |
HPA per role (autoscaling/v2, CPU+memory). |
streamingHub.<role>.pdb.enabled |
false |
PodDisruptionBudget per role (policy/v1). |
global.externalPostgresDefinitions.enabled |
false |
Bootstrap Job for the hub’s one DB/role (PreSync, sync-wave -10). |
streamingHub.migrations.enabled |
false |
Out-of-band schema migration Job (PreSync, sync-wave -1). |
streamingHub.migrations.image.repository |
ghcr.io/lerianstudio/streaming-hub-migrations |
Migrations image (FROM migrate/migrate + the hub’s migrations/). |
streamingHub.migrations.image.tag |
"" |
Empty falls back to streamingHub.image.tag, then Chart.appVersion. |
streamingHub.migrations.image.digest |
"" |
Pin by digest; wins over tag. |
streamingHub.migrations.useExistingSecret |
false |
true = Job reads STREAMING_HUB_POSTGRES_DSN from existingSecretName. |
streamingHub.migrations.existingSecretName |
"" |
Required when migrations.useExistingSecret. Must already exist before the PreSync phase — not the main-Sync app Secret. |
streamingHub.migrations.backoffLimit |
3 |
Job retry cap. |
streamingHub.migrations.activeDeadlineSeconds |
600 |
Job wall-clock cap. |
streamingHub.migrations.ttlSecondsAfterFinished |
600 |
Finished-Job GC TTL. |
postgres.caBundle.enabled |
false |
Project the Postgres server CA at /etc/streaming-hub/postgres-ca/bundle.pem in all three roles and the migrations Job. See Postgres TLS and the CA bundle. |
postgres.caBundle.pem |
"" |
The PEM itself; the chart renders the ConfigMap holding it. |
postgres.caBundle.existingConfigMap |
"" |
Mount an operator-managed ConfigMap instead. Must carry the key bundle.pem. |
For the full env contract (defaults, required-in-SaaS markers, the F4 tenant
caution, KEK source vars), see streaming-hub/.env.reference.
See docs/TOPOLOGY.md for the role model in depth.