Helm chart values
The OX AI-Service integrates 3rd party AIs in App Suite UI, currently ChatGPT from OpenAI and LocalAI compatible models.
Please check out the full documentation here: OX AI-Service documentation
Chart Configuration
| Parameter | Description | Default |
|---|---|---|
| image.repository | The image to be used for the deployment | appsuite-core-internal/ai-service |
| image.pullPolicy | The imagePullPolicy for the deployment | IfNotPresent |
| image.tag | The image tag, defaults to app version | "" |
| defaultRegistry | Registry the image is pulled from | registry.open-xchange.com |
| imagePullSecrets | Image pull secrets, e.g. [{name: ai-service-pullsecret}] | [] |
| nameOverride | Override the chart name | "" |
| fullnameOverride | Override the full name of the release resources | "" |
| replicaCount | Number of pods; ignored when autoscaling is enabled | 1 |
| containerPort | Container port of the HTTP API | 8080 |
| resources | Container resource requests and limits | requests: cpu 500m, memory 384Mi; limits: memory 512Mi |
| autoscaling.enabled | Create a HorizontalPodAutoscaler | false |
| autoscaling.minReplicas | Minimum number of pods | 1 |
| autoscaling.maxReplicas | Maximum number of pods | 100 |
| autoscaling.targetMemoryUtilizationPercentage | Target memory utilization | 80 |
| autoscaling.targetCPUUtilizationPercentage | Target CPU utilization; not set by default | "" |
| nodeSelector | Node selector for the pod | {} |
| tolerations | Tolerations for the pod | [] |
| affinity | Affinity rules for the pod | {} |
| podAnnotations | Annotations added to the pod | logging.open-xchange.com/format: "appsuite-json" |
| probe.liveness | Liveness probe on /live (port 9090); enabled plus any probe field | enabled, periodSeconds 10, failureThreshold 15 |
| probe.readiness | Readiness probe on /ready (port 9090); enabled plus any probe field | enabled, initialDelaySeconds 1, periodSeconds 5, failureThreshold 2, timeoutSeconds 5 |
| service.type | Kubernetes service type | ClusterIP |
| service.port | Kubernetes service port | 80 |
| bindAddr | IP address to listen for connections | "::" |
| origins | Allowed origins for CORS | * |
| logLevel | specify log level for service | "info" |
| exposeApiDocs | Expose API documentation via Swagger UI at /api-docs | false |
| ingress.enabled | Generate ingress resource | false |
| ingress.className | Ingress class name | "" |
| ingress.annotations | Map of key-value pairs that will be added as annotations to the ingress resource | {} |
| ingress.hosts | Hosts the ingress serves (- host: <name>) | chart-example.local |
| ingress.tls | TLS configuration of the ingress | [] |
| istio.enabled | Create an Istio VirtualService | false |
| istio.hostname | Host of the VirtualService | "" |
| istio.gateway | Gateway the VirtualService binds to | "istio-system/default-gateway" |
| istio.oauth | Protect /admin with an oauth2-proxy registered as Istio ext_authz provider, route /oauth2 to it and label the pod for sidecar injection | false |
| istio.oauthProvider | Name of the ext_authz provider in the mesh config | "oauth2-proxy" |
| istio.oauthHostname | Service of the oauth2-proxy (port 4180) | "oauth2-proxy.oauth2-proxy.svc.cluster.local" |
| oidc.issuer | OIDC issuer URL, e.g. https://example.com/appsuite/ui | "" |
| openaiSecret.enabled | Enable the secret for openai | true |
| openaiAPIKey | OpenAI API Key | "" |
| openaiModerationModel | OpenAI moderation model | "omni-moderation-latest" |
| localaiSecret.enabled | Enable the secret for localai. May be optional, depending on your used service and model. | false |
| localaiAPIKey | localAI API Key. May be optional, depending on your used service and model. | "" |
| mysqlSecret.enabled | Create the kubernetes secret for mysql (enable if you want to use the DB or provide own secret) | true |
| overrides.mysqlSecret | If you provide your own secret for mysql put the secret name here | "" |
| overrides.openaiSecret | If you provide your own secret for OpenAI put the secret name here (key openaiAPIKey) | "" |
| overrides.awsSecret | If you provide your own secret for AWS put the secret name here (keys awsAccessKey, awsSecretKey) | "" |
| overrides.localaiSecret | If you provide your own secret for LocalAI put the secret name here (key localaiAPIKey) | "" |
| overrides.mcpSessionEncryptionSecret | If you provide your own secret for the MCP session encryption key put the secret name here (key mcpSessionEncryptionKey) | "" |
| mcpSessionEncryptionSecret.enabled | Create the kubernetes secret for the MCP session encryption key (from mcpSessionEncryptionKey) | false |
| mcpSessionEncryptionKey | MCP session encryption key (AES-256-GCM seed); generate with openssl rand -hex 32 | "" |
| mcp.allowedApiHosts | Comma-separated allow-list of Middleware hostnames for MCP auth (*.example.com wildcards supported); required when MCP is enabled | "" |
| mcp.rateLimitAuthPerMin | Rate limit for POST /api/mcp/auth per user per minute | "5" |
| mcp.rateLimitToolsPerMin | Rate limit for MCP tool calls per user per minute | "60" |
| mcp.maxConcurrentCalls | MCP requests one user may have in flight at the same time (per pod) | "4" |
| mcp.allowedOrigins | Comma-separated origins allowed to call POST /api/mcp/ from a browser; empty sends no CORS headers | "" |
| mcp.publicUrl | MCP endpoint exactly as users enter it in a client, e.g. https://appsuite.example.com/api/ai/mcp (env MCP_PUBLIC_URL). OAuth for claude.ai and Claude Code is on only when this and mcp.oauthAppSuiteUrl are set; the discovery paths sit below this URL, and for claude.ai Anthropic's outbound range (see documentation/mcp.md) must reach the service | "" |
| mcp.oauthAppSuiteUrl | App Suite that shows the OAuth consent dialog, e.g. https://appsuite.example.com (env MCP_OAUTH_APP_SUITE_URL); set together with mcp.publicUrl | "" |
| mcp.maxGrantsPerUser | Connections (OAuth clients plus manual keys) one user may hold (env MCP_MAX_GRANTS_PER_USER); rotating the MCP session encryption key invalidates registered OAuth client ids | "10" |
| mcp.oauthAllowedRedirects | Security setting, recommended in production: comma-separated hostnames (https, exact match) and private-use schemes with their colon that OAuth clients may redirect to, e.g. claude.ai,cursor: (env MCP_OAUTH_ALLOWED_REDIRECTS); loopback is always allowed; empty allows every redirect, see documentation/mcp.md | "" |
| mcp.externalInvitations | Covers event invitations, mail recipients, vacation notice text, and text sent to external attendees or organizers (event and task text changes, the display name of an invited attendee, comments on replies, declines and cancellations), and booked resources whose address is outside the user's mail domains and their subdomains. false refuses addresses that are not listed as users in the global address book, and addresses the lookup cannot check | false |
| mcp.scopeTiers | Object mapping the key tiers read, write and destructive to Middleware module scopes (env MCP_SCOPE_TIERS); a tier left out keeps the mapping derived from the tools; invalid scopes stop the service at startup | {} |
| database.enabled | Use Database. This is mandatory for AI chat, usage tracking, and rate limiting and therefore deprecated. With App Suite 8.34, the default changed to true. | true |
| database.host | SQL server hostname | RELEASE-NAME-ai-service-sql |
| database.port | SQL server port | 3306 |
| database.name | Database name | RELEASE-NAME-ai-service |
| database.connections | Number of concurrent connections to the DB server | "10" |
| database.user | DB User with access rights to sqlDB | "" |
| database.password | Password of the database user | "" |
| migration.jobId | Rotation token for the migrate Job name suffix (e.g. $CI_JOB_ID); falls back to release revision | "" |
| cron.cleanupDb | Database cleanup interval (Cron notation) | "0 4 * * *" |
| cron.sidecarInjection.disabled | Disable Istio Sidecar Injection for CronJobs | true |
| openaiBaseUrl | Url of the OpenAI service (internal or localAI use only) | "" |
| localaiBaseUrl | Url of the localAI service. Falls back to to openaiBaseUrl if configured. | "" |
| aiEgressProxy.url | Outbound proxy for the AI provider APIs (OpenAI, LocalAI, Azure, AWS Bedrock) | "" |
| aiEgressProxy.noProxy | Hosts that stay direct; NO_PROXY syntax (host, .domain, *.domain, host:port, IP, *), evaluated the same way for every provider including Bedrock | "" |
| aiEgressProxy.secret.enabled | Render aiEgressProxy.url into a secret; required when the url carries credentials | false |
| overrides.aiEgressProxySecret | Your own secret for the proxy url (key aiEgressProxyUrl); needs secret.enabled=false | "" |
| extraCaCerts.certificates | Inline PEM bundle of additional CA certificates; the chart creates the Secret | "" |
| extraCaCerts.existingSecret | Name of an existing Secret holding the PEM bundle; takes precedence over certificates | "" |
| extraCaCerts.key | Key inside the Secret that holds the bundle | "ca.crt" |
| localaiModerationDisabled | Legacy switch; LocalAI models are never moderated, regardless of this value | true |
| accounting.enabled | Enable the accounting feature | false |
| chatAssistantContext | First (system) message that is sent to the ai chat to setup the mood and general context. | "You are a helpful AI assistant." |
| enforceDefaultModel | Always use the configured default model regardless of the clients request | false |
| disableAPI | Disable all API endpoints. Only UI source files will be delivered | false |
| moderationDisabled | Disable moderation for all models (testing only) | false |
| awsSecret.enabled | Enable the aws Secret: static keys (renders AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY) or a Bedrock API key (awsBearerToken); leave false for IRSA (serviceAccount.annotations) or EKS Pod Identity (association on serviceAccount.name) | false |
| awsRegion | Region of the AWS user with Bedrock & Claude enabled; rendered whenever set, independent of awsSecret.enabled | "" |
| awsAccessKey | AWS IAM access key with Bedrock & Claude enabled (rendered as AWS_ACCESS_KEY_ID) | "" |
| awsSecretKey | AWS IAM secret key with Bedrock & Claude enabled (rendered as AWS_SECRET_ACCESS_KEY) | "" |
| awsBearerToken | Bedrock API key, rendered as AWS_BEARER_TOKEN_BEDROCK from the aws Secret (third path next to static keys and IAM roles; needs awsSecret.enabled) | "" |
| awsClaudeUrl | URL of the AWS Bedrock/Claude service (internal use only) | "" |
| bedrock.guardrail.id | Bedrock Guardrail identifier for output moderation; unset falls back to the classifier | "" |
| bedrock.guardrail.version | Bedrock Guardrail version | "" |
| bedrock.moderationModelId | Model or inference profile ID for the classifier moderation fallback | "" |
| bedrock.moderationDisabled | Disable Bedrock output moderation independently of moderationDisabled | false |
| bedrock.moderationFailClosed | Retract the answer in the UI ({reset}) when the Bedrock moderation call fails instead of delivering it unchecked; errors count in ai_moderation_errors_count either way | false |
| serviceAccount.create | Create a dedicated ServiceAccount for the pods; set false and name an existing one in serviceAccount.name when an operator already created the SA (e.g. for IRSA), otherwise the upgrade fails because the SA is not managed by Helm | true |
| serviceAccount.name | Name of the ServiceAccount; defaults to the chart fullname | "" |
| serviceAccount.annotations | Annotations on the ServiceAccount, e.g. eks.amazonaws.com/role-arn for IRSA | {} |
| serviceAccount.automountServiceAccountToken | Set on the ServiceAccount resource itself | false |
| automountServiceAccountToken | Pod-level token automount; this service never calls the Kubernetes API | false |
| basicAuth.enabled | Enable the secret for basic authentication (Admin routes) | false |
| basicAuth.user | Username for basic authentication (Admin routes) | "" |
| basicAuth.password | Password for basic authentication (Admin routes) | "" |
| overrides.basicAuthSecret | If you provide your own secret for basic auth put the secret name here | "" |
| bearerAuth.enabled | Enable the secret for bearer authentication (Admin routes) | false |
| bearerAuth.keys | Comma separated keys to use with bearer auth (Admin Routes) | "" |
| overrides.bearerAuthSecret | If you provide your own secret for bearer auth put the secret name here | "" |
| qdrant.enabled | Enable Qdrant vector database for semantic search | false |
| qdrant.url | URL of the Qdrant instance | "" |
| qdrant.secret.enabled | Create the kubernetes secret for the Qdrant API key (from qdrant.apiKey) | false |
| qdrant.apiKey | Qdrant API Key (used when qdrant.secret.enabled is true) | "" |
| qdrant.timeoutMs | Timeout in milliseconds for Qdrant requests | "" |
| qdrant.collectionName | Collection name used for mail embeddings | "appsuite_mail_embeddings" |
| qdrant.distance | Distance metric for vector similarity | "Cosine" |
| qdrant.mSize | HNSW M parameter for the main vector index | "16" |
| qdrant.efConstruct | HNSW ef_construct parameter for index build | "100" |
| qdrant.payloadMSize | HNSW M parameter for the payload index | "16" |
| qdrant.embeddingsOnDisk | Store vectors on disk in Qdrant | "false" |
| qdrant.hnswIndexOnDisk | Store the HNSW index on disk in Qdrant | "false" |
| overrides.qdrantSecret | If you provide your own secret for Qdrant put the secret name here (key qdrantAPIKey) | "" |
| pgvector.enabled | Enable PostgreSQL + pgvector as vector store (alternative to Qdrant, enable only one) | false |
| pgvector.url | PostgreSQL connection string without the password | "" |
| pgvector.secret.enabled | Create the kubernetes secret for the pgvector password (from pgvector.password) | false |
| pgvector.password | Password for the pgvector database user | "" |
| overrides.pgvectorSecret | If you provide your own secret for pgvector put the secret name here (key pgvectorPassword) | "" |
| openaiEmbeddingModel | Embedding model used for Smart Search when using OpenAI/localAI compatible embeddings | "" |
| embeddingSize | Embedding vector size written to the vector store | 384 |
| mailFetchConcurrency | Number of concurrent mail fetch operations during embedding/backfill | 5 |
| mailEmbedConcurrency | Number of concurrent embedding operations during embedding/backfill | 2 |
| embeddingQueueTtlMs | TTL for embedding jobs in milliseconds | 7200000 |
| embedRetryMax | Maximum number of retries for failed embedding jobs | 4 |
| oauth2.enabled | Enable OAuth2/OIDC authentication for the admin routes | false |
| oauth2.domain | Domain of the openid connect capable OAuth2 server | "" |
| plans | List of plans with limits for users and brands | See full documentation for details |
| modelsConfig | List of models that the service supports | See full documentation for details |
| config | Application configuration that is also relevent for the UI parts | See full documentation for details |
| monitoring.grafana.enabled | Enable Grafana integration for dashboards and init job | false |
| monitoring.grafana.grafanaOperator | Enable Grafana Operator integration (adds label and skips Node.js job) | false |
| monitoring.grafana.url | URL of the Grafana instance | "" |
| monitoring.grafana.secretName | Name of the Kubernetes secret for Grafana credentials | "" |
| monitoring.grafana.username | Username for Grafana API access (if required) | "" |
| monitoring.grafana.password | Password for Grafana API access (if required) | "" |
| monitoring.grafana.apiToken | API token for Grafana (preferred for automation) | "" |
| monitoring.grafana.folder | Target folder in Grafana for dashboards | "" |
| monitoring.grafana.ttlInitJob | TTL in seconds for the Grafana init job after completion | 600 |
| monitoring.prometheus.url | URL of the Prometheus instance used for metrics collection | "" |
| monitoring.prometheus.dataSource | Override the Prometheus datasource name in Grafana | "" |
| monitoring.prometheus.kube-prometheus | Enable ServiceMonitor creation for kube-prometheus environments | false |
| monitoring.prometheus.serviceMonitor.namespace | Target namespace for ServiceMonitor | "" |
| monitoring.prometheus.serviceMonitor.interval | Scrape interval for ServiceMonitor | 30s |
| monitoring.prometheus.serviceMonitor.scrapeTimeout | Scrape timeout for ServiceMonitor | "" |
| monitoring.prometheus.serviceMonitor.honorLabels | Honor labels for ServiceMonitor | false |
| monitoring.prometheus.serviceMonitor.additionalLabels | Additional labels for ServiceMonitor | {} |
| monitoring.prometheus.serviceMonitor.annotations | Annotations for ServiceMonitor | {} |
| monitoring.prometheus.serviceMonitor.relabelings | Relabeling configuration for ServiceMonitor | maps __meta_kubernetes_pod_label_app_kubernetes_io_name to app |
| monitoring.prometheus.serviceMonitor.metricRelabelings | Metric relabeling configuration for ServiceMonitor | [] |
Job pod labels
The cleanup CronJob and the migration Job run outside the Deployment, so their pods do not carry the Deployment's pod labels (app.kubernetes.io/name plus instance is the Service selector). Kubernetes only adds job-name and controller-uid, which change on every run. To select the job pods in a NetworkPolicy, use the stable labels the chart sets:
podSelector:
matchLabels:
app.kubernetes.io/instance: as8 # your Helm release name
app.kubernetes.io/component: cleanupdb # or migratedb
AWS Bedrock credentials
claude-aws models pick up credentials from the AWS SDK default chain, so the chart supports four ways to deploy them:
- Static keys: set
awsSecret.enabled=true(oroverrides.awsSecretfor a pre-existing Secret) plusawsAccessKey/awsSecretKey, andawsRegionas a plain value. The pod getsAWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEYfrom the Secret. - Bedrock API key: set
awsSecret.enabled=trueandawsBearerToken; the pod getsAWS_BEARER_TOKEN_BEDROCKfrom the Secret and no static keys, even whenawsAccessKeyandawsSecretKeyare still set (the token takes precedence, so stale keys cannot interfere). Use a long-term key bound to an IAM user: short-term keys expire after about 12 hours and are valid in one region only, after which every Bedrock call fails with an authentication error. With a pre-existingoverrides.awsSecretonly the static keys are read from it andawsBearerTokenis ignored. - IRSA: leave
awsSecret.enabled=falseand setserviceAccount.annotations["eks.amazonaws.com/role-arn"]to the IAM role ARN. No static keys are rendered; the SDK picks upAWS_ROLE_ARN/AWS_WEB_IDENTITY_TOKEN_FILEthat the EKS webhook injects into the ServiceAccount's pods. - EKS Pod Identity: leave
awsSecret.enabled=false, no annotation; create the Pod Identity association for the chart's ServiceAccount (serviceAccount.name, default the chart fullname). The SDK reads the credentials from the container credential endpoint.
In both role-based modes awsRegion still needs to be set (or AWS_REGION supplied by the cluster's environment), since it is rendered independently of the secret.
Security Context
Security contexts follow the ox-common pattern with defaults for Wolfi-based images (nonroot UID 65532):
| Parameter | Description | Default |
|---|---|---|
| podSecurityContext | Explicit pod security context (overrides defaults) | {} |
| securityContext | Explicit container security context (overrides defaults) | {} |
| defaultPodSecurityContext.fsGroup | Default pod fsGroup (Wolfi nonroot) | 65532 |
| defaultSecurityContext.runAsNonRoot | Default run as non-root | true |
| defaultSecurityContext.runAsUser | Default user ID (Wolfi nonroot) | 65532 |
| defaultSecurityContext.runAsGroup | Default group ID (Wolfi nonroot) | 65532 |
| defaultSecurityContext.allowPrivilegeEscalation | Default prevent privilege escalation | false |
| defaultSecurityContext.readOnlyRootFilesystem | Default read-only root filesystem | true |
| defaultSecurityContext.capabilities.drop | Default dropped capabilities | [ALL] |
| global.useDefaultSecurityContext | Enable default security contexts | false |
To enable Wolfi security defaults, set global.useDefaultSecurityContext=true.