> ## Documentation Index
> Fetch the complete documentation index at: https://docs.r5d.chat/llms.txt
> Use this file to discover all available pages before exploring further.

# Chart values

> Every value accepted by the r5d.chat Helm chart, with defaults and guidance.

Inspect the values shipped with any published version:

```bash theme={null}
helm show values r5d-chat/r5d-chat --version 0.1.0
```

## Images

<ResponseField name="image.repository" type="string" default="ghcr.io/ricsam/r5d-chat-control-plane">
  Control-plane image repository.
</ResponseField>

<ResponseField name="image.tag" type="string" default="(chart appVersion)">
  Control-plane image tag. When empty, the chart's `appVersion` is used, so the
  chart version pins the image published with it.
</ResponseField>

<ResponseField name="image.pullPolicy" type="string" default="IfNotPresent">
  Image pull policy for the control plane.
</ResponseField>

<ResponseField name="workspaceImage.repository" type="string" default="ghcr.io/ricsam/r5d-chat-workspace">
  Workspace image repository, used for every user pod the controller creates.
</ResponseField>

<ResponseField name="workspaceImage.tag" type="string" default="(chart appVersion)">
  Workspace image tag. When empty, the chart's `appVersion` is used.
</ResponseField>

<ResponseField name="imagePullSecrets" type="array" default="[]">
  Pull secrets for control-plane pods, for example `[{ name: ghcr-pull }]`. This
  does **not** apply to workspace pods; set `workspace.imagePullSecret` too.
</ResponseField>

<ResponseField name="replicaCount" type="integer" default="1">
  Control-plane replica count.
</ResponseField>

## Core settings

<ResponseField name="edition" type="string" default="self-hosted">
  `self-hosted` or `managed`. Self-hosted installations should leave this at the
  default: it runs the complete product with no payment provider, no paywall,
  and invite-only registration. `managed` is used only by the hosted r5d.chat
  service and enables subscription billing and open sign-up.
</ResponseField>

<ResponseField name="publicUrl" type="string" default="http://r5d-chat.local" required>
  The exact external URL of the installation. Used for session cookies, auth
  redirects, and the address workspaces use to call back into the control plane.
  A mismatch causes login loops.
</ResponseField>

<ResponseField name="existingSecret" type="string" default="">
  Name of a pre-created Secret holding the application credentials. Strongly
  recommended for production. When set, the chart does not create a Secret and
  the `secrets.*` values are ignored.
</ResponseField>

<ResponseField name="secrets.databaseUrl" type="string" default="">
  PostgreSQL connection string. Defaults to the bundled database when empty.
  Only used when `existingSecret` is empty.
</ResponseField>

<ResponseField name="secrets.betterAuthSecret" type="string" default="" required>
  Session signing key of at least 32 characters. Required when `existingSecret`
  is empty.
</ResponseField>

<ResponseField name="secrets.settingsEncryptionKey" type="string" default="" required>
  AES-256-GCM key of at least 32 characters that encrypts provider and OIDC
  credentials. Required when `existingSecret` is empty. Back it up with the
  database; losing it makes stored credentials unreadable.
</ResponseField>

<ResponseField name="secrets.setupClaimToken" type="string" default="">
  One-time operator token required to create the first administrator. Set it
  before exposing the installation publicly.
</ResponseField>

## Billing

These values apply only when `edition: managed`. A self-hosted installation
ignores them entirely and never contacts a payment provider.

<ResponseField name="billing.productId" type="string" default="">
  Polar product whose subscription grants access.
</ResponseField>

<ResponseField name="billing.server" type="string" default="production">
  `production` or `sandbox`.
</ResponseField>

<ResponseField name="billing.graceHours" type="integer" default="72">
  Hours a lapsed subscription keeps access, so a failed renewal is not an
  instant lockout while the provider retries the charge.
</ResponseField>

<ResponseField name="secrets.polarAccessToken" type="string" default="">
  Polar organization access token. Prefer supplying it through `existingSecret`
  as `POLAR_ACCESS_TOKEN`.
