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

# Migrate self-hosted FlowX.AI to the flowx-ai Helm chart

> Move self-hosted FlowX.AI from the per-service or ai-platform Helm charts to the single flowx-ai chart, keeping your databases, files, and qdrant data.

> [!NOTE]
>
> **Available starting with FlowX.AI 5.9.6.** The `flowx-ai` chart deploys the core and AI services as one Helm release.

> [!NOTE]
>
> In FlowX.AI 5.9.6 you can still install the core services with one Helm chart per service. Starting with the next LTS release family, the `flowx-ai` chart is the only supported way to deploy the platform.

## Overview

The migration replaces your platform workloads once. Your databases, stored files, and indexed qdrant collections are kept.

| | Before | After |
| - | - | - |
| **Core services** | One Helm release per service, each from its own FlowX chart | `services.<name>` entries in one `flowx-ai` release |
| **AI services** | The `ai-platform` umbrella chart, one sub-chart per service | `services.<name>` entries in the same release, switched on by group |
| **Values** | One values file per release. A `global` value reaches only its own release. | One values tree with `global`, `groups`, and `services` |
| **Workload labels** | `app.kubernetes.io/name: <service>` | `app.kubernetes.io/name: flowx-ai`, `app.kubernetes.io/instance: <release>`, `app.kubernetes.io/component: <service>`. The bundled qdrant keeps `app.kubernetes.io/name: qdrant`. |

### What stays the same?

