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

# Helm chart reference

> Every value the Popsink data-plane chart exposes, component by component.

The reference for the `data-plane` Helm chart. If you are installing for the
first time, follow
[Install the data plane on Kubernetes](/deployment/install/kubernetes) instead — it walks
the one path that works. This page is what you reach for when the wizard's
output is not enough: bringing your own Kafka, schema registry or PostgreSQL,
tuning resources, or managing secrets out of band.

The chart is published as an OCI artifact:

```
oci://ghcr.io/popsink/charts/data-plane
```

A full, annotated list of every value (with `@param` blocks) ships inside the
chart's own `values.yaml`, and the control-plane wizard links the reference
values file for the version it serves you.

## Components

| Component    | Purpose                                                                       | Default      | Replaceable by       |
| ------------ | ----------------------------------------------------------------------------- | ------------ | -------------------- |
| `data-plane` | Main API + UI. Talks to the control plane, launches connector workers as pods | **enabled**  | —                    |
| `kodansu`    | Stateless Kafka-compatible broker, object-store backed                        | **enabled**  | `defaultKafka.*`     |
| `kora`       | Confluent-compatible schema registry                                          | **enabled**  | `schemaRegistry.*`   |
| `kotatsu`    | Optional topic / schema browser over the broker's S3 objects                  | **disabled** | —                    |
| `postgresql` | Bitnami sub-chart, in-cluster Postgres for the data-plane database            | **enabled**  | `externalDatabase.*` |

Connector workers are not part of the chart: the data plane creates them as pods
in its own namespace at runtime.

<Note>
  `kodansu` was previously called `tansu`, and the chart still reads a `tansu:`
  block for backwards compatibility (anything under `kodansu:` wins). New values
  files should use `kodansu:`.

  Two components that appeared in earlier versions of this page — `karapace` and
  `kafka-ui` — no longer exist in the chart. They were replaced by `kora` and
  `kotatsu` respectively. A `values.yaml` that still sets `karapace.*` or
  `kafkaUi.*` is silently ignored.
</Note>

## Identity

These come from the control plane (**Deployments → New deployment →
Self-hosted**) and must be used verbatim — they are how the data plane
authenticates itself.

```yaml theme={null}
controlPlaneUrl:         https://control-plane-api.popsink.com/api
controlPlaneFrontendUrl: https://control-plane.popsink.com
deploymentMode: SELF_HOSTED       # or STANDALONE — no control plane at all
deploymentId:   <uuid>
deploymentJwtToken:
  token: <jwt>                    # an object, not a string

ingressUrl: https://popsink.your-company.com
```

