> ## 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.

# Configure the flowx-ai Helm chart for your infrastructure

> Connect flowx-ai Helm chart services to your databases, identity provider, and CA certificates with definitions, selectors, Kubernetes secrets, and render guards.

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

The `flowx-ai` chart turns a small set of values into the environment of every service: database URLs, credentials, identity provider settings, and trusted certificates. This page explains the model behind those values so you can point the chart at your own infrastructure and read what it renders.

For the `global`, `groups`, and `services` layout, and for moving from the per-service or `ai-platform` charts, see [Migrate self-hosted FlowX.AI to the flowx-ai Helm chart](https://docs.flowx.ai/5.9/setup-guides/migrate-to-flowx-ai-helm-chart.md).

***

## How do values reach a service?

Each service renders from its own `services.<name>` block plus the whole `global` block. Nothing else in the values tree is visible to it.

| Layer | What it holds | Who reads it |
| - | - | - |
| `global` | Image registry, pod defaults, `global.env` | Every service |
| `global.flowx` | Connection definitions, identity provider, certificates, domains | Every service |
| `groups.<group>` | One switch per service group | The chart, to decide which services deploy |
| `services.<name>` | The image version, `env`, and the `component` block that selects what the service connects to | That service only |

When the same setting exists in more than one place, these rules apply:

* **Environment variables:** `global.env` and `services.<name>.env` are merged, and the service value wins for the same name.
* **Connection definitions:** the chart reads servers and credentials from `global.flowx.<dependency>`. The chart already ships a definition there for PostgreSQL, MongoDB, Redis, Elasticsearch, S3, SpiceDB, SMTP, and Qdrant, so a definition placed under `services.<name>.<dependency>` is not read. Change the global one.
* **Chart defaults:** Helm merges your values file into the chart defaults key by key. Set only the keys you change, for example `global.flowx.postgresql.servers.flowx.host`, and keep the rest.

***

## How does a service connect to a dependency?

Every dependency connection has two halves: a **definition**, which says where a server is and how to log in, and a **selector**, which says which service uses it. This schema is defined by the `flowx-ai` chart. It is not a Kubernetes or Helm convention.

**1. Define the server**

Under `global.flowx.<dependency>.servers.<key>`, set the connection details, such as `host` and `port`. The key is a name you choose, for example `flowx`.

**2. Define the credentials**

Under `global.flowx.<dependency>.credentials.<key>`, set the login, either as plain values or from a Kubernetes secret. One credentials entry usually belongs to one service.

**3. Select them in the service**

Under `services.<name>.component.<dependency>.<entry>`, reference the server with `serverRef` and the credentials with `credentials`, then add what is specific to this service, such as `database`.

Several services can share one server definition and still log in with their own credentials and use their own database.

### Which selector fields can I set?

PostgreSQL selectors take these fields. The other dependencies follow the same pattern with fields of their own.

| Field | Description | Default |
| - | - | - |
| **`serverRef`** | Key of a server under `global.flowx.postgresql.servers` | - |
| **`credentials`** | Key of an entry under `global.flowx.postgresql.credentials` | - |
| **`database`** | Database name. Required when the URL is built from the server definition | - |
| **`url`** | A complete connection URL. When set, it replaces the URL built from `serverRef` and `database`. Java services only | - |
| **`alias`** | Distinguishes a second connection of the same service. See [Several connections to one dependency](#several-connections-to-one-dependency) | None |
| **`prefix`** and **`renderingTemplate`** | Control the names under which the connection is rendered. Leave them unset unless the chart sets them: the service expects the names it renders | Set by the chart |

The chart builds the PostgreSQL URL as `jdbc:postgresql://<host>:<port>/<database><parameters>`, where `port` defaults to `5432` and `parameters` is an optional string on the server definition, such as `?sslmode=require`.

> [!WARNING]
>
> A `serverRef` or `credentials` value that matches no key is not a render error. For PostgreSQL and MongoDB, the service then gets no connection URL or no credentials, and fails when it starts. After you rename a server or credentials key, update every selector that references it.

### Several connections to one dependency

A service can select more than one entry for the same dependency, for example the main database and a second one. Only one entry per dependency may omit `alias`: the unaliased entry is the service's main connection, and each aliased entry is rendered under its alias. The render fails when two entries have no alias.

The `process-engine` service ships with two PostgreSQL entries:

```yaml theme={"dark"}
services:
  process-engine:
    component:
      postgresql:
        processEngine:
          serverRef: "flowx"
          credentials: "processEngine"
          database: "process_engine"
        advancingController:
          serverRef: "flowx"
          credentials: "advancingController"
          database: "advancing_controller"
          alias: "advancing"
```

Keep the aliases the chart sets. A service reads each connection under the alias it expects, so a renamed alias leaves that connection unconfigured.

### Example: point the services at your PostgreSQL server

The chart's selectors already reference a server named `flowx` and one credentials entry per service. To use your own PostgreSQL server, override the server definition and, where needed, the credentials:

```yaml theme={"dark"}
global:
  flowx:
    postgresql:
      servers:
        flowx:
          host: "postgres.db.svc.cluster.local"
          port: 5432
          parameters: "?sslmode=require"
      credentials:
        processEngine:
          username: "process_engine"
          existingSecret:
            name: "flowx-postgres-process-engine"
            keyMapping:
              password: password
```

Every service whose selector uses `serverRef: "flowx"` now connects to `postgres.db.svc.cluster.local`. With these values, `process-engine` connects to `jdbc:postgresql://postgres.db.svc.cluster.local:5432/process_engine?sslmode=require` as `process_engine`, and reads the password from the `password` key of the `flowx-postgres-process-engine` secret.

To send one service to a different server, add a second server definition and change only that service's `serverRef`:

```yaml theme={"dark"}
global:
  flowx:
    postgresql:
      servers:
        reporting:
          host: "pg-reporting.db.svc.cluster.local"
services:
  document-plugin:
    component:
      postgresql:
        documentPlugin:
          serverRef: "reporting"
```

Because Helm merges values key by key, `document-plugin` keeps the `credentials` and `database` of its `documentPlugin` entry and changes only its server.

***

## How do I keep passwords in Kubernetes secrets?

Any definition that holds a secret value, such as credentials or a client secret, can read it from an existing Kubernetes secret through `existingSecret`. The chart never creates these secrets: create them before you install.

```yaml theme={"dark"}
credentials:
  license:
    username: license
    existingSecret:
      name: flowx-postgres-license
      keyMapping:
        password: password
```

For each field, the chart decides where the value comes from:

| `existingSecret` | `keyMapping` | Result |
| - | - | - |
| Set | Lists the field | Read from the secret, under the key the mapping gives |
| Set | Present, but doesn't list the field | Taken from the plain value, such as `username: license` above |
| Set | Absent | Read from the secret, under a key named like the field, for example `password` |
| Not set | - | Taken from the plain value |

An object has one `existingSecret`, so all its secret fields come from the same Kubernetes secret. To keep a username in a secret as well, list it in `keyMapping`:

```yaml theme={"dark"}
existingSecret:
  name: flowx-postgres-license
  keyMapping:
    username: username
    password: password
```

> [!TIP]
>
> Prefer `existingSecret` over plain passwords in your values file. Plain values end up in the rendered manifests and in the Helm release history.

***

## What does configmap mode change?

By default, every rendered setting is an environment variable in the pod spec. With configmap mode on, the chart moves the plain settings into a ConfigMap and keeps only the secret references in the pod spec:

| | Configmap mode off (default) | Configmap mode on |
| - | - | - |
| Plain settings, such as URLs and client IDs | Environment variables in the container spec | A ConfigMap named `<service>-env`, attached with `envFrom` |
| Secret settings, such as passwords | Environment variables that reference the secret | Unchanged: environment variables that reference the secret |

Turn it on for every service with `global.flowx.configmapMode: true`, or for one service with `services.<name>.configmapMode: true`.

**Default:** `false`

> [!NOTE]
>
> `global.flowx.configmapMode` takes precedence over the service value when it is set. To turn configmap mode on for a single service, leave the global key unset.

***

## How do I update one service to a newer version?

The chart builds each service's image tag from `services.<name>.version` and sets it to the version that ships with the platform release. To roll out a fix for one service without upgrading the platform, set that key to the version FlowX gives you, then run `helm upgrade` with the chart version you already run:

```yaml theme={"dark"}
services:
  process-engine:
    version: "{patched-version}"
```

If the service also has `image.tag` set, the chart uses that tag instead, so remove it.

Remove the `version` key again when you upgrade the chart. Your values override the chart defaults on every upgrade, so a leftover key keeps that service on the old image while the rest of the platform moves to the new release.

***

## How do I read what the chart rendered?

Render the chart locally and look at the environment of one service:

```bash theme={"dark"}
helm template flowx-ai <chart> -f values.yaml --show-only templates/deployment.yaml
```

The chart writes a comment before each block it generates, naming the source of the block, for example `# postgresql: processEngine` for the selector entry `processEngine`, `# oauth client: admin` for a client, or `# java resource server variables`. With configmap mode on, the comments appear in the `<service>-env` ConfigMap instead of the pod spec.

***

## Why does the render stop with an error?

The chart checks each enabled service before rendering it. A check that fails stops the whole render with a message that starts with the service name in brackets, so nothing reaches the cluster in a half-configured state.

| Message | What to fix |
| - | - |
| `[<service>] IDP setup incorrect. Valid configurations are keycloak:opaque \| keycloak:jwt \| entraid:jwt` | The resource server the service selects has a `securityType` that the provider doesn't support. See [Resource servers](#resource-servers) |
| `[<service>] IDP host setup incorrect. Configure idp.keycloak.url.host or idp.keycloak.subDomain + .rootDomain` | The Keycloak URL can't be built. Set `global.flowx.idp.keycloak.url.host`, or both `global.flowx.idp.keycloak.subDomain` and `global.flowx.rootDomain` |
| `[<service>] IDP host setup incorrect. Configure idp.keycloak.url.host or .rootDomain` | The same, with `global.flowx.singleDomain: true` |
| `[<service>] IDP entraid setup incorrect. Configure idp.entraid.tenantId` | Set `global.flowx.idp.entraid.tenantId` |
| `[<service>] idp.entraid.appRolesobjectId required when ...` | Set `global.flowx.idp.entraid.appRolesObjectId` for services that call Microsoft Graph |
| `[<service>] Only one PostgreSQL entry may omit an alias` | Give every extra entry an `alias`. The same check exists for Mongo, CosmosMongo, s3, and redis |
| `[<service>] kafka.security.tls.existingSecret required when kafka.security.tls is true` | Point the Kafka TLS settings at the secret that holds the CA |
| `[<service>] cannot enable kafka mtls without tls` | Turn on Kafka TLS before mutual TLS |
| `[<service>] Host for admin ingress not computable. ...` | The service's admin hostname can't be built from its hostname, subdomain, and root domain. The same check exists for the public and admin instances routes |

The chart also stops when the values file still has the old layout: top-level `devtime` or `runtime` flags, service keys at the root instead of under `services:`, a root-level `image` or `podLabels`, or a service whose `group` isn't declared under `groups:`. These keys would otherwise be ignored without any warning. The message names the key and where it moved.

***

## How do I trust a private certificate authority?

When a dependency presents a certificate from a private certificate authority (CA), the services that call it need that CA in their trust store. The chart adds it with an init container:

**1. Store the CA in a secret**

Create a Kubernetes secret that holds the CA certificate in PEM format.

**2. Define the certificate**

Add an entry under `global.flowx.certs` with `type: ca`, the secret name, and one `keyMapping` entry that points to the key of the certificate in the secret.

**3. Select it in each service**

List the entry in `services.<name>.component.certs` for every service that calls the dependency.

```yaml theme={"dark"}
global:
  flowx:
    certs:
      internal-ca:
        type: ca
        existingSecret:
          name: internal-ca-cert
          keyMapping:
            ca: ca.crt
services:
  integration-designer:
    component:
      certs: ["internal-ca"]
```

Before the service starts, the init container builds a trust store and mounts it over the default one:

| Runtime | What the init container does | Mounted at |
| - | - | - |
| Java | Copies the image's Java trust store and imports each selected CA into it | `/opt/java/openjdk/lib/security/cacerts` |
| Python | Appends each selected CA to the system CA bundle | `/etc/ssl/certs/ca-certificates.crt` |

The chart ships one definition, `elasticsearch`, which reads `ca.crt` from the `elasticsearch-es-http-certs-public` secret. It is selected by admin, audit-core, data-search, document-plugin, and process-engine. When you add a certificate to a service that already selects `elasticsearch`, keep it in the list: `certs: ["elasticsearch", "internal-ca"]`.

| Setting | Description | Default |
| - | - | - |
| **`global.flowx.certsInitContainer.enabled`** | Turns the init container on for all services. It runs only for services that select at least one certificate | `true` |
| **`global.flowx.certsInitContainer.resources`** | Resources of the init container | Requests: `100m` CPU, `128Mi` memory |
| **`services.<name>.component.certsMountPathOverride`** | Directory to mount the trust store in, for images whose Java runtime or CA bundle is somewhere else. The chart already sets it where an image needs it: keep that value | None |

> [!NOTE]
>
> Only entries with `type: ca` are imported. Each entry uses one key of its secret: when `keyMapping` lists several, the first one in alphabetical order is used. Use one entry per CA.

***

## Configure the identity provider

The chart renders the identity provider settings of every service from `global.flowx.idp`. A service reads three parts of it: the provider, the clients it selects, and the resource server it selects.

### Choose the provider

Set `global.flowx.idp.provider` to `keycloak` or `entraid`.

**Default:** `keycloak`

**Keycloak**

| Setting | Description | Default |
| - | - | - |
| **`global.flowx.idp.keycloak.realm`** | Realm the FlowX.AI clients live in | `flowx` |
| **`global.flowx.idp.keycloak.url.host`** | Keycloak host name. When set, it is used as is | None |
| **`global.flowx.idp.keycloak.url.protocol`** | Protocol of the Keycloak URL | `https` |
| **`global.flowx.idp.keycloak.url.port`** | Port of the Keycloak URL. `0` or unset leaves the port out | None |
| **`global.flowx.idp.keycloak.subDomain`** | Used to build the host as `<subDomain>.<rootDomain>` when `url.host` isn't set | `auth` |
| **`global.flowx.idp.keycloak.contextPath`** | Path Keycloak is served under | `/auth` |

Without `url.host`, the Keycloak URL is `https://auth.<rootDomain>` by default, built from `global.flowx.rootDomain`. With `global.flowx.singleDomain: true`, it is `https://<rootDomain>`.

```yaml theme={"dark"}
global:
  flowx:
    idp:
      provider: "keycloak"
      keycloak:
        realm: "flowx"
        url:
          host: "auth.example.com"
          protocol: "https"
        contextPath: "/auth"
```

For the realm, clients, and roles to create in Keycloak, see [Configuring an IAM solution](https://docs.flowx.ai/5.9/setup-guides/access-management/configuring-an-iam-solution.md).

**Microsoft Entra ID**

| Setting | Description | Default |
| - | - | - |
| **`global.flowx.idp.entraid.tenantId`** | Directory (tenant) ID. Required with `provider: entraid` | `""` |
| **`global.flowx.idp.entraid.appRolesObjectId`** | Object ID used by services that read app roles through Microsoft Graph. Required for the services that set `component.usesMicrosoftGraph: true` | `""` |

For the Entra ID side, see [Configuring Microsoft Entra ID](https://docs.flowx.ai/5.9/setup-guides/access-management/configuring-an-iam-solution-entra.md).

### Clients

A client is an identity a service uses to call the identity provider. Like connections, clients have a definition and a selector, and a service needs both:

* **Definition:** `global.flowx.idp.clients.<key>`, with `grantType`, `clientId`, and the client secret.
* **Selector:** `services.<name>.component.idpClients`, a list of client keys.

```yaml theme={"dark"}
global:
  flowx:
    idp:
      clients:
        admin:
          alias: "mainIdentity"
          grantType: "client_credentials"
          clientId: flowx-admin-sa
          existingSecret:
            name: "keycloak-flowx-realm-clients"
            keyMapping:
              clientSecret: "KEYCLOAK_ADMIN_CLIENT_SECRET"
services:
  admin:
    component:
      idpClients: ["admin", "anonymous"]
```

| Field | Description |
| - | - |
| **`grantType`** | `client_credentials` for a service account that a backend service uses to call other services, `authorization_code` for the browser login of FlowX Designer and the solution portal |
| **`clientId`** | Client ID in the identity provider |
| **`alias`** | Name the service registers the client under. Backend services read their own service account as `mainIdentity` and the anonymous one as `anonymousIdentity`: keep these names |
| **`existingSecret`** | Secret that holds the client secret, under `keyMapping.clientSecret` |
| **`scopes`** | Scopes to request. The `designer` and `solution-portal` clients request `openid profile email` |
| **`url`** | Identity provider URL for this client only, when it differs from the global Keycloak URL |

The chart ships a client for each backend service, plus `anonymous`, `designer`, and `solution-portal`. All service account secrets are read from the `keycloak-flowx-realm-clients` secret, one key per client, such as `KEYCLOAK_ADMIN_CLIENT_SECRET` or `KEYCLOAK_PROCESS_ENGINE_CLIENT_SECRET`. Create that secret with the client secrets from your identity provider, or point each client's `existingSecret` at your own secret.

### Resource servers

A resource server is how a service checks the access tokens it receives. A service selects one with `services.<name>.component.resourceServer`, and the chart reads its settings from `global.flowx.idp.resourceServers.<key>`.

**Default selection:** `default`

The `securityType` of the selected resource server must match the provider:

| Provider | `securityType` | How tokens are checked |
| - | - | - |
| `keycloak` | `jwt` | Locally, against the Keycloak signing keys |
| `keycloak` | `opaque` | By asking Keycloak about each token (token introspection), with the `opaque.clientId` client and its secret |
| `entraid` | `jwt` | Locally, against the Entra ID signing keys of the tenant. Set `jwt.audience` to the expected audience |

Any other combination stops the render with `IDP setup incorrect`.

The 5.9.6 chart's values define only one resource server, `aiPlatform`, while every service selects `default`. To set how the core services check tokens, define `default`:

```yaml theme={"dark"}
global:
  flowx:
    idp:
      resourceServers:
        default:
          securityType: "jwt"
```

> [!WARNING]
>
> Use `securityType: jwt` for the `default` resource server. With `opaque`, the Java services also need a token introspection URL, which the chart doesn't render, and they fail at startup with `Could not resolve placeholder 'spring.security.oauth2.resource-server.opaque-token.introspection-uri'`.

***

## How do I connect to Kafka with SASL?

By default, the chart connects every service to Kafka with OAuth: `global.flowx.kafka.security.authentication.type` is `oauth`, with the `flowx-service-client` client in the `infra` realm. To log in with a username and password instead, switch the type to `sasl`:

```yaml theme={"dark"}
global:
  flowx:
    kafka:
      bootstrapServers: "kafka.example.com:9093"
      security:
        enabled: true
        authentication:
          enabled: true
          type: sasl
          sasl:
            mechanism: SCRAM-SHA-512
            username: flowx
            existingSecret:
              name: flowx-kafka-sasl
              keyMapping:
                password: password
        tls:
          enabled: true
          existingSecret:
            name: kafka-ca
            keyMapping:
              ca: ca.crt
```

| Setting | Description | Default |
| - | - | - |
| **`security.authentication.sasl.mechanism`** | `SCRAM-SHA-256` or `SCRAM-SHA-512` | None |
| **`security.authentication.sasl.username`** | Kafka user | None |
| **`security.authentication.sasl.password`** or **`existingSecret`** | Password, as a plain value or from a secret under the `password` key | None |
| **`security.authentication.sasl.producer`** and **`.consumer`** | Separate credentials for the producer and the consumer, each with `username` and `password` or `existingSecret` | None |
| **`security.tls.enabled`** | Connect over TLS. Requires `security.tls.existingSecret`, with the broker's CA under the `ca` key | Off |

All settings are under `global.flowx.kafka`. With TLS on, the services connect with `SASL_SSL`, otherwise with `SASL_PLAINTEXT`. When you set `producer` or `consumer`, the chart uses only those credentials and ignores `sasl.username` and `sasl.password`, so set both.

> [!WARNING]
>
> The chart configures SASL for the Java services only. The AI services that run on Python get Kafka client security only with `type: oauth`: with `sasl`, the chart renders no Kafka authentication for them. Keep that in mind before you remove the OAuth listener from your Kafka cluster.

***

## How do I use Redis Sentinel or Redis Cluster?

The chart's services select the `default` Redis server, a standalone Redis at `redis-master:6379`, through `services.<name>.component.redis.cache`. To use Redis Sentinel or Redis Cluster, change the `default` server definition. The chart picks the connection mode in this order, and the first one that is set wins:

1. `url` on the service's selector
2. `host` on the server
3. `sentinel` on the server
4. `cluster` on the server

Because Helm merges your values into the chart's, the `default` server keeps `host: redis-master` unless you clear it. Set `host: ""` for Sentinel or Cluster, or the services keep connecting to the standalone host.

> [tab] Sentinel
>
> ```yaml theme={"dark"}
> global:
>   flowx:
>     redis:
>       servers:
>         default:
>           host: ""
>           sentinel:
>             master: mymaster
>             nodes:
>               - "redis-sentinel-0.redis-sentinel:26379"
>               - "redis-sentinel-1.redis-sentinel:26379"
>               - "redis-sentinel-2.redis-sentinel:26379"
> ```
>
> The services log in to Sentinel with the credentials entry their selector names in `sentinelCredentials`, which is `root` in the chart, the same as for Redis. To use a separate Sentinel password, add a credentials entry under `global.flowx.redis.credentials` and point each service's `component.redis.cache.sentinelCredentials` at it.

> [tab] Cluster
>
> ```yaml theme={"dark"}
> global:
>   flowx:
>     redis:
>       servers:
>         default:
>           host: ""
>           cluster:
>             nodes:
>               - "redis-cluster-0.redis-cluster:6379"
>               - "redis-cluster-1.redis-cluster:6379"
>               - "redis-cluster-2.redis-cluster:6379"
> ```

The password comes from the `root` credentials entry, which reads the `redis-password` key of the `flowx-redis` secret. Change `global.flowx.redis.credentials.root` to read it from your own secret.

***

## How do I use Oracle instead of PostgreSQL?

The chart can render an Oracle connection for the Java services. The chart ships no Oracle definitions and every Java service selects PostgreSQL, so you add the definitions, then replace each service's PostgreSQL selector with an Oracle one. Check each service's setup guide for Oracle support before you switch it.

**1. Define the server and the credentials**

```yaml theme={"dark"}
global:
  flowx:
    oracle:
      servers:
        flowx:
          host: "oracle.db.example.com"
          port: 1521
      credentials:
        processEngine:
          username: "process_engine"
          existingSecret:
            name: "flowx-oracle-process-engine"
            keyMapping:
              password: password
        advancingController:
          username: "advancing_controller"
          existingSecret:
            name: "flowx-oracle-advancing-controller"
            keyMapping:
              password: password
```

The chart builds the URL as `jdbc:oracle:thin:@//<host>:<port>/<database><parameters>`, where `port` defaults to `1521` and `parameters` is an optional string on the server definition. Set `url` on a selector to use a complete URL instead.

**2. Replace the PostgreSQL selector of each service**

Set `component.postgresql` to `~` to remove the chart's PostgreSQL selector, and add an Oracle entry for each connection the service had. Keep the entry keys and aliases of the PostgreSQL selector:

```yaml theme={"dark"}
services:
  process-engine:
    component:
      postgresql: ~
      oracle:
        processEngine:
          serverRef: "flowx"
          credentials: "processEngine"
          database: "{process-engine-service-name}"
        advancingController:
          serverRef: "flowx"
          credentials: "advancingController"
          database: "{advancing-service-name}"
          alias: "advancing"
```

`database` is the Oracle service name in the URL. The FlowX.AI Engine and the Advancing Controller must use the same database type, so move `process-engine`, `advancing-controller`, and the advancing connection of `integration-designer` together. See [Advancing controller setup](https://docs.flowx.ai/5.9/setup-guides/advancing-controller-setup-guide.md).

**3. Switch data-sync too**

The data-sync job reads the databases of process-engine, application-manager, authorization-system, and license. Under `dataSync.component`, set `postgresql: ~` and add an `oracle` entry for each of them that moved, with the alias the job uses (`engine`, `appmanager`, `authsystem`, or `license`) and `renderingTemplate: "FLOWX_DATASOURCE_{{ .alias }}"`, as in the job's PostgreSQL entries.

> [!NOTE]
>
> The AI services that run on Python don't connect to Oracle through the chart.

***

## How do I scale a service and keep it available?

Each service has its own replica count, autoscaler, and disruption budget under `services.<name>`. All are off by default, and every service runs `global.replicaCount` replicas, which is `1` unless you change it.

| Setting | Description | Default |
| - | - | - |
| **`replicaCount`** | Replicas of this service when autoscaling is off | `global.replicaCount` |
| **`autoscaling.enabled`** | Renders a HorizontalPodAutoscaler for the service | `false` |
| **`autoscaling.minReplicas`** | Lowest replica count | `1` |
| **`autoscaling.maxReplicas`** | Highest replica count. Required when autoscaling is on | None |
| **`autoscaling.targetCPUUtilizationPercentage`** | Average CPU use, as a percentage of the CPU request, that the autoscaler keeps | None |
| **`autoscaling.targetMemoryUtilizationPercentage`** | The same, for memory | None |
| **`podDisruptionBudget.enabled`** | Renders a PodDisruptionBudget for the service | `false` |
| **`podDisruptionBudget.minAvailable`** | Pods that must stay up during a voluntary disruption, such as a node drain | None |
| **`podDisruptionBudget.maxUnavailable`** | Pods that may be down during a voluntary disruption. Set this or `minAvailable` | None |

```yaml theme={"dark"}
services:
  process-engine:
    autoscaling:
      enabled: true
      minReplicas: 2
      maxReplicas: 6
      targetCPUUtilizationPercentage: 75
    podDisruptionBudget:
      enabled: true
      minAvailable: 1
```

With autoscaling on, the chart leaves `replicas` out of the Deployment so that the autoscaler owns it, and `replicaCount` has no effect. Utilization targets are percentages of the container's resource requests, so the service needs CPU or memory requests for the target you set. A `minAvailable` equal to the replica count blocks node drains: keep it below `minReplicas`.

***

## How do I restrict network traffic to a service?

Set `services.<name>.networkPolicy.enabled: true` to render a NetworkPolicy for the service. By default, it allows inbound traffic from anywhere, but only to the ports under `services.<name>.service.ports`, which is the HTTP port `8080` by default.

| Setting | Description | Default |
| - | - | - |
| **`networkPolicy.enabled`** | Renders the NetworkPolicy | `false` |
| **`networkPolicy.allowExternal`** | `true` allows the service ports from any source. `false` allows them only from pods labeled `<service>-client: "true"`, from the service's own pods, and from the namespaces in `ingressNSMatchLabels` | `true` |
| **`networkPolicy.ingressNSMatchLabels`** | Labels of namespaces whose pods may connect when `allowExternal` is `false` | None |
| **`networkPolicy.ingressNSPodMatchLabels`** | Narrows `ingressNSMatchLabels` to pods with these labels | None |
| **`networkPolicy.extraIngress`** | More ingress rules, in NetworkPolicy syntax | None |
| **`networkPolicy.extraEgress`** | Egress rules. Setting it restricts outbound traffic: the service can then reach only DNS and the destinations you list | None |

> [!WARNING]
>
> The policy doesn't open the management port, where Prometheus scrapes metrics. On the Java services that is port `8081`. The AI services set their own, under `services.<name>.mngt.service.ports.mngt.targetPort`. If you [collect metrics with Prometheus](#how-do-i-collect-metrics-with-prometheus), add a rule for it:
>
> ```yaml theme={"dark"}
> services:
>   process-engine:
>     networkPolicy:
>       enabled: true
>       extraIngress:
>         - ports:
>             - port: 8081
>           from:
>             - namespaceSelector:
>                 matchLabels:
>                   kubernetes.io/metadata.name: monitoring
> ```
>
> With `extraEgress`, list every dependency the service calls, such as its databases, Kafka, Redis, the identity provider, and the other FlowX.AI services. Anything you leave out is blocked.

***

## How do I collect metrics with Prometheus?

The chart renders a Prometheus Operator `ServiceMonitor` for each service where you turn it on. The `ServiceMonitor` resource type comes with the Prometheus Operator, so install it first, or the release fails to install.

```yaml theme={"dark"}
services:
  process-engine:
    metrics:
      enabled: true
      serviceMonitor:
        enabled: true
        interval: 30s
        additionalLabels:
          release: prometheus
```

The `ServiceMonitor` scrapes the service's management port, `mngt`, at `/manage/actuator/prometheus`, the path the Java services serve. The service must have its management port turned on with `mngt.enabled`, which the chart sets for its services by default.

| Setting | Description | Default |
| - | - | - |
| **`metrics.enabled`** and **`metrics.serviceMonitor.enabled`** | Both must be `true` to render the `ServiceMonitor` | `false` |
| **`metrics.serviceMonitor.namespace`** | Namespace to create the `ServiceMonitor` in | The release namespace |
| **`metrics.serviceMonitor.path`** | Metrics path | `/manage/actuator/prometheus` |
| **`metrics.serviceMonitor.interval`** and **`.scrapeTimeout`** | Scrape interval and timeout | The Prometheus defaults |
| **`metrics.serviceMonitor.additionalLabels`** | Labels that your Prometheus uses to select `ServiceMonitor` resources | None |
| **`metrics.serviceMonitor.honorLabels`** | Keeps the labels of scraped metrics when they clash with Prometheus labels | None |
| **`metrics.serviceMonitor.relabellings`** | Metric relabeling rules. Note the spelling of the key | None |
| **`metrics.podAnnotations`** | Annotations added to the service's pods, for a Prometheus that discovers targets by annotation | None |

***

## Troubleshooting

> [accordion] The render stops with a message that starts with a service name in brackets
>
> A check failed for that service. Find the message in [Why does the render stop with an error?](#why-does-the-render-stop-with-an-error) and fix the value it names. The checks run on the merged values, so the value may come from `global.flowx` rather than the service block.

> [accordion] A service starts without a database URL or credentials
>
> Check that the `serverRef` and `credentials` of its selector match keys under `global.flowx.<dependency>.servers` and `.credentials` exactly. A key that matches nothing renders nothing. Run `helm template` and look for the `# postgresql: <entry>` comment to see what was rendered for the entry.

> [accordion] A pod stays in CreateContainerConfigError
>
> The pod references a secret or a secret key that doesn't exist. Compare every `existingSecret.name` and `keyMapping` value in your values file with `kubectl get secret <name> -o jsonpath='{.data}'`. Without `keyMapping`, the key must be named like the field, for example `password`.

> [accordion] Java services fail at startup with an opaque-token introspection placeholder error
>
> The `default` resource server uses `securityType: opaque`. Set it to `jwt`, as described in [Resource servers](#resource-servers).

> [accordion] Calls to a dependency fail with a certificate path or PKIX error
>
> The service doesn't trust the dependency's CA. Add the CA under `global.flowx.certs` and list it in that service's `component.certs`, keeping `elasticsearch` if it was already listed. See [How do I trust a private certificate authority?](#how-do-i-trust-a-private-certificate-authority)

> [accordion] The render stops because autoscaling.maxReplicas is missing
>
> A service has `autoscaling.enabled: true` without `autoscaling.maxReplicas`. Set it, see [How do I scale a service and keep it available?](#how-do-i-scale-a-service-and-keep-it-available)

> [accordion] Services still connect to the standalone Redis after you configured Sentinel or Cluster
>
> The `default` Redis server still has `host: redis-master` from the chart, and `host` wins over `sentinel` and `cluster`. Set `host: ""` on the server, see [How do I use Redis Sentinel or Redis Cluster?](#how-do-i-use-redis-sentinel-or-redis-cluster)

> [accordion] Prometheus shows the ServiceMonitor target as down after you turned on the network policy
>
> The network policy doesn't open the management port. Add an `extraIngress` rule for it, see [How do I restrict network traffic to a service?](#how-do-i-restrict-network-traffic-to-a-service)

***

## Related resources

> [card] [Migrate to the flowx-ai Helm chart](https://docs.flowx.ai/5.9/setup-guides/migrate-to-flowx-ai-helm-chart.md)
>
> Service groups, global settings, and the cutover

> [card] [Configuring an IAM solution](https://docs.flowx.ai/5.9/setup-guides/access-management/configuring-an-iam-solution.md)
>
> Keycloak realm, clients, and roles

> [card] [AI Platform setup](https://docs.flowx.ai/5.9/setup-guides/ai-platform-setup.md)
>
> AI services, configuration, and deployment

> [card] [SpiceDB](https://docs.flowx.ai/5.9/setup-guides/spicedb.md)
>
> Authorization service setup


## Related topics

- [Migrate self-hosted FlowX.AI to the flowx-ai Helm chart](https://docs.flowx.ai/5.9/setup-guides/migrate-to-flowx-ai-helm-chart.md)
- [Helm chart](https://docs.flowx.ai/release-notes/v6.x/v6.0.0-october-2026/migrating-from-v5.9-to-v6.0/helm-chart.md)
- [AI Platform setup](https://docs.flowx.ai/5.9/setup-guides/ai-platform-setup.md)
- [FlowX.AI 5.9.0 Release Notes](https://docs.flowx.ai/release-notes/v5.x/v5.9.0-june-2026/v5.9.0-june-2026.md)
- [FlowX.AI 5.3.0 Release Notes](https://docs.flowx.ai/release-notes/v5.x/v5.3.0-december-2025/v5.3.0-december-2025.md)


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