</ResponseField>

<ResponseField name="secrets.polarWebhookSecret" type="string" default="">
  Shared secret used to verify Polar webhook signatures. Prefer supplying it
  through `existingSecret` as `POLAR_WEBHOOK_SECRET`.
</ResponseField>

<Note>
  Billing stays inert until the access token, webhook secret, and product id are
  all present. A managed installation with incomplete billing configuration
  grants access to everyone rather than locking every user out.
</Note>

## Service and ingress

<ResponseField name="service.type" type="string" default="ClusterIP">
  Service type for the control plane.
</ResponseField>

<ResponseField name="service.port" type="integer" default="80">
  Service port. It targets container port 3000.
</ResponseField>

<ResponseField name="ingress.enabled" type="boolean" default="false">
  Create an Ingress for the control plane.
</ResponseField>

<ResponseField name="ingress.className" type="string" default="traefik">
  Ingress class name.
</ResponseField>

<ResponseField name="ingress.host" type="string" default="chat.example.com">
  Hostname to route. Required when `ingress.enabled` is true, and should match
  the host in `publicUrl`.
</ResponseField>

<ResponseField name="ingress.annotations" type="object" default="{}">
  Additional Ingress annotations, for example certificate issuer directives.
</ResponseField>

## Cloudflare Tunnel

Run a [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/)
connector next to the control plane to publish the installation without exposing
an inbound port or provisioning TLS certificates yourself. The connector dials
out to Cloudflare, so it works behind NAT and strict egress firewalls.

Public hostnames and origin routing are configured remotely in Cloudflare
(`config_src: cloudflare`), not in this chart. Point the tunnel's public hostname
at the control-plane Service, for example
`http://<release>-r5d-chat.<namespace>.svc.cluster.local:80`.

<Note>
  `publicUrl` must still match the hostname users type in the browser, for
  example `https://chat.example.com`. Cloudflare terminates TLS at its edge, so
  keep the `https://` scheme even though the tunnel forwards to the Service over
  plain HTTP inside the cluster.
</Note>

`ingress.enabled` and `cloudflared.enabled` are independent. Enable only the
tunnel to publish exclusively through Cloudflare, or enable both to keep a
direct cluster-local entry point alongside it.

<ResponseField name="cloudflared.enabled" type="boolean" default="false">
  Deploy the `cloudflared` connector.
</ResponseField>

<ResponseField name="cloudflared.tokenSecret.name" type="string" default="cloudflared-tunnel">
  Name of an existing Secret holding the tunnel connector token. Create it
  separately so the credential is never stored in values files:

  ```bash theme={null}
  kubectl -n <namespace> create secret generic cloudflared-tunnel \
    --from-literal=token='<connector-token>'
  ```
</ResponseField>

<ResponseField name="cloudflared.tokenSecret.key" type="string" default="token">
  Key inside that Secret containing the token.
</ResponseField>

<ResponseField name="cloudflared.replicaCount" type="integer" default="1">
  Connector replicas. Each replica opens its own set of edge connections; use
  two or more to survive a node failure without dropping the tunnel.
</ResponseField>

<ResponseField name="cloudflared.image.repository" type="string" default="cloudflare/cloudflared">
  Connector image repository.
</ResponseField>

<ResponseField name="cloudflared.image.tag" type="string" default="2026.2.0">
  Connector image tag. Pin it, and update deliberately, because
  `--no-autoupdate` is set.
</ResponseField>

<ResponseField name="cloudflared.configRevision" type="string" default="1">
  Arbitrary value copied to a pod annotation. Change it to force a connector
  restart, which is occasionally useful after editing remote tunnel routing.
</ResponseField>

<ResponseField name="cloudflared.resources" type="object">
  Connector resource requests and limits.
</ResponseField>

## Workspaces

<ResponseField name="workspace.storageClass" type="string" default="rook-ceph-block">
  Storage class for per-user home PVCs. Must support `ReadWriteOnce`.
</ResponseField>