`deploymentMode: STANDALONE` disables every control-plane call, including the
heartbeat, and requires no `deploymentJwtToken`. It is the mode behind
air-gapped installs — see
[Standalone](/deployment/architecture/control-data-plane#standalone-no-control-plane-at-all)
for what you give up.

<Tip>
  `ingressUrl` **must** match the public URL the control plane redirects users to
  — it is also used for OAuth-style callbacks. A mismatch shows up as a login
  redirect loop.
</Tip>

## Secrets you must generate

Beyond the values handed by the control plane, four secrets are generated
locally. The wizard mints three of them in your browser; installing by hand
means producing them yourself.

| Secret                                                      | Format                                         | How to generate                                                                             |
| ----------------------------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `adminCredentials.username` / `password`                    | any                                            | Pick a strong password                                                                      |
| `jwt.secret`                                                | random string ≥ 32 chars                       | `openssl rand -base64 48`                                                                   |
| `connectorConfigEncryptionKey.key`                          | URL-safe base64-encoded **32-byte** Fernet key | `python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"` |
| `postgresql.auth.password` (or `externalDatabase.password`) | any                                            | `openssl rand -base64 24`                                                                   |

<Warning>
  These four secrets encrypt connector credentials, sign user sessions and protect
  the admin login. **Lose them and you lose access to your stored connector
  configs.** Back them up in your secret manager.

  `connectorConfigEncryptionKey.key` must **never be rotated**: every stored
  connector configuration is encrypted with it and becomes unreadable if it
  changes. Each deployment gets its own — never share one across deployments.
</Warning>

## Managing secrets with `existingSecret`

Any sensitive value can come from an existing Kubernetes Secret instead of a
literal. Set the matching `existingSecret` field and leave the literal unset —
this works with Vault, Sealed Secrets and the External Secrets Operator, and is
the recommended shape in production.

Available for `deploymentJwtToken`, `jwt`, `connectorConfigEncryptionKey`,
`adminCredentials`, `kora.database`, `kodansu.storage.aws`, `externalDatabase`
and `postgresql.auth`.

The sources are mutually exclusive: an `existingSecret` combined with a literal
`password` or `url` is rejected at render time.

## Image pull

All images are published to a private GAR registry:

```
europe-west1-docker.pkg.dev/popsink-common-438615/onprem
```

Popsink provides a service-account JSON token to pull them.

<Tabs>
  <Tab title="Chart-managed pull secret">
    ```yaml theme={null}
    imagePullSecret:
      create: true
      registry: europe-west1-docker.pkg.dev/popsink-common-438615/onprem
      token: |
        <base64-encoded service account JSON, or the JSON itself>

    global:
      imagePullSecrets:
        - <release-name>-regcred
    ```

    <Note>
      `global.imagePullSecrets` is required alongside `imagePullSecret.create`.
      The chart creates the secret but does not attach it to any pod, so without
      it every image fails with `ImagePullBackOff`. This is a known chart defect.
    </Note>
  </Tab>

  <Tab title="Bring your own pull secret">
    ```bash theme={null}
    kubectl create secret docker-registry popsink-registry \
      --docker-server=europe-west1-docker.pkg.dev \
      --docker-username=_json_key \
      --docker-password="$(cat key.json)" \
      -n popsink
    ```

    ```yaml theme={null}
    global:
      imagePullSecrets:
        - popsink-registry
    ```
  </Tab>
</Tabs>

## Data-plane database

<Tabs>
  <Tab title="In-cluster (default)">
    ```yaml theme={null}
    postgresql:
      enabled: true
      auth:
        password: <strong-password>
      primary:
        persistence:
          size: 50Gi
          storageClass: <your-ssd-class>
    ```

    Fine for evaluation. In production, prefer a managed instance with
    snapshots.
  </Tab>

  <Tab title="External (recommended in production)">
    ```yaml theme={null}
    postgresql:
      enabled: false

    externalDatabase:
      host: popsink.abc123.eu-west-2.rds.amazonaws.com
      port: 5432
      user: popsink
      database: popsink
      password: <strong-password>
    ```

    <Warning>
      `externalDatabase.user` and `externalDatabase.database` are **not** guarded
      by the chart: left empty they render, install, and the data plane then
      crash-loops on an empty `DB_USER`. The database must exist before the
      install — the data plane runs its own migrations on startup.
    </Warning>
  </Tab>
</Tabs>

## Schema registry (Kora)

Kora is Popsink's schema registry: every connector registers the structure of
what it reads there, and every target resolves schemas from it. It is enabled by
default and it **requires its own PostgreSQL role and database, which the chart
does not create for you**.

<Warning>
  This is the step most first installs miss. The bundled PostgreSQL provisions the
  data-plane's own role and database (`postgresql.auth.*`) and nothing else, so
  leaving `kora.database` unset points Kora at a role that exists nowhere. From
  chart `0.1.0-alpha.162` the install aborts with the instructions below; on
  earlier charts it installs a pod that crash-loops on:

  ```
  failed to connect to database: Backend("error returned from database:
  password authentication failed for user \"kora\"")
  ```
</Warning>

<Tabs>
  <Tab title="Dedicated database (recommended)">
    Run once against your PostgreSQL instance — managed services such as RDS or
    Cloud SQL included:

    ```sql theme={null}
    CREATE ROLE kora LOGIN PASSWORD 'a-strong-password';
    CREATE DATABASE kora OWNER kora;
    ```

    ```yaml theme={null}
    kora:
      database:
        host: "popsink.abc123.eu-west-2.rds.amazonaws.com"   # bundled: "<release>-postgresql"
        port: 5432
        user: "kora"
        database: "kora"
        password: "a-strong-password"
    ```
  </Tab>

  <Tab title="Share the data-plane database">
    No SQL to run — point Kora at the same role and database as the data plane.
    This keeps a self-hosted install to a single database to operate, and is what
    the control-plane wizard generates.

    ```yaml theme={null}
    kora:
      database:
        host: "<same as externalDatabase.host, or <release>-postgresql>"
        port: 5432
        user: "popsink"      # same as externalDatabase.user / postgresql.auth.username
        database: "popsink"  # same as externalDatabase.database / postgresql.auth.database
        password: "<same password>"
    ```
  </Tab>

  <Tab title="Bring your own registry">
    If you already operate a Confluent-compatible schema registry:

    ```yaml theme={null}
    kora:
      enabled: false

    schemaRegistry:
      url: "https://schema-registry.example.com"
      username: "popsink"
      password: "<secret>"
      # or: existingSecret + secretKeys.{username,password}
    ```
  </Tab>
</Tabs>

Credentials can also come from a secret rather than a literal — use
`kora.database.existingSecret` with `secretKeys.password`, or `secretKeys.url`
for a full connection URL.

Kora also supports an Oracle backend: `kora.database.backend: oracle`, port
`1521`, and `database` set to the service name (e.g. `FREEPDB1`). The chart
selects the matching image automatically.

## Broker storage (Kodansu)

Kodansu is a stateless Kafka broker that stores log segments in an object store.
`kodansu.storage.engine` decides where records live, and both it and
`kodansu.storage.aws.region` are required by the chart.

<CodeGroup>
  ```yaml AWS — IRSA (recommended on EKS) theme={null}
  kodansu:
    storage:
      engine: "s3://my-popsink-bucket/kafka/"
      aws:
        region: eu-west-1
        irsaRoleArn:    arn:aws:iam::123456789012:role/popsink-kodansu
        assumedRoleArn: arn:aws:iam::123456789012:role/popsink-kodansu
  ```

  ```yaml AWS — static credentials theme={null}
  kodansu:
    storage:
      engine: "s3://my-popsink-bucket/kafka/"
      aws:
        region: eu-west-1
        accessKeyId: AKIA…
        secretAccessKey: <secret>
  ```

  ```yaml MinIO / non-AWS S3 theme={null}
  kodansu:
    storage:
      engine: "s3://my-popsink-bucket/kafka/"
      aws:
        region: us-east-1
        endpoint: http://minio.minio.svc.cluster.local:9000
        allowHttp: true
        accessKeyId: minio
        secretAccessKey: minio123
  ```

  ```yaml PostgreSQL retention theme={null}
  kodansu:
    storage:
      engine: "postgres://user:password@db.example.com:5432/database"
      aws:
        # Required by the chart even here, and never read on this path.
        region: us-east-1
  ```
</CodeGroup>

The bucket must exist, be writable by the configured identity, and ideally have
**object versioning** enabled.

<Note>
  PostgreSQL retention is simpler to operate but does not scale the same way, and
  rules out Kotatsu (there is no bucket to browse).
</Note>

### Bring your own Kafka

```yaml theme={null}
kodansu: { enabled: false }
kora:    { enabled: false }

defaultKafka:
  bootstrapServer: kafka.example.com:9093
  securityProtocol: SASL_SSL
  saslMechanism: SCRAM-SHA-512
  saslUsername: popsink
  saslPassword: <secret>
  caCert: |-
    -----BEGIN CERTIFICATE-----
    …
  cert: ""
  key:  ""

schemaRegistry:
  url: https://schema-registry.example.com
  username: popsink
  password: <secret>
```

## Stream browser (Kotatsu)

Kotatsu is **optional and disabled by default**. It is a read-only browser over
the objects the broker wrote to S3, and it backs two things in the UI:

* search, pagination and the timestamp column on the datamodel **Stream** tab;
* the per-subscription **target lag** rows on the pipeline latency view.

Without it, the Stream tab falls back to a live Kafka read (no search, no
pagination) and the target-lag rows are simply absent. Pipelines, connectors,
subscriptions and delivery are unaffected — it is safe to leave disabled in
production.

Because it reads the bucket the broker writes, it only means anything with S3
retention. To enable it:

```yaml theme={null}
kotatsu:
  enabled: true
```

That is the entire configuration. Bucket, cluster id, region, endpoint and
credentials all default to the broker's own (`kodansu.storage.*`,
`kodansu.clusterId`) — the only combination that can work, since the two read and
write the same objects. Set `kotatsu.s3.*` only to point Kotatsu at a replica of
the bucket or at a separate read-only credential. On EKS with IRSA, leave the
keys empty and annotate `kotatsu.serviceAccount.annotations` with the role ARN.