* **Your data.** Databases, files in the object store, and qdrant collections carry over.
* **Datastores.** The chart does not install, change, or delete PostgreSQL, MongoDB, Redis, Elasticsearch, Kafka, or your object store.
* **Persistent volumes.** The qdrant volume claims keep their names and are reattached. See [PVC behavior](#pvc-behavior).
* **Resource names.** Deployments, Services, and Ingresses keep their per-service names, except for the objects listed in [Other behavior changes](#other-behavior-changes).

### What changes?

The workload labels change, and they are part of `spec.selector`. Kubernetes does not allow the selector of an existing Deployment or StatefulSet to change, so `helm upgrade` cannot move a workload from the old charts to the new one. Every Deployment must be deleted and recreated. The qdrant StatefulSet must be recreated too, unless the `flowx-ai` release keeps the release name of your `ai-platform` release: qdrant keeps its `app` and `app.kubernetes.io/name` labels, so its selector changes only through `app.kubernetes.io/instance`.

> [!WARNING]
>
> The services are unavailable from the time you delete the old workloads until the new pods are ready. Plan a maintenance window.

The `flowx-ai` chart deploys the platform version it was released with, so the migration also upgrades every service to that version. Before the window, read the upgrade notes of every release between your current version and the chart's version, including the [5.9.6 upgrade notes](/release-notes/v5.x/v5.9.6-october-2026/v5.9.6-october-2026#upgrade-notes).

***

## Before you begin

### Support for the previous charts

* **`ai-platform` umbrella chart:** retired. From FlowX.AI 5.9.6, the AI services ship in the `flowx-ai` chart.
* **Per-service charts:** move to the `flowx-ai` chart so that later upgrades are a single `helm upgrade` of one release.

### Prerequisites

* Helm 3 and `kubectl`, with access to the namespace that runs FlowX.AI
* `jq` and `yq` v4 (the mikefarah Go version), for the commands in this guide
* Pull access to the `flowx-ai` chart in the chart registry FlowX provides for your installation, at the chart version for your target release. The chart version is the platform release, for example `5.9.6`.
* For the default Gateway API mode: the Gateway API CRDs (`gateway.networking.k8s.io/v1`) in the cluster, a Gateway controller, and a Gateway named `default-gateway` with an `https` listener in the FlowX.AI namespace. The chart attaches its HTTPRoutes to that Gateway but does not create it. To use an existing Gateway, set its name and namespace in `gatewayName` and `gatewayNamespace` under `global.flowx.gateway.public`, `global.flowx.gateway.admin`, and `global.flowx.gateway.adminInstances`. Without the CRDs the chart does not render and fails with `Gateway API HTTPRoute CRD (gateway.networking.k8s.io/v1) not found`. To use Kubernetes Ingress instead, see [Check the global settings](#check-the-global-settings).
* Image pull secrets for every service in the namespace, AI services included. The chart applies `imagePullSecrets` to all pods.
* The `flowx-events-gateway-sa` client and its secret in your identity provider realm. events-gateway authenticates with it in the `flowx-ai` chart.
* If SpiceDB already runs in your environment: `spicedb-operator.enabled: false` and `spicedb-cluster.enabled: false` in your values. The chart installs both by default.
* A backup of PostgreSQL, MongoDB, the object store, and the qdrant collections, taken right before the maintenance window. It is your recovery point for a rollback.
* A maintenance window. Measure the delete-to-ready time in a test environment first.

The commands in this guide read the namespace from `NS`. Set it once in your shell:

```bash theme={"dark"}
export NS={your-flowx-namespace}
```

***

## Service groups

Every service in the chart belongs to exactly one group, set in `services.<name>.group`. A service with no group belongs to `base`. You turn a whole group on or off under `groups`. The groups replace the `devtime` and `runtime` flags of the `ai-platform` umbrella chart.

| Group | Purpose | Default | Members |
| - | - | - | - |
| `core` | FlowX.AI platform services | On | admin, advancing-controller, application-manager, audit-core, authorization-system, cms-core, data-search, designer, document-plugin, email-gateway, events-gateway, file-gateway, integration-designer, license, nosql-db-runner, notification-plugin, organization-manager, process-engine, runtime-manager, scheduler-core, solution-portal, task-management-plugin, webhook-gateway |
| `base` | The AI foundation that the other AI services call | On | ai-gateway, doc-parser, embedder, knowledgebase-indexer-v2, knowledgebase-rag, and the bundled qdrant |
| `devtime` | AI assistance in FlowX Designer | Off | ai-analyst, ai-architect, ai-assistant, ai-designer, ai-developer, kb-enrichment, planner |
| `runtime` | AI capabilities used by running processes | Off | agent-builder, data-privacy, doc-converter, evals-judge, speech-to-text, web-crawler |

The qdrant vector database follows the `base` group. `qdrant.enabled` overrides the group when you set it: `true` deploys qdrant with `base` off, and `false` leaves it out with `base` on.

### How do the switches combine?

The chart decides whether a service runs in this order:

1. `services.<name>.enabled`, if you set it, wins.
2. Otherwise, `groups.<group>.enabled` applies, from your values or from the chart default.

For example, these values turn on the `runtime` group but leave out speech-to-text:

```yaml theme={"dark"}
groups:
  runtime:
    enabled: true
services:
  speech-to-text:
    enabled: false
```

To run the core platform without any AI services, turn off `base`, `devtime`, and `runtime`. qdrant is left out together with `base`.

> [!TIP]
>
> Group defaults can change between chart versions. Set every group explicitly in your values, so your installation behaves the same after any chart upgrade.

### How do the umbrella flags map?

| `ai-platform` umbrella | `flowx-ai` chart |
| - | - |
| `devtime.enabled` | `groups.devtime.enabled` |
| `runtime.enabled` | `groups.runtime.enabled` |
| `runtime.<name>.enabled` | `services.<name>.enabled` |
| Base services, always on | `groups.base.enabled` (on by default), or `services.<name>.enabled` per service |

If your values still contain a top-level `devtime` or `runtime` key, the chart fails to render with the message `top-level devtime/runtime flags moved`. This is intentional: left in place, those keys would be ignored and only the `base` group would deploy.

***

## Convert your values

The `flowx-ai` chart reads one values tree. Most of your settings go under three top-level keys:

* `global`: settings shared by every service, such as domain, identity provider, datastore servers, registry, and ingress
* `groups`: turns service groups on and off. See [Service groups](#service-groups).
* `services.<name>`: the settings of one service

The bundled components keep their own top-level keys: `qdrant`, `spicedb-operator`, `spicedb-cluster`, and `dataSync`.

The chart validates the shape of your values. It fails to render if it finds a service key at the top level instead of under `services`, a top-level `image` or `podLabels` key, or a group that the chart does not declare.

The per-service charts and the `flowx-ai` chart use the same values structure for a service, so most keys inside a service keep their names. The work is mostly moving keys and removing duplicates.

### From the per-service charts

**1. Merge the global blocks**

Take the `global` block of each release's current values and merge them into one `global` block. Where two releases set the same key to different values, choose the value that is correct for the whole platform.

**2. Move each service's keys under services**

Move every other top-level key of a service's values file under `services.<name>`. For example, `resources` in `values-application-manager.yaml` becomes `services.application-manager.resources`:

```yaml theme={"dark"}
# Before: values-application-manager.yaml
replicaCount: 2
resources:
  limits:
    memory: 2Gi

# After: one values file for the flowx-ai release
services:
  application-manager:
    replicaCount: 2
    resources:
      limits:
        memory: 2Gi
```

**3. Remove image tag overrides**

Delete `image.tag` from each service's values. The service version moves to `services.<name>.version`, which the chart pins to the platform release of the chart version. A leftover tag override keeps that service on its old version while the rest of the platform moves.

### From the ai-platform umbrella chart

**1. Move each service key under services**

Move each root-level service key under `services`. For example, `embedder:` becomes `services.embedder:`.

**2. Replace the devtime and runtime flags**

Use the mapping in [How do the umbrella flags map?](#how-do-the-umbrella-flags-map).

**3. Merge the global block**

Merge the umbrella's `global` block into the shared `global` block.

### Check the global settings

In the old layout, a `global` value reached only the services of its own release. In the `flowx-ai` chart, a `global` value reaches every service. Review these cases before you render:

* **Ingress or Gateway API.** The chart routes traffic through Gateway API by default, and `global.flowx.ingress.enabled` defaults to `false`. To keep Kubernetes Ingress, set `global.flowx.ingress.enabled: true`, and set `global.flowx.gateway.enabled: false` if the cluster has no Gateway API CRDs: turning Ingress on does not turn the Gateway API routes off. A service then gets an Ingress only when its own `ingress.enabled` is also on. Ingress mode does not strip path prefixes, so services served under a prefix, such as `/license`, `/ai`, and `/integration`, need regex paths with `rewrite-target` annotations. With Ingress on, five AI services also get an admin Ingress by default: ai-gateway, doc-converter, data-privacy, speech-to-text, and web-crawler. Set `ingress.admin.enabled: false` on any of them that has none today.
* **`global.env`, `global.podAnnotations`, and `global.replicaCount`.** If these came from your core releases, they now also apply to the AI services, for example a Java agent annotation on Python pods. Move them onto the core services that need them.
* **Values set to `null` to unset a default.** The chart validates values against one schema for all services and rejects some of these. Delete them: the result is the same when the chart sets no default for that key.

### Choose the release name

The release name becomes part of the name of the CA certificates init container. It is also passed to doc-parser, doc-converter, evals-judge, data-privacy, speech-to-text, and web-crawler in the `GRPC_HOST_RESOLVER_HELM_CHART` environment variable, for service discovery.

Choose a name that no other release in the namespace uses, and keep it for the life of the installation. A name that contains `flowx-ai`, such as `flowx-ai`, keeps the generated names short, because the chart does not append `-flowx-ai` to it.

### Render and compare

Render the chart with your new values before the maintenance window, and compare the result with what runs today. Use the chart reference from your FlowX chart registry, and the platform release as the chart version:

```bash theme={"dark"}
helm template {your-release-name} {flowx-ai-chart-reference} --version {chart-version} \
  -n "$NS" -f values-flowx-ai.yaml > rendered.yaml

# Environment variables per Deployment, today
kubectl -n "$NS" get deploy -o json | jq -r '.items[] | .metadata.name as $n
  | .spec.template.spec.containers[0].env[]? | "\($n)\t\(.name)=\(.value // "")"' \
  | sort > env-live.txt

# Environment variables per Deployment, after the migration
yq -o json 'select(.kind == "Deployment")' rendered.yaml | jq -rs '.[] | .metadata.name as $n
  | .spec.template.spec.containers[0].env[]? | "\($n)\t\(.name)=\(.value // "")"' \
  | sort > env-new.txt

diff env-live.txt env-new.txt
```

Every line in the diff is a behavior change. Resolve each one, or confirm that you expect it, before you continue. Pay particular attention to variables that contain `BUCKET`, `URL`, and `REALM`.

The comparison covers inline `env` values of the first container only. Compare variables that come from `valueFrom` or `envFrom` (Secrets and ConfigMaps), and any other containers, separately.

***

## Migrate S3 buckets

The `flowx-ai` chart sets no bucket names and no bucket prefixes. Carry over the names your services use today, or they start writing to new, empty buckets.

> [!WARNING]
>
> A wrong bucket name does not always fail loudly. A service that is allowed to create buckets creates a missing bucket on first use. New files go to the new bucket, and files stored under the old name are no longer found. Nothing is deleted, but uploaded documents, media library assets, and knowledge base files appear to be missing.

### How do bucket names reach a service?

If a value is set, the chart renders it as an environment variable, such as `APPLICATION_FILE_STORAGE_S3_BUCKET_PREFIX` as rendered by the chart. If it is not set, the chart renders nothing, and the service falls back to the default built into its image. Several of those image defaults are empty.

A service that uses a prefix derives its bucket names from it: `<prefix>-<file type>` for regular uploads, and `<prefix>` alone for date-partitioned files. Dots become dashes.

### Bucket keys and previous defaults

The following table lists every bucket setting and the name a previous chart set when you did not.

| Service | Key in `flowx-ai` values | Per-service chart default | `ai-platform` umbrella default |
| - | - | - | - |
| application-manager | `services.application-manager.component.s3.main.bucketPrefix` | None (image default) | Not in this chart |
| runtime-manager | `services.runtime-manager.component.s3.main.bucketPrefix` | None (image default) | Not in this chart |
| integration-designer | `services.integration-designer.component.s3.main.bucketPrefix` | None (image default) | Not in this chart |
| document-plugin | `services.document-plugin.component.s3.main.bucketPrefix` | `flowx-dev` | Not in this chart |
| notification-plugin | `services.notification-plugin.component.s3.main.bucketPrefix` | `flowx-dev` | Not in this chart |
| cms-core, public | `services.cms-core.component.s3.public.bucketName` and `.rootDirectory` | `dev-env-media-library`, root directory `pr` | Not in this chart |
| cms-core, private | `services.cms-core.component.s3.private.bucketName` and `.rootDirectory` | `cms-private-bucket`, root directory `devtest` | Not in this chart |
| agent-builder | `services.agent-builder.component.s3.workflows.bucketName` | `workflows-bucket` | `workflows-bucket` |
| doc-parser | `services.doc-parser.component.s3.documents.bucketName` | `email-ingest` | `email-ingest` |
| kb-enrichment | `services.kb-enrichment.component.s3.applicationFileStorage.bucketName` | `kb-enrichment-bucket` | `kb-enrichment-bucket` |
| knowledgebase-indexer-v2 | `services.knowledgebase-indexer-v2.component.s3.applicationFileStorage.bucketName` | `kb-enrichment-bucket` (shared with kb-enrichment) | `kb-enrichment-bucket` |
| data-privacy | `services.data-privacy.component.s3.documents.bucketName` | `data-privacy-bucket` | `data-privacy-bucket` |

What this means for your previous charts:

* **From the per-service charts:** document-plugin, notification-plugin, cms-core, and the AI services in the table had bucket names set by their chart. The `flowx-ai` chart does not set them, so set them yourself, even if you never changed them.
* **From the `ai-platform` umbrella chart:** set the AI bucket names explicitly. Most AI image defaults are empty.
* **Services marked None:** they used their image default, and keep it if you leave the key unset.

### Step 1: Find the names in use

List the bucket and root directory variables that each running service uses:

```bash theme={"dark"}
kubectl -n "$NS" get deploy -o json | jq -r '.items[] | .metadata.name as $n
  | .spec.template.spec.containers[0].env[]?
  | select(.name | test("BUCKET|ROOT"))
  | "\($n)\t\(.name)=\(.value)"'
```

The filter is broad on purpose, so it can also list unrelated variables, such as a root log level. A service that is missing from the output uses its image default. In your object store, confirm which buckets hold the data, for example with the MinIO console, `mc ls`, or `aws s3 ls`.

### Step 2: Set the names in your values

Set every name from step 1 under the keys in the table:

```yaml theme={"dark"}
services:
  application-manager:
    component:
      s3:
        main:
          bucketPrefix: "{your-bucket-prefix}"
  cms-core:
    component:
      s3:
        public:
          bucketName: "{your-public-media-bucket}"
          rootDirectory: "{your-public-root-directory}"
        private:
          bucketName: "{your-private-media-bucket}"
          rootDirectory: "{your-private-root-directory}"
  doc-parser:
    component:
      s3:
        documents:
          bucketName: "{your-documents-bucket}"
```

Leave a key unset only for a service that should keep its image default. If an earlier values file sets a name you want to clear, remove the key from that file. Do not set it to `null`.

Two differences from the per-service charts need attention when you copy values across:

* **Entry name.** The per-service core charts named the S3 entry `minio`. In `flowx-ai` the entry is `main`. Set the prefix under `component.s3.main` and remove the `minio` entry: if both are present, the chart fails to render with `Only one s3 entry may omit an alias`.
* **cms-core root directory.** The per-service chart also set a `rootDirectory` for each cms-core bucket. Files are stored under that directory, so carry over `rootDirectory` together with `bucketName`.

If you set the document-plugin prefix with the older `MINIO_BUCKET_PREFIX` environment variable, move it to `bucketPrefix`. document-plugin reads `MINIO_BUCKET_PREFIX` only as a fallback, so a prefix that the chart renders takes precedence.

Before the maintenance window, check the names in the render:

```bash theme={"dark"}
yq -o json 'select(.kind == "Deployment")' rendered.yaml | jq -rs '.[] | .metadata.name as $n
  | .spec.template.spec.containers[0].env[]?
  | select(.name | test("BUCKET|ROOT"))
  | "\($n)\t\(.name)=\(.value)"'
```

Every name from step 1 must appear with the same value.

### Step 3: Verify the bucket names after the cutover

**1. Check the running values**

Run the command from step 1 again and check each value.

**2. Open files uploaded before the migration**

Open a process attachment, a media library asset, a notification template asset, and a knowledge base document.

**3. Check the logs**

Look for `bucket not found` errors in the service logs. They point to a name mismatch.

> [!WARNING]
>
> Do not rename buckets or change root directories as part of this migration. The databases reference stored files by bucket and path, so keep every bucket name, prefix, and `rootDirectory` exactly as it is today.

***

## PVC behavior

qdrant is the only workload in the chart that stores data on a PersistentVolumeClaim (PVC). Its volumes survive the cutover and are reattached to the new StatefulSet, as long as the conditions in this section hold.

If the `base` group is off and `qdrant.enabled` is not set, or if you set `qdrant.enabled: false`, the chart deploys no qdrant StatefulSet. Existing qdrant claims then stay in the namespace unused, until you delete them or turn qdrant back on.

### Volumes the chart uses

| PVC | Mounted by | Default size | Values key |
| - | - | - | - |
| `qdrant-storage-qdrant-0` | qdrant, collection data | `30Gi` | `qdrant.persistence` |
| `qdrant-snapshots-qdrant-0` | qdrant, snapshots | `30Gi` | `qdrant.snapshotPersistence` |

Databases, Kafka, and the object store run in their own releases. The chart does not create, change, or delete their volumes.

### Why do the volumes survive?

* **PVC names do not change.** A StatefulSet names each claim `<volumeClaimTemplate>-<StatefulSet name>-<ordinal>`. The chart names the StatefulSet `qdrant` through `qdrant.fullnameOverride`, so the new StatefulSet requests the same claims and binds the existing volumes.
* **Deleting the StatefulSet keeps its PVCs.** Kubernetes applies the StatefulSet's `persistentVolumeClaimRetentionPolicy`. The qdrant chart does not set it, so the default `whenDeleted: Retain` applies. The StorageClass `reclaimPolicy` plays no part, because the PVCs are never deleted and the volumes are never released.
* **`helm uninstall` keeps them too.** Helm deletes only the objects in the release manifest, and PVCs that a StatefulSet creates from its templates are not in the manifest.

> [!WARNING]
>
> Do not delete the qdrant StatefulSet with `--cascade=orphan`. That option keeps the old pod running, but the new StatefulSet has a different selector and cannot adopt it, so it cannot start its own pod either.

### Before the cutover: check the volumes

**1. Confirm the claim names**

```bash theme={"dark"}
kubectl -n "$NS" get pvc | grep qdrant
```

Expect `qdrant-storage-qdrant-0` and, if snapshots were on, `qdrant-snapshots-qdrant-0`.

**2. Check the retention policy**

```bash theme={"dark"}
kubectl -n "$NS" get statefulset -o custom-columns=NAME:.metadata.name,WHEN_DELETED:.spec.persistentVolumeClaimRetentionPolicy.whenDeleted
```

An empty value or `Retain` is safe. If the value is `Delete`, the PVCs are deleted together with the StatefulSet. Change the policy to `Retain` before the cutover:

```bash theme={"dark"}
kubectl -n "$NS" patch statefulset qdrant --type merge \
  -p '{"spec":{"persistentVolumeClaimRetentionPolicy":{"whenDeleted":"Retain"}}}'
```

**3. Compare sizes and StorageClass**

Compare your existing claims with `qdrant.persistence` and `qdrant.snapshotPersistence`. The StatefulSet reuses an existing claim as is, whatever the template says, but matching values keep the release definition accurate.

**4. Decide on a snapshots claim**

If your installation never had a snapshots claim, decide whether you want one. With `qdrant.snapshotPersistence.enabled: true`, the default, the chart creates a new `30Gi` claim. Set it to `false` to keep the current layout.

### What if your claim names are different?

If the first check shows other names, for example because the qdrant StatefulSet carried a release prefix, the new StatefulSet does not find your volumes. It creates new, empty claims instead, and the AI services lose their indexed collections.

Before the cutover, do one of the following, and test it in a non-production environment first:

* Set `qdrant.fullnameOverride` to the existing StatefulSet name, and set `global.flowx.qdrant.servers.flowx.grpc.host` to that name's headless Service, `{existing-name}-headless`, so that both the claim names and the service address match. The chart default is `qdrant-headless`.
* Take a qdrant snapshot of each collection, migrate with new claims, and restore the snapshots afterwards.

### After the cutover

Confirm that the new pod uses the existing claims and that no new claim appeared:

```bash theme={"dark"}
kubectl -n "$NS" get pvc | grep qdrant
kubectl -n "$NS" get pod qdrant-0 -o jsonpath='{.spec.volumes[*].persistentVolumeClaim.claimName}'
```

The `AGE` column of each claim must show its original age.

***

## Other behavior changes

Review these changes against your installation before the cutover. Most need no action.

| Area | Change | What to do |
| - | - | - |
| **Labels** | `app.kubernetes.io/name` is `flowx-ai` on every platform service object, and the service name moves to `app.kubernetes.io/component`. `app.kubernetes.io/version` is no longer set on those objects. The bundled qdrant keeps its `app.kubernetes.io/name: qdrant` and `app.kubernetes.io/version` labels. | Update dashboards, alerts, network policies, and scripts that select on the old labels. |
| **Renamed objects** | The release ConfigMap is `flowx-ai-release` instead of `flowx-release`. The data-sync Job is named `data-sync-<chart-version>`, with dots in the version replaced by dashes, such as `data-sync-5-9-6`. | Delete the old `flowx-release` ConfigMap and data-sync Job after the cutover, if your deploy tool does not prune them. |
| **Web app rename** | The `react-saas` web app is renamed `solution-portal`, so its objects get the new name. With Ingress on, the chart also renders a transitional `react-saas-public` Ingress on an unused path, so that the renamed Ingress can take over the host. `migrations.reactSaasIngressOverride` controls it and is on by default. | Remove the old `react-saas` workload with the other old workloads. If an old release still owns a `react-saas-public` Ingress, remove it or hand it over to the `flowx-ai` release, or the install fails on ownership. |
| **SpiceDB** | The chart installs the SpiceDB operator and a SpiceDB cluster by default. | If SpiceDB already runs in your environment, set `spicedb-operator.enabled: false` and `spicedb-cluster.enabled: false`. Never run two SpiceDB clusters on one datastore. |
| **Identity provider clients** | events-gateway authenticates with its own client, `flowx-events-gateway-sa`. | Make sure the client and its secret exist in your realm. Without it, real-time updates fail with `403`. |
| **Identity provider URL** | Services use one identity provider URL from `global.flowx.idp`. FlowX Designer and the web app take their public URL from their own client entry. | If you point the global URL at an in-cluster address, set the public URL on the `designer` and `solution-portal` clients. Otherwise make sure pods can reach the public URL. |
| **Ingress routing** | In Ingress mode the chart does not strip path prefixes. Gateway API mode does, through `stripPrefix`. | In Ingress mode, add regex paths with `rewrite-target` annotations for services served under a prefix, such as `/license`, `/ai`, and `/integration`. Or use Gateway API mode. |
| **Ingress TLS** | An Ingress gets TLS when `tls` is `"true"`, which names the secret `<host>-tls`, or when `ingress.<type>.extraTls` is set. `global.flowx.ingress.<type>.tls` overrides the per-service `tls` value. | `<host>-tls` suits cert-manager. With a wildcard certificate, set `extraTls` on each service with your secret name. |
| **Image pull secrets** | The chart applies the global image pull secrets to every pod, AI services included. | Create the pull secrets in the namespace and list them in your `global` values. |
| **Probes** | Probe timings are set per service by the chart, and some AI services have no probes. | If you tuned probes in your previous values, compare `livenessProbe`, `readinessProbe`, and `startupProbe` in `rendered.yaml` with your running Deployments. |
| **CA certificates init container** | Services that import a certificate run a CA certificates init container that requests `100m` CPU and `128Mi` memory. By default these are admin, audit-core, data-search, document-plugin, and process-engine, which import the Elasticsearch CA. | Check namespace quotas if they are tight. |
| **Document storage** | The chart sets the file partition strategy of document-plugin and integration-designer to `PROCESS_DATE`, so new files are stored in date-partitioned paths. The per-service charts did not set it, and the service default is `NONE`. Existing files stay readable: each service stores a file's full path when it saves the file and reads it back from that path. With `PROCESS_DATE`, document-plugin rejects Kafka document requests that have no `processStartDate` header and sends an error to the reply topic. The process engine sends this header. | If you migrate from the per-service charts, expect `APPLICATION_FILESTORAGE_PARTITIONSTRATEGY=PROCESS_DATE`, as rendered by the chart, in the render diff for these two services. If your own apps publish directly to the document-plugin topics, make sure they send the `processStartDate` header. To keep the previous layout, set `APPLICATION_FILESTORAGE_PARTITIONSTRATEGY: "NONE"` under `services.document-plugin.env` and `services.integration-designer.env`. |

***

## Run the cutover

The cutover replaces the old releases with the `flowx-ai` release. How you carry it out depends on your deployment tooling: this section describes what the cutover must achieve.

Complete [Convert your values](#convert-your-values), [Migrate S3 buckets](#migrate-s3-buckets), the checks in [PVC behavior](#pvc-behavior), and the review of [Other behavior changes](#other-behavior-changes) first.

### Prepare

**1. Freeze the namespace**

Freeze all other changes to the namespace, so that the cutover changes only the charts.

**2. Take the backups**

Take the backups listed in [Prerequisites](#prerequisites).

**3. Keep the old definitions**

Keep a copy of the old releases' values and rendered manifests. You need them to [roll back](#roll-back).

**4. Rehearse**

Measure how long the cutover takes in a test environment, and size the maintenance window from it.

### What must the cutover do?

**1. Stop the old releases from managing their workloads**

Make sure nothing recreates the old Deployments or StatefulSet while you replace them, for example an automated sync or a scheduled pipeline.

**2. Remove the old Deployments and the qdrant StatefulSet**

Their selectors cannot change, so they must be deleted before the new release can create its own. **The outage starts here.**

* Remove only the platform workloads. If your databases, Kafka, or object store run in the same namespace, make sure their StatefulSets are not caught by the same operation.
* Delete the qdrant StatefulSet with normal cascading deletion, not as an orphan. See [PVC behavior](#pvc-behavior). If the `flowx-ai` release keeps the release name of your `ai-platform` release, the qdrant selector does not change and the StatefulSet can stay.

**3. Hand over or remove the other objects of the old releases**

Services, Ingresses, ServiceAccounts, ConfigMaps, and RBAC objects keep their names in `flowx-ai`, but Helm refuses to install an object that another release owns. Either remove them with the old releases, or transfer their ownership to the new release.

Helm reads ownership from the annotations `meta.helm.sh/release-name` and `meta.helm.sh/release-namespace` and the label `app.kubernetes.io/managed-by: Helm`. To hand an object over, set the two annotations to the `flowx-ai` release name and namespace.

**4. Preserve what the new release does not create**

If the old releases created Secrets or ConfigMaps through their values that your services still need, recreate them outside the old releases, or make sure your `flowx-ai` values create them.

**5. Install the flowx-ai release**

Install it with your new values. The outage ends when every Deployment is available and the qdrant pod has reattached its volumes.

**6. Remove objects that changed name**

The release ConfigMap `flowx-release` is replaced by `flowx-ai-release`, and the data-sync Job gets a new name that includes the chart version. Jobs are immutable, so the old data-sync Job is not updated in place. After the cutover, delete the old objects if your tooling does not prune them.

> [!TIP]
>
> With plain Helm, the simplest cutover is: recreate outside Helm any Secrets the old releases created, run `helm uninstall` for each old release (the qdrant claims are kept), then run `helm install {your-release-name} {flowx-ai-chart-reference} --version {chart-version} -n "$NS" -f values-flowx-ai.yaml`.

> [!NOTE]
>
> Do not leave a force-replace or recreate option turned on permanently in your tooling to get through the removal step. It would delete and recreate workloads on every later deployment, not only during the cutover.

***

## Verify the migration

Run these checks right after the cutover, and again after the first day of normal use:

* Every Deployment is available, and only the services of your enabled groups run: `kubectl -n "$NS" get deploy`.
* No workload from an old release remains: `kubectl -n "$NS" get deploy,statefulset -L app.kubernetes.io/name,app.kubernetes.io/instance` shows `flowx-ai` on every platform workload and `qdrant` on the qdrant StatefulSet, all with your release name as the instance.
* The qdrant pod uses the original claims. See [After the cutover](#after-the-cutover).
* The bucket variables match the names you set. See [Step 3: Verify the bucket names after the cutover](#step-3-verify-the-bucket-names-after-the-cutover).
* You can sign in to FlowX Designer and to a deployed app.
* A process instance starts, and its UI updates in real time. This exercises events-gateway.
* A file uploaded before the migration opens, and a new upload succeeds.
* If AI groups are on, an AI feature answers, for example a chat through ai-gateway or a knowledge base query.
* Scheduled jobs run at their next trigger.
* The service logs show no `bucket not found`, `401`, `403`, or connection errors that were not there before.
* Your monitoring still finds the services. Dashboards, alerts, and scripts that select on `app.kubernetes.io/name=<service>` must now select on `app.kubernetes.io/component=<service>`.

***

## Roll back

A rollback crosses the same selector change in the other direction, so it is also a delete and recreate. A Helm release history rollback cannot do it, because the old and new workloads belong to different releases.

The qdrant PVCs were never deleted, so the old releases reattach them. If the new services changed data that the old versions cannot read, restore the backup you took before the window.

**1. Remove the flowx-ai release**

Stop your tooling from managing the `flowx-ai` release, then run `helm uninstall` for it, or remove its workloads and the objects it owns. The qdrant claims are kept. If the release installed the SpiceDB operator and cluster, they are removed with it: make sure the old releases bring back the SpiceDB setup you ran before.

**2. Restore the datastores if needed**

Restore PostgreSQL, MongoDB, and the object store from the backup.

**3. Reinstall the old releases**

Use their previous values, at the chart versions you ran before.

**4. Check qdrant**

Confirm that the qdrant pod reattached its claims. If you need to restore the qdrant collections, restore them from the snapshots now, while qdrant is running.

> [!NOTE]
>
> Files written while `flowx-ai` was running go to the buckets that its values named. If those names differed from the old ones, the old releases cannot see those files after a rollback. Carrying over the bucket names as described in [Migrate S3 buckets](#migrate-s3-buckets) avoids this. document-plugin and integration-designer also stored new files in date-partitioned paths. Test the rollback in a non-production environment before you rely on it.

Keep the old values and manifests until you have signed off the migration.

***

## Troubleshooting

> **The render fails with top-level devtime/runtime flags moved**
>
> Your values still contain the `devtime` or `runtime` key of the `ai-platform` umbrella chart. Replace them with `groups.devtime.enabled`, `groups.runtime.enabled`, and `services.<name>.enabled`, as described in [How do the umbrella flags map?](#how-do-the-umbrella-flags-map).

> **The render fails with Only one s3 entry may omit an alias**
>
> A service has both the old `minio` S3 entry and the new `main` entry. Move the settings to `component.s3.main` and remove the `minio` entry.

> **The install fails with invalid ownership metadata**
>
> An object from an old release still exists, and Helm will not take it over. Remove it, or hand it over by setting the `meta.helm.sh/release-name` and `meta.helm.sh/release-namespace` annotations to the `flowx-ai` release. See [What must the cutover do?](#what-must-the-cutover-do).

> **The upgrade fails with field is immutable**
>
> You ran `helm upgrade` against an existing Deployment or StatefulSet whose selector the new chart changes. Delete the old workload first, as described in [What must the cutover do?](#what-must-the-cutover-do).

> **Files are missing, or the logs show bucket not found**
>
> A bucket name or prefix did not carry over. Compare the values from [Step 1: Find the names in use](#step-1-find-the-names-in-use) with the running Deployments, set the missing names, and upgrade the release. Files stored under the old name are still in the old bucket.

> **qdrant starts with an empty collection set**
>
> The new StatefulSet created new claims instead of reattaching yours. Check the claim names as described in [What if your claim names are different?](#what-if-your-claim-names-are-different), or restore the collections from your qdrant snapshots.

***

## Related resources

> **[AI Platform setup](./ai-platform-setup)**
>
> AI services, configuration, and deployment

> **[Qdrant setup](./qdrant)**
>
> Vector database configuration

> **[SpiceDB](./spicedb)**
>
> Authorization service setup

> **[5.9.6 release notes](/release-notes/v5.x/v5.9.6-october-2026/v5.9.6-october-2026#upgrade-notes)**
>
> Upgrade notes for FlowX.AI 5.9.6


## Related topics

- [FlowX.AI 5.9.0 Release Notes](/release-notes/v5.x/v5.9.0-june-2026/v5.9.0-june-2026.md)
- [FlowX.AI 5.9.4 Release Notes (LTS)](/release-notes/v5.x/v5.9.4-august-2026/v5.9.4-august-2026.md)
- [Migrate process instances](/5.9/docs/projects/runtime/active-process/migrate-process-instances.md)
- [FlowX.AI 5.12.0 Release Notes](/release-notes/v5.x/v5.12.0-september-2026/v5.12.0-september-2026.md)
- [AI Platform setup](/5.9/setup-guides/ai-platform-setup.md)


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