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

# Kubernetes Deployment

> Deploy Formbricks on Kubernetes with the current OCI Helm chart.

Deploy Formbricks on Kubernetes using the current OCI Helm chart published from the `charts/formbricks`
directory in the Formbricks repository.

<Info>
  Formbricks v5 self-hosting expects Hub to be part of the runtime. The chart handles that by default. Use the
  [migration guide](/docs/self-hosting/advanced/migration#v5) before upgrading an existing 4.x deployment.
</Info>

## Prerequisites

Ensure you have the following before proceeding:

* a running Kubernetes cluster
* Helm 3 installed locally
* a public hostname for `formbricks.webappUrl`
* a plan for PostgreSQL and Redis/Valkey, either in-cluster or managed externally
* an edge rate-limiting plan for the v5-covered routes: the chart's Envoy bundle or an equivalent external edge
  solution

## 1. Install The Chart

<Steps>
  <Step title="Create A Minimal values.yaml">
    ```yaml theme={null}
    formbricks:
      webappUrl: https://surveys.example.com
    ```

    Add any additional overrides you need for ingress, external services, secrets, or Enterprise license features.
  </Step>

  <Step title="Install Formbricks">
    ```sh theme={null}
    helm install formbricks oci://ghcr.io/formbricks/helm-charts/formbricks \
      -n formbricks \
      --create-namespace \
      -f values.yaml
    ```

    By default, the chart deploys:

    * the Formbricks application
    * Formbricks Hub
    * Cube
    * PostgreSQL
    * Redis
    * generated Kubernetes Secrets
  </Step>
</Steps>

<Warning>
  The default values are a starting point, not a production edge or high-availability configuration. Both
  `ingress.enabled` and `envoy.enabled` default to `false`, so the chart does not expose the application or add
  the v5 edge rate limits unless you configure them. The defaults also combine `autoscaling.minReplicas: 1` with
  `pdb.minAvailable: 1`; that PodDisruptionBudget can block a node drain because Kubernetes cannot voluntarily
  evict the only app pod.
</Warning>

## 2. Configure Secrets And External Services

### Using Generated Secrets

The default chart path keeps `secret.enabled: true`, which lets the chart generate the required application
secrets for you.

### Adding An Enterprise License

There is no separate `enterprise.enabled` switch. Enterprise features are unlocked by a valid
`ENTERPRISE_LICENSE_KEY`. For a quick-start deployment that uses the chart-generated app Secret, set:

```yaml theme={null}
enterprise:
  licenseKey: your-enterprise-license-key
```

For production, keep the license out of a values file and provide `ENTERPRISE_LICENSE_KEY` through your existing
Secret or ExternalSecret instead. The `enterprise.licenseKey` chart value only writes to the generated app Secret
when `secret.enabled: true`.

### Using Managed PostgreSQL And Redis

For production workloads, many teams prefer managed services:

```yaml theme={null}
postgresql:
  enabled: false
  externalDatabaseUrl: "postgresql://user:password@your-postgres-host:5432/formbricks"

redis:
  enabled: false
  externalRedisUrl: "redis://your-redis-host:6379"
```

### Using External Secrets

If your cluster already uses an external secret manager, enable `externalSecret` and point it at your existing
SecretStore. Ensure the resulting app secret exposes the values your deployment needs, including `DATABASE_URL`,
`REDIS_URL`, and `HUB_API_KEY`.

### SpiceDB authorization

Formbricks v6 uses SpiceDB, maintained by AuthZed, as its authorization engine. Fresh chart installations
enable a private, two-replica SpiceDB cluster and `fully_consistent` decisions by default. If the cluster already
has a compatible SpiceDB operator, keep the authorization runtime enabled but disable this release's operator
installation:

```yaml theme={null}
authzed:
  operator:
    install: false
```

The default `authzed.operator.install: true` installs the pinned operator, creates the cluster, and bootstraps a
dedicated `spicedb` database and login. Install only one operator in a Kubernetes cluster.

For managed PostgreSQL, create a separate SpiceDB database and login first. Store its connection URI and a
strong API token in a Kubernetes Secret using the keys `datastore_uri` and `preshared_key`, then configure:

```yaml theme={null}
authzed:
  enabled: true
  auth:
    existingSecret: formbricks-authzed
  datastore:
    existingSecret: formbricks-authzed
```

Use `authzed.mode: external`, `authzed.operator.install: false`, `authzed.endpoint: <host>:<port>`, and
`authzed.insecure: false` when connecting
to an externally managed AuthZed endpoint. The external endpoint must serve TLS because the preshared token is
sent on every authenticated request. The application endpoint remains internal and uses plaintext gRPC by
default when the chart owns SpiceDB.

For an already-running v6 installation, Helm prints release-specific diagnostic commands that execute the
release-matched operator CLI inside a Formbricks pod:

```bash theme={null}
kubectl exec -n <namespace> deployment/<release-name> -- formbricks-authzed health
kubectl exec -n <namespace> deployment/<release-name> -- formbricks-authzed schema check
kubectl exec -n <namespace> deployment/<release-name> -- formbricks-authzed upgrade check
```

A fresh install runs an idempotent initialization Job, but application startup and probes remain independent
from it. Upgrades never run relationship preparation automatically. For an existing v5 installation, follow
[the v6 upgrade procedure below](#upgrading-from-v5-to-v6) before running `helm upgrade` or resuming GitOps.
The chart refuses an unacknowledged upgrade, disabled AuthZed, or weaker consistency. Follow
[AuthZed Operations](/docs/self-hosting/advanced/authzed-operations) for ongoing operations and do not expose SpiceDB
through an Ingress.

## 3. v5-Specific Deployment Notes

### Hub Is Mandatory

Formbricks v5 does not support `hub.enabled=false`. Keep the default `hub.enabled=true` behavior in place.

Use `hub.image.tag`, `hub.resources`, and `hub.existingSecret` only when you need to pin or customize the Hub
deployment details.

### Envoy Bundle Modes

The chart supports these edge patterns for the v5-covered routes:

* **Bundled Envoy controller and CRDs**: set `envoy.enabled=true`, `envoy.controller.enabled=true`, and
  `envoy.crds.enabled=true`
* **Bundled Envoy controller with platform-managed CRDs**: keep the controller enabled and set
  `envoy.crds.enabled=false`
* **Existing cluster Envoy controller**: set `envoy.enabled=true`, `envoy.controller.enabled=false`,
  `envoy.formbricks.gatewayClass.create=false`, and `envoy.formbricks.gatewayClass.name` to the existing class
* **Equivalent external edge protection**: keep using your platform's own ingress or gateway layer if it already
  provides equivalent rate-limiting coverage

If you use the chart-managed Envoy rate-limiting path, enable a dedicated backend with:

```yaml theme={null}
envoy:
  enabled: true
  controller:
    enabled: true
  crds:
    enabled: true

envoyRedis:
  enabled: true
```

This installs Envoy Gateway 1.8.4 with Gateway API 1.5.1 and keeps Envoy rate-limiting state separate from the
application's own Redis traffic.

If your platform already manages compatible Gateway API CRDs, install only the matching Envoy Gateway CRDs:

```bash theme={null}
crd_dir="$(mktemp -d)"
helm pull oci://docker.io/envoyproxy/gateway-crds-helm \
  --version v1.8.4 --untar --untardir "${crd_dir}"
helm template envoy-gateway-crds "${crd_dir}/gateway-crds-helm" \
  --set crds.gatewayAPI.enabled=false \
  --set crds.envoyGateway.enabled=true \
  | kubectl apply --server-side --force-conflicts -f -
```

Pulling the OCI chart first keeps Helm 4's pull messages out of the manifest pipe. Remove the temporary directory
after the apply succeeds.

Then keep the bundled controller but prevent the Formbricks release from managing either CRD set:

```yaml theme={null}
envoy:
  enabled: true
  controller:
    enabled: true
  crds:
    enabled: false
```

For a Flux `HelmRelease`, also make the ownership boundary explicit:

```yaml theme={null}
spec:
  install:
    crds: Skip
  upgrade:
    crds: Skip
```

The example installs the bundled controller. If your cluster already provides a compatible Envoy Gateway
controller, set `envoy.controller.enabled: false`, disable GatewayClass creation, and provide the existing
GatewayClass name. The controller dependency, including its CRDs, is disabled in this mode. In either mode, configure
`envoy.formbricks.ingress` or your platform ingress/load balancer so public traffic reaches the Envoy-managed
routes. If you use an equivalent external edge solution instead, verify that it covers every route in the
[rate-limiting guide](/docs/self-hosting/advanced/rate-limiting) before exposing Formbricks.

<Warning>
  Istio and other Gateway API implementations can remain in front of Formbricks, but they do not reconcile
  Envoy-specific resources such as `SecurityPolicy`, `HTTPRouteFilter`, or `EnvoyProxy`. Enabling the Feedback
  Records gateway route therefore still requires an Envoy Gateway controller.
</Warning>

### Production Replicas And Disruption Budgets

For a production deployment that should remain available during a voluntary disruption, run at least two app
replicas:

```yaml theme={null}
autoscaling:
  enabled: true
  minReplicas: 2

pdb:
  enabled: true
  minAvailable: 1
```

If you intentionally run a single app replica, either disable the PDB or use `maxUnavailable: 1` instead of
`minAvailable` and accept downtime during the eviction. Never set both fields; set the unused field to `null` in
your values file.

Two autoscaling defaults are easy to miss:

* **`maxReplicas: 10` can exceed what the node pool can hold.** Each app pod requests 1 vCPU, so the ceiling
  needs about 10 allocatable vCPU for the app alone, on top of the platform reserve and the Hub, Cube and
  SpiceDB pods. A smaller pool leaves the extra replicas `Pending` — an HPA does not lower its own ceiling.
* **The HPA scales on CPU and memory utilization only.** Queue depth is not one of its metrics, so a job
  backlog can grow while both targets sit under their thresholds and no replica is added. Backlog-driven
  scaling needs a queue-depth metric published to the metrics API and added to the HPA yourself.

### Cube

Cube is part of the baseline Formbricks v5 stack and is bundled with the chart by default
(`cube.enabled: true`). To run an external Cube cluster instead:

* set `cube.enabled: false` to skip the bundled Cube deployment
* point the app at your external endpoint via `deployment.env.CUBEJS_API_URL`
* supply `CUBEJS_API_SECRET` via `deployment.env` or `deployment.envFrom` if you disable generated
  secrets

### Optional Bundled Qwen/vLLM For AI

The Helm chart can optionally deploy a Formbricks-provided Qwen runtime through vLLM. This is disabled by
default and requires GPU-capable Kubernetes nodes.

To deploy Qwen/vLLM and automatically point the Formbricks app at the in-cluster OpenAI-compatible endpoint:

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

With `llm.enabled: true`, the chart renders the vLLM router and Qwen serving engine, then injects the required
`AI_PROVIDER=openai-compatible` app environment variables unless you override them in `deployment.env`.

If you want to deploy the bundled Qwen runtime without changing the Formbricks app AI configuration:

```yaml theme={null}
llm:
  enabled: true
  autoConfigureApp: false
```

Keep `llm.enabled: false` when you use Google Vertex, Azure, AWS Bedrock, or an externally managed
OpenAI-compatible endpoint. Configure those providers through `deployment.env`.

### Optional AI Taxonomy Beta

The Helm chart can optionally deploy the standalone taxonomy service. This is disabled by default and does not
force the bundled Qwen/vLLM runtime.

To deploy taxonomy and reuse the bundled Qwen/vLLM runtime:

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

taxonomy:
  enabled: true
```

With this setup, taxonomy uses the in-cluster vLLM router for cluster labeling and 5-level taxonomy generation.
The chart also injects `TAXONOMY_SERVICE_URL`, `TAXONOMY_SERVICE_TOKEN`, and `HUB_INTERNAL_API_TOKEN` into Hub API.

To use your own OpenAI-compatible LLM endpoint instead, keep `llm.enabled: false` and set the taxonomy LLM values:

```yaml theme={null}
taxonomy:
  enabled: true
  llm:
    model: qwen3-14b-awq
    baseUrl: http://my-llm-gateway:8000/v1
    existingSecret: taxonomy-llm-secret
```

The `taxonomy-llm-secret` secret must contain `TAXONOMY_LLM_API_KEY`. If your endpoint does not enforce
authentication, store a non-empty dummy value.

To use Gemini on Vertex AI instead, keep `llm.enabled: false`, create a secret containing
`TAXONOMY_GOOGLE_CLOUD_CREDENTIALS_JSON`, and configure the Vertex provider:

```yaml theme={null}
taxonomy:
  enabled: true
  llm:
    provider: vertex-gemini
    model: gemini-2.5-flash
    vertex:
      project: formbricks-cloud
      location: europe-west3
      existingSecret: taxonomy-vertex-secret
```

The service account in `TAXONOMY_GOOGLE_CLOUD_CREDENTIALS_JSON` must be allowed to call Vertex AI for the selected
project and location.

After startup, run the authenticated preflight check from inside the taxonomy pod:

```sh theme={null}
kubectl exec -n formbricks deploy/formbricks-taxonomy -- \
  python -c 'import os, urllib.request; req = urllib.request.Request("http://127.0.0.1:8000/v1/preflight", headers={"Authorization": "Bearer " + os.environ["TAXONOMY_SERVICE_TOKEN"]}); print(urllib.request.urlopen(req, timeout=10).read().decode())'
```

The taxonomy service is internal-only by default. Kubernetes probes use public `/health`; `/v1/preflight` is for
operator validation and requires the internal taxonomy bearer token.

## 4. Upgrade The Deployment

### Upgrading from v5 to v6

Use the [v6 maintenance upgrade guide](/docs/self-hosting/advanced/v6-maintenance-upgrade) for the complete procedure,
temporary Job template, and recovery instructions. Use only an upgrade pair listed as tested in the target
release notes. Pin the target Formbricks image by digest and the matching chart version; preserve existing
database, storage, and Secret bindings.

<Steps>
  <Step title="Enter maintenance and take backups">
    Prepare private SpiceDB connectivity and complete its datastore migrations before the application upgrade.
    Suspend GitOps reconciliation, autoscalers, image updaters, and schedules that could restart writers. Stop
    all writers, including application replicas, Hub/workflow workers, running Jobs, and external integrations,
    then wait for in-flight work and database transactions to finish. Take recoverable backups after writers
    stop and keep maintenance active through preparation and access verification.
  </Step>

  <Step title="Prepare with the target v6 image">
    Run an explicit, temporary Kubernetes Job using the exact target image and the database/AuthZed environment
    bindings that v6 will use, including `AUTHZED_CONSISTENCY=fully_consistent`. Run this command in that Job,
    not in an existing v5 application pod:

    ```bash theme={null}
    formbricks-authzed-prepare --backup-confirmed --writers-stopped
    ```

    The command validates configuration and SpiceDB health, runs application database migrations, prepares the
    schema, drains the outbox, repairs relationships, and performs a separate final `upgrade check`. The flags
    acknowledge prerequisites you have already verified; they do not stop workloads or take backups. Review
    any non-empty schema mismatch using the guide's guarded replacement procedure.
  </Step>

  <Step title="Deploy only after preparation succeeds">
    Require the preparation Job to exit `0`, then set `authzed.migrationAcknowledged: true` and deploy the same
    image digest with the reviewed chart version. The Helm pre-upgrade hook verifies the prepared graph; it
    does not run database migrations, apply the schema, or backfill relationships. Verify authorization and
    outbox delivery before restoring controllers, workers, and public traffic as described in the guide.
  </Step>
</Steps>

<Warning>
  If preparation fails, remain in maintenance. After database changes, `helm rollback` or switching back to a
  v5 image may be unsafe; recovery may require restoring the coordinated backups. `/health` alone does not
  establish authorization readiness.
</Warning>

### Later chart upgrades

For later v6 upgrades, follow the target release's compatibility and maintenance notes. A changed canonical
SpiceDB schema requires explicit preparation again; a previous migration acknowledgement does not bypass
verification. For a compatible routine upgrade:

```sh theme={null}
helm upgrade formbricks oci://ghcr.io/formbricks/helm-charts/formbricks \
  -n formbricks \
  --version <reviewed-chart-version> \
  -f values.yaml
```

For a Formbricks 4.x to 5.0 migration, confirm the following before running the upgrade:

* Hub remains enabled
* `HUB_API_KEY` is present
* your edge rate-limiting plan is in place
* any required `AI_*` variables are added
* `CUBEJS_API_SECRET` is configured (the generated app secret supplies it by default; provide an external
  endpoint if you set `cube.enabled: false`)

## 5. Key Values

| Field | Description |
| - | - |
| `formbricks.webappUrl` | Public base URL for the Formbricks app |
| `deployment.image.tag` | Formbricks image tag override |
| `hub.enabled` | Must stay `true` in Formbricks v5 |
| `hub.image.tag` | Hub image tag override |
| `envoy.enabled` | Enables chart-managed Envoy Gateway resources |
| `envoy.controller.enabled` | Installs the bundled Envoy controller when `true` |
| `envoyRedis.enabled` | Deploys a dedicated Redis backend for Envoy rate limiting |
| `llm.enabled` | Deploys the optional bundled Qwen/vLLM runtime |
| `llm.autoConfigureApp` | Injects Formbricks OpenAI-compatible env vars for bundled Qwen |
| `taxonomy.enabled` | Deploys the optional standalone AI taxonomy service |
| `postgresql.externalDatabaseUrl` | Uses an external PostgreSQL service instead of in-cluster |
| `redis.externalRedisUrl` | Uses an external Redis/Valkey service instead of in-cluster |

For the complete values surface, refer to the chart README in the repository:
[charts/formbricks/README.md](https://github.com/formbricks/formbricks/tree/main/charts/formbricks).

## 6. Uninstalling The Deployment

To remove the deployment:

```sh theme={null}
helm uninstall formbricks -n formbricks
```

If you also want to remove in-cluster persistent volumes:

```sh theme={null}
kubectl delete pvc --all -n formbricks
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.