<Note>
  On charts before `0.1.0-alpha.162` Kotatsu defaulted to **enabled**, so an
  install that had not opted into S3 aborted with
  `kotatsu.s3.bucket must be provided when kotatsu is enabled`. Setting
  `kotatsu.enabled: false` is the correct workaround on those versions.
</Note>

## Ingress

`ingress.enabled` is **off by default** and we recommend leaving it that way:
ingress in production usually needs company-specific annotations (cert-manager,
WAF, allow-lists). Both shapes — chart-rendered and bring-your-own — are covered
in [step 4 of the install guide](/deployment/install/kubernetes#4-configure-ingress).

| Value                             | What it does                                           |
| --------------------------------- | ------------------------------------------------------ |
| `ingress.enabled`                 | Render an `Ingress` resource (default `false`)         |
| `ingress.hostname`                | Host to serve on                                       |
| `ingress.ingressClassName`        | `nginx`, `traefik`, `istio`…                           |
| `ingress.tls`                     | Enable TLS on the rendered resource                    |
| `ingress.annotations`             | Passed through — cert-manager issuer, WAF, allow-lists |
| `ingress.extraHosts` / `extraTls` | Additional hosts and TLS blocks                        |
| `ingress.path`                    | Path prefix (default `/`)                              |
| `ingress.existingSecret`          | Use an existing TLS secret rather than issuing one     |

The chart never installs an ingress controller. Bringing your own means pointing
it at the `<release>-data-plane` service on port `80`.

## Resources and availability

| Value                            | Default | What it does                                          |
| -------------------------------- | ------- | ----------------------------------------------------- |
| `replicaCount`                   | `2`     | Data-plane API replicas                               |
| `resources.requests` / `.limits` | small   | Per-component CPU and memory — tune for your workload |
| `kodansu.replicaCount`           | `3`     | Broker replicas                                       |
| `pdb.create`                     | `true`  | PodDisruptionBudget — keeps ≥ 1 pod during drains     |
| `autoscaling.hpa.*`              | off     | Horizontal pod autoscaling                            |
| `autoscaling.vpa.*`              | off     | Vertical pod autoscaling                              |
| `image.tag`                      | chart   | Pin the data-plane image — never deploy `latest`      |

## Safety switches

| Value              | Default | What it does         |
| ------------------ | ------- | -------------------- |
| `allowDesignLogin` | `false` | Dev-only. Leave off. |
| `pipelineMode`     | `false` | Dev-only. Leave off. |

## A complete `values.yaml`

```yaml values.yaml theme={null}
# ───── Identity (from the control-plane wizard) ─────
controlPlaneUrl:         https://control-plane-api.popsink.com/api
controlPlaneFrontendUrl: https://control-plane.popsink.com
deploymentMode: SELF_HOSTED
deploymentId:   <uuid-from-wizard>
deploymentJwtToken:
  token: <jwt-from-wizard>

# ───── Public URL of this data plane ─────
ingressUrl: https://popsink.your-company.com

# ───── Image pull ─────
imagePullSecret:
  create: true
  registry: europe-west1-docker.pkg.dev/popsink-common-438615/onprem
  token: <gar-service-account-json>
global:
  imagePullSecrets:
    - <release-name>-regcred

# ───── Secrets you generated ─────
adminCredentials:
  username: admin
  password: <strong-password>
jwt:
  secret: <openssl rand -base64 48>
connectorConfigEncryptionKey:
  key: <fernet-key>

# ───── In-cluster Postgres (default) ─────
postgresql:
  enabled: true
  auth:
    password: <strong-password>
  primary:
    persistence:
      size: 50Gi
      storageClass: <your-ssd-class>

# ───── Schema registry database — NOT created by the chart ─────
# Run once:  CREATE ROLE kora LOGIN PASSWORD '<kora-password>';
#            CREATE DATABASE kora OWNER kora;
kora:
  database:
    host: <release>-postgresql        # or your RDS / Cloud SQL endpoint
    port: 5432
    user: kora
    database: kora
    password: <kora-password>

# ───── Broker + S3 ─────
kodansu:
  storage:
    engine: "s3://my-popsink-bucket/kafka/"
    aws:
      region: eu-west-1
      irsaRoleArn:    arn:aws:iam::123:role/popsink-kodansu
      assumedRoleArn: arn:aws:iam::123:role/popsink-kodansu

# ───── Optional stream browser (S3 retention only) ─────
kotatsu:
  enabled: true
```

## Most useful values

| Path                                            | What it does                                          |
| ----------------------------------------------- | ----------------------------------------------------- |
| `controlPlaneUrl` / `controlPlaneFrontendUrl`   | Control plane API and UI URLs                         |
| `deploymentId`, `deploymentJwtToken.token`      | Identity vs. the control plane                        |
| `deploymentMode`                                | `SELF_HOSTED` or `STANDALONE`                         |
| `ingressUrl`                                    | Public URL of this data plane                         |
| `image.tag`                                     | Pin the data-plane image version                      |
| `replicaCount`                                  | Data-plane API replicas (default 2)                   |
| `resources`                                     | Data-plane CPU / memory requests and limits           |
| `imagePullSecret.*` / `global.imagePullSecrets` | How to authenticate to the GAR registry               |
| `adminCredentials.*`                            | First admin login                                     |
| `jwt.secret`                                    | Signs user session tokens                             |
| `connectorConfigEncryptionKey.key`              | Encrypts connector credentials at rest                |
| `kodansu.enabled`, `kodansu.storage.*`          | In-cluster broker or BYO Kafka; object-store backend  |
| `kora.enabled` / `schemaRegistry.*`             | In-cluster schema registry or BYO                     |
| `kora.database.*`                               | **Required.** Kora's own PostgreSQL role and database |
| `kotatsu.enabled`                               | Optional stream browser (default `false`)             |
| `postgresql.enabled` / `externalDatabase.*`     | In-cluster Postgres or BYO                            |
| `pdb.create`                                    | PodDisruptionBudget (default `true`)                  |
| `autoscaling.hpa.*`, `autoscaling.vpa.*`        | HPA / VPA — disabled by default                       |

## Further reading

<CardGroup cols={2}>
  <Card title="Install the data plane on Kubernetes" icon="cloud-arrow-up" href="/deployment/install/kubernetes">
    The guided path, from the control-plane wizard to a Live deployment.
  </Card>

  <Card title="Troubleshooting" icon="triangle-exclamation" href="/deployment/operate/troubleshooting">
    Symptom-to-cause table for the install and the first boot.
  </Card>

  <Card title="Kubernetes requirements" icon="server" href="/deployment/install/requirements">
    What the cluster, the network and your Popsink account must provide first.
  </Card>

  <Card title="Deployments and environments" icon="layer-group" href="/deployment/architecture/topology">
    How to map deployments onto your regions, networks and staging tiers.
  </Card>
</CardGroup>