<ResponseField name="workspace.storageSize" type="string" default="5Gi">
  Size of each user's home volume. This applies at creation time; growing it
  later affects only newly created workspaces.
</ResponseField>

<ResponseField name="workspace.idleSeconds" type="integer" default="1800">
  Seconds of inactivity, with no active leases, before a workspace scales to
  zero.
</ResponseField>

<ResponseField name="workspace.runtimeClassName" type="string" default="">
  Optional RuntimeClass for workspace pods. Set to `kata` or `gvisor` for a
  stronger isolation boundary.
</ResponseField>

<ResponseField name="workspace.imagePullSecret" type="string" default="">
  Name of a pull secret attached to workspace pods. Required for private
  workspace images.
</ResponseField>

<ResponseField name="workspace.resources.requests" type="object" default="{ cpu: 200m, memory: 512Mi }">
  Resource requests for workspace pods.
</ResponseField>

<ResponseField name="workspace.resources.limits" type="object" default="{ cpu: '2', memory: 2Gi }">
  Resource limits for workspace pods. Raise the memory limit if agents run
  builds or data processing.
</ResponseField>

<ResponseField name="resources.requests" type="object" default="{ cpu: 50m, memory: 128Mi }">
  Control-plane resource requests.
</ResponseField>

<ResponseField name="resources.limits" type="object" default="{ cpu: 500m, memory: 512Mi }">
  Control-plane resource limits.
</ResponseField>

## Bundled PostgreSQL

<ResponseField name="postgresql.enabled" type="boolean" default="true">
  Deploy the bundled single-replica PostgreSQL StatefulSet. Disable it to use an
  external database.
</ResponseField>

<ResponseField name="postgresql.image" type="string" default="postgres:17.6-alpine">
  PostgreSQL image. Version 17 is the tested baseline.
</ResponseField>

<ResponseField name="postgresql.username" type="string" default="r5d_chat">
  Database user created on first start.
</ResponseField>

<ResponseField name="postgresql.database" type="string" default="r5d_chat">
  Database name created on first start.
</ResponseField>

<ResponseField name="postgresql.password" type="string" default="r5d_chat">
  Database password. Override it, or supply `POSTGRES_PASSWORD` through
  `existingSecret`.
</ResponseField>

<ResponseField name="postgresql.storageClass" type="string" default="rook-ceph-block">
  Storage class for the database volume.
</ResponseField>

<ResponseField name="postgresql.storageSize" type="string" default="2Gi">
  Size of the database volume.
</ResponseField>

<Warning>
  The bundled database is a single replica with no automated backups. It is
  suitable for evaluation and small installations. Production deployments should
  set `postgresql.enabled: false` and use a managed database.
</Warning>

## Service account and network policy

<ResponseField name="serviceAccount.create" type="boolean" default="true">
  Create a ServiceAccount with the Role needed to reconcile workspace objects.
</ResponseField>

<ResponseField name="serviceAccount.name" type="string" default="">
  Override the ServiceAccount name.
</ResponseField>

<ResponseField name="networkPolicy.enabled" type="boolean" default="true">
  Create NetworkPolicies for the control plane and workspaces. Workspace ingress
  is restricted to the control plane on port 7337.
</ResponseField>

<ResponseField name="networkPolicy.workspaceInternetEgress" type="boolean" default="true">
  Allow workspace pods to reach the public internet, excluding RFC1918,
  loopback, and link-local ranges. Set to `false` for air-gapped tenants, which
  also prevents agents from installing packages.
</ResponseField>

## Reference values file

The chart ships `values-r5d.yaml` as a worked example of a hardened install with
Kata isolation and private images:

```yaml theme={null}
publicUrl: https://chat.example.com
imagePullSecrets:
  - name: ghcr-pull
workspace:
  runtimeClassName: kata
  imagePullSecret: ghcr-pull
ingress:
  enabled: true
  className: traefik
  host: chat.example.com
cloudflared:
  enabled: true
  tokenSecret:
    name: cloudflared-tunnel
    key: token
postgresql:
  storageClass: rook-ceph-block
```
