Reference
Helm values
Every value the platform chart accepts, its default, and what it does.
Helm values
Every value the chart accepts. Two of them have no default and the chart refuses to render without them: platform.host, the public hostname of your API, and activation.code, the enrollment code you mint in the Hub. platform.kind is required too, but values-mvne.yaml and values-mvno.yaml set it for you. If you bring your own Secret through secrets.existingSecret, the chart renders none of its own and activation.code stops being required.
| Value | Default | What it does |
|---|---|---|
nameOverride | "" | Replaces the chart name inside every generated resource name. Leave it empty. The release name already keeps two installs in one namespace apart. |
fullnameOverride | "" | Replaces the whole resource-name prefix, normally the release name plus the chart name. Set it only to match names an existing install already uses. |
platform.kind | "" | Which role this install plays, either mvne or mvno. It selects the API image, the web image, the Kafka client id and the event topic names. The chart refuses to render if it is neither value, so pass values-mvne.yaml or values-mvno.yaml, or set it directly. |
platform.host | "" | The public hostname of your API, for example api.yourdomain.com. You must set this. There is no sensible default for a self-hosted install and the chart refuses to render without it. |
platform.scheme | https | Use https for any real install. http skips TLS and also turns passkey sign-in off, because WebAuthn will not run over plain http on a non-localhost host. It exists for local development only. |
image.registry | ghcr.io | The registry the API image is pulled from, and the derived web image with it, since the chart builds the web reference out of this registry, platform.kind and image.tag. Change it if you mirror our images into a registry of your own. The license agent, email worker and realtime images are full references and ignore it. |
image.repository | "" | The API image repository. Left empty, the chart derives solvegio/mvne-api or solvegio/mvno-api from platform.kind. Set it when you mirror the image under a different name. |
image.tag | latest | The tag on the API image, and on the web image the chart derives whenever app.image is empty. Prefer image.digest in production for the API: a tag can be re-pushed under you, a digest cannot. Note that the digest does not cover the web tier, which keeps following this tag. |
image.digest | "" | Pins the API image by digest, for example sha256:abc123, and wins over image.tag when set. It covers the API image and nothing else. The web tier still resolves through image.tag, so an install with only this set is not fully pinned. To pin the web tier as well, set app.image to a repo@sha256:... reference. The license agent, email worker and realtime images take the same form. |
image.pullPolicy | IfNotPresent | When the kubelet re-pulls the first-party images. One value covers all five: API, web, email worker, realtime and the license agent. GoTrue has its own, images.auth.pullPolicy. IfNotPresent suits released tags. Always only makes sense if you keep re-pushing one tag. |
imagePullSecret | "" | Name of a dockerconfigjson Secret used to pull private images. Leave it empty when the registry is public, or when the images are already imported into the node container runtime. |
images.auth.repository | supabase/gotrue | The GoTrue image that runs the auth service. Change it only to point at your own mirror. |
images.auth.tag | v2.188.1 | The GoTrue version. Stay on the tag the chart ships with unless we tell you to move: that is the version the platform is tested against. |
images.auth.pullPolicy | IfNotPresent | When the kubelet re-pulls the GoTrue image. It ships on a pinned version tag, so IfNotPresent is right and Always buys nothing but a registry round trip on every pod start. This is separate from image.pullPolicy, which covers the first-party images. |
images.licenseAgent | ghcr.io/solvegio/license-agent:latest | Full reference for the license agent image. That pod enrolls your install and keeps its lease renewed. If it cannot run, the API never starts serving. |
images.ess | ghcr.io/solvegio/ess:latest | Full reference for the email worker image. Only read when ess.enabled is true. |
images.realtime | ghcr.io/solvegio/realtime:latest | Full reference for the realtime image, which serves the live event stream to browsers. Only read when realtime.enabled is true. |
serviceAccount.create | true | Whether the chart creates the ServiceAccount the API pod runs as. Turn it off only if cluster policy makes you pre-create service accounts, and then name that account in serviceAccount.name. Setting this false while leaving the name empty is a quiet trap: the API pod lands on the namespace default ServiceAccount instead, which is a fallback and not a decision. |
serviceAccount.name | "" | Name of the API ServiceAccount. Empty means the chart names it after the release. The license agent always gets a separate account of its own, and that is the only identity in the chart holding any Kubernetes permissions. |
activation.code | "" | The one-time code you mint in the Hub console for this install. It has no default, so a fresh install needs it alongside platform.host. Keep it in the values file afterwards: to recover a revoked deployment you mint a fresh code and run helm upgrade with it, and the agent re-enrolls itself. Running an upgrade again with an already used code does nothing and is safe. |
activation.parentUrl | https://api.solvegio.com | The URL this install enrolls its license against. It defaults to the Solvegio Hub. Point it at your own MVNE when an MVNO enrolls against you instead. |
activation.allowUnpinned | false | It has no effect on a released build. Setting it true is meant to let the agent enroll without pinning the parent key fingerprint, but the branch that honours it is compiled out of every binary we ship, so an unpinned activation code is refused whatever this value says. It only does anything in a dev-tagged build used for local work. |
agent.resources | {} | CPU and memory for the license agent pod. Empty means no requests and no limits at all, which leaves it best-effort: it schedules anywhere and is first in line to be evicted when a node runs short of memory. The agent is small, but it is the thing that keeps your lease alive, so on a busy cluster give it a modest request rather than none. |
agent.metricsPort | 9090 | The port declared on the license agent pod for its metrics endpoint. The binary listens on 9090, and changing this number alone does not move that listener, so leave it be. |
postgres.mode | operator | Where the database lives. operator, the default, hands it to CloudNativePG, which has to be installed in the cluster first (see preflight.skipCRDCheck for the check that enforces this). external points at a managed Postgres you already run. |
postgres.external.dsn | "" | The connection string for your managed Postgres, for example postgres://user:pass@host:5432/db?sslmode=require. Required when postgres.mode is external. |
postgres.external.authAdminPassword | "" | Password for the supabase_auth_admin role on your managed Postgres. Required when postgres.mode is external. You create that role and the auth schema yourself. The chart only prepares them when postgres.mode is operator. |
postgres.imageName | ghcr.io/cloudnative-pg/postgresql:18 | The Postgres image CloudNativePG runs. Only read when postgres.mode is operator. |
postgres.instances | 1 | How many Postgres instances CloudNativePG runs. One gives you no failover. Set three for a database that survives losing a node. |
postgres.storageSize | 20Gi | Disk size for each CloudNativePG instance, so three instances cost three times this. Kubernetes will not shrink a volume later, and on many storage classes growing one is a manual job, so size it for the year ahead. |
postgres.storageClass | "" | StorageClass for the CloudNativePG volumes. Empty uses the cluster default. Name an SSD-backed class if that default is slow disk. |
redis.mode | bundled | Where the cache lives. bundled, the default, runs a single-node Redis the chart manages itself, no operator involved. external points at a managed one. |
redis.external.url | "" | Your managed Redis endpoint as host:port, with no scheme in front. Required when redis.mode is external. |
redis.external.password | "" | Password for your managed Redis. Leave it empty only if that instance accepts unauthenticated connections, which is rarely a good idea. |
redis.image | redis:7-alpine | The image the bundled Redis runs. Only read when redis.mode is bundled. |
redis.storage | 5Gi | Disk size for the bundled Redis volume. Redis keeps an append-only file there, so a restart does not lose the cache. |
redis.storageClass | "" | StorageClass for the bundled Redis volume. Empty uses the cluster default. |
redis.maxmemory | 512mb | How much memory the bundled Redis fills before it starts evicting the least recently used keys. Keep it below redis.resources.limits.memory, or the pod gets killed instead of evicting. |
redis.resources.requests.cpu | 100m | CPU the bundled Redis reserves. |
redis.resources.requests.memory | 256Mi | Memory the bundled Redis reserves. |
redis.resources.limits.memory | 640Mi | Hard memory ceiling for the bundled Redis pod. Keep it above redis.maxmemory, since Redis needs room on top of the data it holds. |
kafka.mode | operator | Where the event bus lives. operator, the default, hands it to Strimzi, which has to be installed in the cluster first. external points at brokers you already run. With no brokers at all, event streaming stops, and the live stream and templated email stop with it. |
kafka.external.brokers | "" | Comma-separated host:port brokers. Required when kafka.mode is external. Leaving it empty in external mode is how you deliberately run with no event bus. |
kafka.replicas | 1 | How many brokers Strimzi runs. One has no redundancy. Use three for production. |
kafka.version | "" | The Kafka version Strimzi runs the brokers at. Empty, the default, lets Strimzi pick a version it supports. Strimzi is a prerequisite you install yourself, not something this chart controls, and each Strimzi release only accepts a narrow window of Kafka versions, so only set this if you need to pin, and only to a version your installed Strimzi actually accepts. A version outside that window fails at reconcile, not at render. |
kafka.storageSize | 20Gi | Disk size for each Strimzi broker, which holds queued events across a pod restart. |
kafka.replicationFactor | 1 | How many brokers each partition is copied to. It cannot exceed kafka.replicas. At one, losing a broker loses events. Use three alongside three brokers. |
kafka.minInsync | 1 | How many replicas must confirm a write before it counts as written. With a replication factor of three, set two: a write then survives one broker going down and writes keep working. |
preflight.skipCRDCheck | false | Skips the check that fails the render when postgres.mode=operator or kafka.mode=operator names an operator whose CRDs are not registered in the cluster. Turn it on only for offline rendering, such as helm template run against a cluster Helm cannot see, where there is no live API to check against. Leave it off for a real install: the check is what turns a silently-unreconciled Cluster or Kafka object into an immediate, actionable error. |
ingress.enabled | true | Whether the chart publishes Ingress objects at all. Turn it off if you front the platform with your own gateway. |
ingress.className | nginx | The IngressClass the chart attaches to. It is written for nginx and its annotations assume it. |
ingress.clusterIssuer | letsencrypt-prod | The cert-manager ClusterIssuer that issues your TLS certificate. It has to exist in the cluster already. Ignored when platform.scheme is http. |
ingress.externalDNS | false | Adds hostname annotations for external-dns so your DNS records get created for you. Leave it false when you manage the record by hand, which is the usual case on a single server. |
ingress.hosts.api | "" | Reserved. The API Ingress is published on platform.host and nothing reads this value today, so set platform.host instead. |
ingress.hosts.app | "" | The public hostname for the console or storefront, for example app.yourdomain.com. Without it the web tier gets no Ingress and stays reachable only inside the cluster, since it would otherwise collide with the API on the same host. |
ingress.hosts.realtime | "" | The public hostname for the live event stream, for example rt.yourdomain.com. Without it the realtime service gets no Ingress and browsers cannot reach it, for the same reason as the web host. |
auth.replicas | 1 | How many auth pods run. One suits most installs. Go to two or more, with highAvailability.enabled, when sign-in has to survive a node drain. |
auth.externalURL | "" | The public URL of the auth service. Empty derives your API host plus /v1/auth, which is where the API proxies it. Override it only if auth sits somewhere else, and keep the /v1/auth path or confirmation links point at nothing. |
auth.siteUrl | "" | Where a user lands after confirming an email, following a magic link, or finishing a social login. Empty uses the web host, falling back to platform.host. |
auth.resendAPIKey | "" | Your Resend API key. Setting it switches auth email on and new accounts then have to confirm. Leave it empty and fill auth.smtp instead if you would rather use your own mail server. With neither set, accounts are confirmed automatically and no mail leaves the cluster, which is not what you want in production. |
auth.smtp.host | "" | The mail server that sends auth email, for operators not using Resend. Setting it switches auth email on and new accounts then have to confirm. |
auth.smtp.port | 587 | Port on your mail server. 587 is submission with STARTTLS. |
auth.smtp.user | "" | The username GoTrue authenticates to your mail server with. It is often the full mailbox address, though some providers want a fixed literal instead and put the real credential in auth.smtp.password. Get it wrong and nothing fails at install: mail fails later, at send time. |
auth.smtp.password | "" | Password for your mail server. The chart puts it in the platform Secret rather than inline in the pod definition. |
auth.smtp.adminEmail | "" | The sender address your mail server sees. Empty falls back to auth.fromEmail. Some providers reject mail whose sender is not an address they have verified. |
auth.fromEmail | support@example.com | The From address on auth email. The default example.com address gets your mail rejected, so change it to a domain you can send from. |
auth.fromName | Solvegio | The From name on auth email, and the fallback From name on transactional email. It also names your site in the passkey prompt the browser shows. Transactional email prefers the site name from your portal settings, so this only shows up while that is unset. |
auth.confirmationSubject | Please confirm | Subject line of the account-confirmation email. |
auth.passkey.enabled | true | Whether users can register and sign in with passkeys. Passkeys need https, so this stays off while platform.scheme is http no matter what you set here. |
auth.passkey.maxPerUser | 10 | How many passkeys one user may register. Raise it if your users sign in from many devices. |
auth.oauthServer.enabled | true | Whether your install can act as an OAuth server, which is what lets an AI assistant or another client connect on behalf of a user. Turn it off to close that surface. |
auth.emailHook.enabled | false | Sends auth email through the platform email pipeline so it uses your branded templates instead of the plain built-in ones. It needs ess.enabled. |
auth.providers.google.enabled | false | Whether users can sign in with Google. Fill in the client id and secret below when you turn it on, or the provider stays half-configured and is dropped. |
auth.providers.google.clientId | "" | The OAuth client id from your Google Cloud project. |
auth.providers.google.secret | "" | The OAuth client secret from your Google Cloud project. |
auth.providers.apple.enabled | false | Whether users can sign in with Apple. Fill in the client id and secret below when you turn it on, or the provider stays half-configured and is dropped. |
auth.providers.apple.clientId | "" | The service id from your Apple developer account. |
auth.providers.apple.secret | "" | The client secret from your Apple developer account. Apple secrets expire, so put a renewal in your calendar. |
auth.resources.requests.cpu | 50m | CPU each auth pod reserves. |
auth.resources.requests.memory | 128Mi | Memory each auth pod reserves. |
auth.resources.limits.memory | 256Mi | Hard memory ceiling for an auth pod. |
auth.podSecurityContext.seccompProfile.type | RuntimeDefault | The seccomp profile applied to the auth pod. RuntimeDefault is the hardened setting. Change it only if a cluster policy demands something else. |
auth.containerSecurityContext.allowPrivilegeEscalation | false | Whether the auth container may gain privileges beyond the ones it starts with. Keep it false. |
auth.containerSecurityContext.capabilities.drop | ALL | Linux capabilities taken away from the auth container. ALL removes every one of them, which is what you want. |
api.replicas | 1 | How many API pods run. Ignored once api.autoscaling.enabled is true. |
api.autoscaling.enabled | false | Lets a HorizontalPodAutoscaler size the API on CPU. Your cluster needs metrics-server installed or the autoscaler has nothing to read. |
api.autoscaling.minReplicas | 1 | Floor for API pods under autoscaling. Two or more keeps the API answering through a node drain. |
api.autoscaling.maxReplicas | 4 | Ceiling for API pods under autoscaling. Check your nodes can actually schedule that many. |
api.autoscaling.targetCPU | 70 | The average CPU percentage the autoscaler tries to hold. A lower number scales out sooner and costs more. |
api.port | 8080 | The port the API listens on inside the cluster. The Ingress and the web tier both follow it, so changing it is safe but rarely worth doing. |
api.probes.path | /health | The liveness endpoint Kubernetes polls to decide whether an API pod is still alive and should be left running. It reports only that the process is up. |
api.probes.readyPath | /ready | The readiness endpoint Kubernetes polls to decide whether an API pod should receive traffic. Unlike the liveness path it also fails while the platform holds no valid lease, so an unlicensed pod leaves the load balancer instead of serving licensed routes that cannot work. It checks the database and cache too, so a hard dependency outage correctly takes the pod out of rotation. Point it back at the liveness path only if you are willing to let an unlicensed pod take traffic. |
api.requireParentLease | true | Whether the platform refuses to run without a valid lease. Leave it at true. The chart sets it explicitly so that a Secret which predates activation, is hand-managed, or is rotated without the key cannot quietly downgrade the platform into running unlicensed. Only an explicit false is permissive, and that is for local development. |
api.env.TRANSLATION_AI_PROVIDER | anthropic | Which AI provider translates catalog content: anthropic, openai or google. The matching API key goes in api.secretEnv. With no key the translation engine stays off: the platform boots and serves as usual, and translation jobs sit pending. |
api.env.TRANSLATION_AI_MODEL | claude-sonnet-5 | The model used for translation. It is ignored when the provider is google. |
api.secretEnv | {} | Extra secret environment for the API. Every key here is merged into the platform Secret alongside the ones the chart generates. This is where the translation key goes: ANTHROPIC_API_KEY for the default provider, or OPENAI_API_KEY or GOOGLE_TRANSLATE_API_KEY if you change api.env.TRANSLATION_AI_PROVIDER. Without one the translation engine never starts and jobs sit pending. Anything that is not a credential belongs in api.env, which lands in a ConfigMap instead. |
api.resources.requests.cpu | 100m | CPU each API pod reserves. |
api.resources.requests.memory | 256Mi | Memory each API pod reserves. |
api.resources.limits.memory | 512Mi | Hard memory ceiling for an API pod. Cross it and Kubernetes kills the pod, so raise it before you raise traffic. |
app.enabled | true | Whether the chart runs the web tier, meaning the operator console on an MVNE and the storefront on an MVNO. It ships off, so turn it on when you want a browser interface as well as an API. |
app.replicas | 1 | How many web pods run. Ignored once app.autoscaling.enabled is true. |
app.autoscaling.enabled | false | Lets a HorizontalPodAutoscaler size the web tier on CPU. Your cluster needs metrics-server for it. |
app.autoscaling.minReplicas | 1 | Floor for web pods under autoscaling. |
app.autoscaling.maxReplicas | 6 | Ceiling for web pods under autoscaling. |
app.autoscaling.targetCPU | 70 | The average CPU percentage the autoscaler tries to hold for the web tier. |
app.port | 8080 | The port the web Service and its Ingress route to, and the port declared on the web container. The Nuxt image bakes in a listener on 8080 and the chart does not pass this value through to it, so changing this number on its own points the Service at a port nothing is listening on. Leave it at 8080. |
app.image | "" | Full reference for the web image. Empty derives it from image.registry, platform.kind and image.tag, which on a default MVNO install gives ghcr.io/solvegio/mvno:latest. Set it when you mirror the image under another name, and set it to a repo@sha256:... reference when you want the web tier pinned by digest, because image.digest pins only the API. |
app.sessionPassword | "" | The key that signs browser session cookies, 32 characters or more. Leave it empty: the chart generates one on first install and reuses it across upgrades. Set it yourself only when two installs have to share one session cookie. |
app.resources.requests.cpu | 100m | CPU each web pod reserves. |
app.resources.requests.memory | 256Mi | Memory each web pod reserves. Server-side rendering is the hungry part. |
app.resources.limits.memory | 512Mi | Hard memory ceiling for a web pod. |
app.env | {} | Extra environment for the web tier, merged into its ConfigMap. The chart already sets the API host, the realtime host and the cookie flags, so this is for your own NUXT_PUBLIC_ settings on top. It is a ConfigMap and not a Secret, so keep credentials out of it. |
ess.enabled | true | Whether the email worker runs. It sends the platform emails, order confirmations and the like, through Resend. It needs an event bus: with no brokers it does not start. |
ess.replicas | 1 | How many email workers run. They share the queue, so one is enough until you send in volume. |
ess.resources.requests.cpu | 50m | CPU each email worker reserves. |
ess.resources.requests.memory | 128Mi | Memory each email worker reserves. |
ess.resources.limits.memory | 256Mi | Hard memory ceiling for an email worker. |
realtime.enabled | true | Whether the realtime service runs, pushing live updates into open browser tabs. It needs an event bus, and a hostname in ingress.hosts.realtime to be reachable from outside the cluster. |
realtime.port | 8081 | The port the realtime service listens on. Unlike app.port this one really moves the listener: it reaches the container as PORT, and the Service and Ingress follow it. Change it only if something else in your pod network already claims 8081. |
realtime.logLevel | info | How much the realtime service logs: debug, info, warn or error. |
realtime.replicas | 1 | How many realtime pods run. Each holds its own browser connections, so more pods spread the connections out. |
realtime.eventsTopic | "" | The Kafka topic that carries platform events. Empty derives a topic scoped to this release, which is what stops two installs on one shared Kafka from delivering events to each other. Pin a literal name only to join a topic that already exists. |
realtime.resources.requests.cpu | 100m | CPU each realtime pod reserves. |
realtime.resources.requests.memory | 128Mi | Memory each realtime pod reserves. It grows with the number of open connections. |
realtime.resources.limits.memory | 256Mi | Hard memory ceiling for a realtime pod. |
secrets.existingSecret | "" | The name of a Secret you already manage yourself, with SOPS, sealed-secrets or External Secrets. Set it and the chart creates no Secret of its own and reads everything from yours, which means you now own every key in it, database connection strings included. |
secrets.jwtSecret | "" | The key that signs and verifies session tokens. Leave it empty: the chart generates one on first install and keeps it across upgrades. If you do set it and get it wrong, every authenticated request comes back 401. |
secrets.kek.recipient | "" | The public half of the age key that encrypts stored secrets, from age-keygen. Leave both halves empty and the platform still runs, but it writes those secrets to the database in plain text. Generate a key for anything real. |
secrets.kek.identity | "" | The private half of the age key that encrypts stored secrets. Keep a copy somewhere you can find it: lose it and everything already encrypted with it is unreadable. |
resources.requests.cpu | 100m | A cross-cutting default that no workload reads today. Pods take their sizing from their own block, api.resources and auth.resources and so on, so set those instead. |
resources.requests.memory | 256Mi | A cross-cutting default that no workload reads today. Set the per-workload resources block instead. |
resources.limits.memory | 512Mi | A cross-cutting default that no workload reads today. Set the per-workload resources block instead. |
highAvailability.enabled | false | Spreads pods across zones and nodes and adds a disruption budget for the API and auth. It only buys you anything once those Deployments run two or more replicas. |
highAvailability.topologyKey | topology.kubernetes.io/zone | The node label pods are spread across. The zone label is the default. On a single-zone cluster use kubernetes.io/hostname so pods spread across nodes instead. |
highAvailability.minAvailable | 1 | How many pods have to stay up during a voluntary disruption such as a node drain. At one replica this blocks the drain outright, so raise replicas before you rely on it. |
highAvailability.createPriorityClass | false | Reserved. The chart does not create a PriorityClass today, so this value has no effect. See highAvailability.priorityClassName. |
highAvailability.priorityClassName | solvegio-platform | The PriorityClass attached to pods once highAvailability.enabled is true. The chart does not create it, so either create a PriorityClass with this name first or set this empty. Kubernetes rejects a pod that names a class it cannot find. |
highAvailability.priorityValue | 1000000 | Reserved. It was meant for a PriorityClass the chart does not create, so it has no effect today. |
podSecurityContext.runAsNonRoot | true | Refuses to start a container that would run as root. Keep it true. |
podSecurityContext.runAsUser | 65534 | The user id the first-party containers run as. 65534 is nobody, and the images are built for it, so changing this mostly just breaks file permissions. |
podSecurityContext.runAsGroup | 65534 | The group id the first-party containers run as. |
podSecurityContext.fsGroup | 65534 | The group that owns mounted volumes. It is what lets a container running as nobody write to its own disk. |
podSecurityContext.seccompProfile.type | RuntimeDefault | The seccomp profile applied to platform pods. RuntimeDefault is the hardened setting. |
containerSecurityContext.allowPrivilegeEscalation | false | Whether a container may gain privileges beyond the ones it starts with. Keep it false. |
containerSecurityContext.readOnlyRootFilesystem | true | Mounts the container filesystem read-only. The pods get a writable /tmp for scratch, which is all they need. Keep it true. |
containerSecurityContext.capabilities.drop | ALL | Linux capabilities taken away from platform containers. ALL removes every one of them. |
ordering.enabled | true | Makes pods wait for what they depend on before starting. The API waits for auth, auth waits for the database, the web tier waits for the API, and the email worker waits for the database and the event bus. Turning it off skips every one of those. The API still waits for its license whatever you set here, because an unlicensed platform must not serve. |
ordering.waitImage | curlimages/curl:8.10.1 | The image behind three init containers: the API waiting for auth, the web tier waiting for the API, and the API waiting for its license to land in the Secret. It needs a shell and curl, so a distroless image will not do. |
ordering.dbWaitImage | postgres:18-alpine | The image used by the init containers that wait for Postgres. It needs pg_isready. |
ordering.kafkaWaitImage | busybox:1.36 | The image used by the init container that waits for the event bus. It needs nc. |