Configure

Data services

Postgres and Kafka run under an operator by default, or point them at a managed service. Redis is the one service the chart still runs itself.

Data services

The platform needs a Postgres database, a Redis cache, and (optionally) a Kafka broker. Each one is configured on its own, through postgres.mode, redis.mode, and kafka.mode. You can mix them freely: a managed Postgres alongside a bundled Redis is a normal shape.

ModePostgresRedisKafka
defaultoperator, CloudNativePGbundledoperator, Strimzi
externalyesyesyes

Postgres

postgres.mode=operator is the default. The chart renders a CloudNativePG Cluster, and CNPG (a cluster-scoped prerequisite you install once, see Prerequisites) reconciles it into a running Postgres instance with real failover.

--set postgres.instances=3 \
--set postgres.storageSize=100Gi \
--set postgres.storageClass=fast-ssd \
--set postgres.imageName=ghcr.io/cloudnative-pg/postgresql:18

postgres.instances defaults to 1, which renders and reconciles fine but gives you no failover. Three instances survive losing a node. postgres.storageSize is per instance, so three instances cost three times this value. Kubernetes will not shrink a volume later and most storage classes make growing one a manual job, so size it for the year ahead.

CNPG normally invents its own database password on first reconcile, which is too late for this chart: the platform Secret's DB_DSN is composed at helm install time, before the operator has run. The chart goes the other way. It generates the passwords itself and pre-seeds CNPG with them through two small Secrets (<release>-platform-pg-app and <release>-platform-pg-auth-role), which the Cluster references for the application role and for supabase_auth_admin. Nothing for you to configure here; it explains the two extra Secrets you will see in your namespace.

Service DNS, if you need to reach it directly: the CNPG Cluster is <release>-platform-postgres, with a read-write Service at <release>-platform-postgres-rw:5432.

postgres.mode=external

Point it at a managed Postgres instead, and you no longer need CNPG in the cluster at all:

--set postgres.mode=external \
--set postgres.external.dsn='postgres://solvegio:pw@pg.acme.internal:5432/solvegio?sslmode=require' \
--set postgres.external.authAdminPassword=<supabase_auth_admin-password>

postgres.external.dsn is passed through to the API untouched, so whatever you put in it, including sslmode=require, is what the platform connects with.

postgres.external.authAdminPassword is required whenever postgres.mode=external, and it exists because of how GoTrue is built. GoTrue does not share the platform's own database role: it connects as supabase_auth_admin and owns a separate auth schema. In operator mode the chart creates that role and schema for you as part of bringing up the CNPG Cluster. On a managed Postgres the chart has no such hook, so you create the role and schema yourself, before you install, and hand the chart that role's password so it can compose GoTrue's connection string. backend/scripts/tenant-db-reset.sql in the platform repository is the reference for exactly what that role needs: the schema, USAGE on it, EXECUTE on its functions, BYPASSRLS, and the default privileges that let the app role read auth.users. Get the password wrong and the auth pods restart on a failed connect while the rest of the platform looks healthy.

Kafka

kafka.mode=operator is the default. The chart renders a Strimzi KafkaNodePool and Kafka, and Strimzi (also a cluster-scoped prerequisite) reconciles them into a running cluster.

--set kafka.replicas=3 \
--set kafka.storageSize=50Gi \
--set kafka.replicationFactor=3 \
--set kafka.minInsync=2

kafka.replicas defaults to 1, no redundancy. kafka.replicationFactor and kafka.minInsync also default to 1; set them alongside kafka.replicas or a 3-broker cluster still writes with no redundancy at all. With replicationFactor=3 and minInsync=2, a write survives one broker going down and the cluster keeps accepting writes.

kafka.version defaults to empty, and that is deliberate. Strimzi is a prerequisite you install yourself, so the chart does not control which version of it you are running, and each Strimzi release only accepts a narrow window of Kafka versions. Leaving kafka.version empty lets Strimzi pick a version it supports. Pin it only if you have a reason to, and only to a version your installed Strimzi actually accepts; a version outside that window fails at reconcile, not at render, so you will not find out until the Kafka object refuses to converge. If you are not sure what your Strimzi supports, do not set this.

Service DNS: the Strimzi Kafka is <release>-platform-kafka, with a bootstrap Service at <release>-platform-kafka-kafka-bootstrap:9092. The doubled kafka-kafka is Strimzi's own naming convention, not a typo.

kafka.mode=external

--set kafka.mode=external \
--set kafka.external.brokers=kafka-0.acme.internal:9092

You no longer need Strimzi in the cluster. kafka.external.brokers is a comma-separated list of host:port. Leaving it empty while kafka.mode=external is a valid, deliberate configuration: it disables event streaming entirely, and with it the realtime stream and templated email. The rest of the platform runs normally without it.

Redis

redis.mode=bundled is the default, and Redis is the one data service the chart still runs itself: a single-node StatefulSet in your namespace. There is no operator involved, on purpose. Redis here is a cache, not durable state, so a cluster-scoped prerequisite just to run it is not worth adding. The chart generates the password and hands it to both the Redis pod and the connection string.

One node means no failover: if the pod goes away, the cache is unavailable until it is rescheduled. That is a reasonable trade for a cache and a bad one to lose sleep over, since nothing durable lives in it.

--set redis.storage=10Gi \
--set redis.storageClass=fast-ssd \
--set redis.maxmemory=1gb

redis.maxmemory caps what Redis holds before it starts evicting the least recently used keys. Keep it below redis.resources.limits.memory, or the pod gets OOM-killed instead of evicting.

redis.mode=external

--set redis.mode=external \
--set redis.external.url=redis.acme.internal:6379 \
--set redis.external.password=<redis-password>

redis.external.url is a bare host:port, no scheme in front of it. Point this at a managed cache (ElastiCache, Memorystore, Redis Enterprise, whatever you already run) if you need Redis to survive a node failure; that is the HA path for the cache, since there is no bundled-Redis clustering.

Every value on this page is listed with its default in the Helm values reference. Replica counts on the data layer are half of a highly available install; the other half is the platform's own workloads, covered in High availability.

Copyright © 2026