A Helm chart for deploying Red Hat Developer Hub, which is a Red Hat supported version of Backstage.
The telemetry data collection feature is enabled by default. Red Hat Developer Hub sends telemetry data to Red Hat by using the backstage-plugin-analytics-provider-segment plugin. To disable this and to learn what data is being collected, see https://docs.redhat.com/en/documentation/red_hat_developer_hub/1.10/html-single/telemetry_data_collection_and_analysis/index
Homepage: https://developers.redhat.com/products/rhdh
This repository now provides the productized RHDH chart. For the Generally Available version of this chart, see:
- https://github.com/openshift-helm-charts/charts - official releases to https://charts.openshift.io/
| Name | Url | |
|---|---|---|
| Red Hat | https://redhat.com |
- https://github.com/redhat-developer/rhdh-chart/tree/main/charts/rhdh
- https://github.com/redhat-developer/rhdh
- https://github.com/redhat-developer/rhdh-plugins
- https://github.com/redhat-developer/rhdh-plugin-export-overlays
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add redhat-developer https://redhat-developer.github.io/rhdh-chart
helm install my-rhdh redhat-developer/redhat-developer-hub --version 3.5.13This chart bootstraps a Red Hat Developer Hub deployment on a Kubernetes cluster using the Helm package manager.
Unlike the legacy backstage chart, this chart owns all Kubernetes templates directly (Deployment or StatefulSet, Service, ConfigMap, etc.) without depending on an upstream Backstage subchart. The Backstage pod specification is shared across workload kinds via a common template. It uses an "add, don't replace" pattern: system-required volumes, volume mounts, environment variables, and init containers are hardcoded in the pod template, while user-provided values (extraVolumes, extraVolumeMounts, extraEnv, extraInitContainers, extraContainers) are always appended — never replacing the defaults.
- Kubernetes 1.27+ (OpenShift 4.14+)
- Helm 3.10+ or latest release
- PV provisioner support in the underlying infrastructure
Charts are available in the following formats:
Up-to-date instructions on installing RHDH through the chart can be found in the installation docs.
The following command can be used to add the chart repository:
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add redhat-developer https://redhat-developer.github.io/rhdh-chartOnce the chart has been added, install this chart. However before doing so, please review the default values.yaml and adjust as needed.
-
To get proper connection between frontend and backend of Backstage please update the
apps.example.comto match your cluster host:openshift: clusterRouterBase: apps.example.com
Tip: you can use
helm upgrade -i --set openshift.clusterRouterBase=apps.example.com ...instead of a value file -
If your cluster doesn't provide PVCs, you should disable PostgreSQL persistence via:
postgresql: primary: persistence: enabled: false
helm upgrade -i <release_name> redhat-developer/redhat-developer-hubCharts are also available in OCI format. The list of available releases can be found here.
Install one of the available versions:
helm upgrade -i <release_name> oci://quay.io/rhdh/chart --version=<version>Tip: List all releases using
helm list
Once an Helm Release has been deployed, you can test it using the helm test command:
helm test <release_name>This will run a simple Pod in the cluster to check that the application deployed is up and running.
You can control whether to disable this test pod or customize the image, pull policy, and security context it uses.
See the test.enabled, test.image, and test.securityContext parameters in the values.yaml file.
Tip: Disabling the test pod will not prevent the
helm testcommand from passing later on. It will simply report that no test suite is available.
Below are a few examples:
Disabling the test pod
helm install <release_name> <repo_or_oci_registry> \
--set test.enabled=falseCustomizing the test pod image
helm install <release_name> <repo_or_oci_registry> \
--set test.image.repository=curl/curl-base \
--set test.image.tag=8.11.1To uninstall/delete the my-rhdh deployment:
helm uninstall my-rhdhThe command removes all the Kubernetes components associated with the chart and deletes the release.
If you are upgrading from the legacy backstage chart (used in RHDH 1.y), the new redhat-developer-hub chart is a clean break. The values structure has changed significantly — all global.* and upstream.backstage.* nesting has been flattened to root-level keys. You cannot pass your old values file directly to the new chart; you must migrate your values first, then helm upgrade the release in place.
See the Migration guide for step-by-step instructions and a complete values mapping reference.
Kubernetes: >= 1.31.0-0
| Repository | Name | Version |
|---|---|---|
| https://charts.bitnami.com/bitnami | common | 2.41.0 |
| oci://registry-1.docker.io/bitnamicharts | postgresql | 16.2.5 |
| Key | Description | Type | Default |
|---|---|---|---|
| affinity | Affinity rules for pod assignment. | object | {} |
| appConfig | Inline Backstage app-config YAML. Rendered into a ConfigMap and mounted as app-config-from-configmap.yaml. | object | Default config with base URLs, CORS, database connection, and backend auth. |
| argsOverride | Override the container arguments entirely. When set, system config arguments are NOT added automatically; you must include them yourself. | list | [] |
| auth | Service-to-service authentication configuration. | object | {"backend":{"enabled":true,"existingSecretRef":{"key":"backend-secret","name":""},"value":""}} |
| auth.backend.enabled | Enable backend service-to-service authentication. Generates a random secret unless existingSecretRef is set or value is provided. Disable if you inject the secret via extraEnvFrom or extraEnv instead. | bool | true |
| auth.backend.existingSecretRef | Reference an existing Secret instead of generating one. When not set, the chart auto-generates a random token. | object | {"key":"backend-secret","name":""} |
| auth.backend.existingSecretRef.key | Key within the Secret that holds the backend auth token. | string | "backend-secret" |
| auth.backend.existingSecretRef.name | Name of the existing Secret. When empty, the chart generates one. | string | "" |
| auth.backend.value | Use a specific value instead of generating one. | string | "" |
| autoscaling | Horizontal Pod Autoscaler configuration. | object | {"enabled":false,"maxReplicas":3,"minReplicas":1,"targetCPUUtilizationPercentage":80} |
| catalogIndex | Catalog index configuration for automatic plugin discovery. | object | {"extraImages":[],"image":{"digest":"","registry":"quay.io","repository":"rhdh/plugin-catalog-index","tag":"next"}} |
| catalogIndex.extraImages | Extra catalog index images for additional plugin discovery in the Extensions UI. Each item must include registry, repository, and tag fields; name and digest are optional. Only catalog entities are extracted from extra images (no dynamic-plugins.default.yaml handling). |
list | [] |
| commandOverride | Override the container command. | list | [] |
| commonAnnotations | Annotations applied to ALL chart resources. | object | {} |
| commonLabels | Labels applied to ALL chart resources. | object | {} |
| containerSecurityContext | Security context for the main RHDH container (not the Lightspeed Core sidecar or init containers). | object | {"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true,"runAsNonRoot":true,"seccompProfile":{"type":"RuntimeDefault"}} |
| deploymentAnnotations | Annotations for the Deployment resource (not the pod). | object | {} |
| dynamicPlugins | Dynamic plugin system configuration. | object | {"includes":["dynamic-plugins.default.yaml"],"initContainer":{"argsOverride":[],"commandOverride":[],"extraArgs":[],"extraEnv":[],"extraVolumeMounts":[],"resources":{"limits":{"cpu":"1000m","ephemeral-storage":"5Gi","memory":"2.5Gi"},"requests":{"cpu":"250m","memory":"256Mi"}},"securityContext":{}},"maxEntrySize":40000000,"plugins":[],"volume":{"emptyDir":{},"ephemeral":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"5Gi"}},"storageClassName":""},"pvc":{"claimName":""},"statefulSetPVC":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"5Gi"}},"storageClassName":""},"type":"ephemeral"}} |
| dynamicPlugins.includes | Array of YAML files listing dynamic plugins to include. Relative paths are resolved from the working directory of the initContainer (/opt/app-root/src). |
list | ["dynamic-plugins.default.yaml"] |
| dynamicPlugins.initContainer | Configuration for the install-dynamic-plugins init container. | object | {"argsOverride":[],"commandOverride":[],"extraArgs":[],"extraEnv":[],"extraVolumeMounts":[],"resources":{"limits":{"cpu":"1000m","ephemeral-storage":"5Gi","memory":"2.5Gi"},"requests":{"cpu":"250m","memory":"256Mi"}},"securityContext":{}} |
| dynamicPlugins.initContainer.argsOverride | Override the default arguments. Leave empty to use the defaults. | list | [] |
| dynamicPlugins.initContainer.commandOverride | Override the default command. Leave empty to use the default (./install-dynamic-plugins.sh /dynamic-plugins-root). | list | [] |
| dynamicPlugins.initContainer.extraArgs | Extra arguments appended after the default arguments. Ignored when argsOverride is set. | list | [] |
| dynamicPlugins.initContainer.extraEnv | Extra environment variables appended after the system env vars (NPM_CONFIG_USERCONFIG, MAX_ENTRY_SIZE, CATALOG_INDEX_IMAGE, etc.). | list | [] |
| dynamicPlugins.initContainer.extraVolumeMounts | Additional volume mounts appended after the system mounts (dynamic-plugins-root, npmrc, registry-auth, npmcacache, extensions-catalog, temp). | list | [] |
| dynamicPlugins.initContainer.resources | Resource requests and limits. | object | {"limits":{"cpu":"1000m","ephemeral-storage":"5Gi","memory":"2.5Gi"},"requests":{"cpu":"250m","memory":"256Mi"}} |
| dynamicPlugins.initContainer.securityContext | Security context for the init container. | object | Same as containerSecurityContext |
| dynamicPlugins.maxEntrySize | Maximum uncompressed size (in bytes) of a single dynamic plugin entry. | int | 40000000 |
| dynamicPlugins.plugins | List of dynamic plugins. Every item defines the plugin package as a NPM package spec or OCI reference. |
list | [] |
| dynamicPlugins.volume | Volume configuration for the dynamic plugins root directory. | object | {"emptyDir":{},"ephemeral":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"5Gi"}},"storageClassName":""},"pvc":{"claimName":""},"statefulSetPVC":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"5Gi"}},"storageClassName":""},"type":"ephemeral"} |
| dynamicPlugins.volume.emptyDir | Raw Kubernetes emptyDir volume spec. Used when type is "emptyDir". | object | {} |
| dynamicPlugins.volume.ephemeral | Ephemeral volume configuration. Used when type is "ephemeral". The chart builds the full ephemeral.volumeClaimTemplate.spec from these fields. | object | {"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"5Gi"}},"storageClassName":""} |
| dynamicPlugins.volume.ephemeral.accessModes | Access modes for the ephemeral PVC. | list | ["ReadWriteOnce"] |
| dynamicPlugins.volume.ephemeral.resources | Resource requests for the ephemeral PVC. | object | {"requests":{"storage":"5Gi"}} |
| dynamicPlugins.volume.ephemeral.storageClassName | StorageClass for the ephemeral volume. When empty, uses global.defaultStorageClass or the cluster default. | string | "" |
| dynamicPlugins.volume.pvc | Raw Kubernetes persistentVolumeClaim volume spec. Used when type is "pvc". | object | {"claimName":""} |
| dynamicPlugins.volume.statefulSetPVC | StatefulSet-owned PVC volume spec. Used when type is "statefulSetPVC". | object | {"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"5Gi"}},"storageClassName":""} |
| dynamicPlugins.volume.statefulSetPVC.accessModes | Access modes for the StatefulSet-owned PVC. | list | ["ReadWriteOnce"] |
| dynamicPlugins.volume.statefulSetPVC.resources | Resource requests for the StatefulSet-owned PVC. | object | {"requests":{"storage":"5Gi"}} |
| dynamicPlugins.volume.statefulSetPVC.storageClassName | StorageClass. When empty, uses global.defaultStorageClass or the cluster default. | string | "" |
| dynamicPlugins.volume.type | Volume type: "ephemeral" (auto-provisioned PVC per pod), "emptyDir" (scratch space, lost on pod restart), "pvc" (pre-existing PersistentVolumeClaim), or "statefulSetPVC" (StatefulSet volumeClaimTemplate; requires workload.kind=StatefulSet). | string | "ephemeral" |
| envFromOverride | Override the container envFrom entirely. When set, extraEnvFrom is ignored. Accepts raw Kubernetes envFrom entries (configMapRef, secretRef, prefix). | list | [] |
| envOverride | Override the container environment variables entirely. When set, system env vars (BACKEND_SECRET, DB credentials, etc.) are NOT added automatically. | list | [] |
| externalDatabase | External database connection. Used when postgresql.enabled is false. See docs/external-db.md for TLS setup and privilege requirements. When both postgresql.enabled and externalDatabase.host are false/empty, the chart renders no database env vars (BYO configuration via extraEnv or appConfig). | object | {"existingSecretRef":{"key":"password","name":""},"host":"","port":5432,"user":"postgres"} |
| externalDatabase.existingSecretRef | Reference to an existing Secret containing the database password. | object | {"key":"password","name":""} |
| externalDatabase.existingSecretRef.key | Key within the Secret that holds the password. | string | "password" |
| externalDatabase.existingSecretRef.name | Name of the existing Secret. | string | "" |
| externalDatabase.host | External database hostname. | string | "" |
| externalDatabase.port | External database port. | int | 5432 |
| externalDatabase.user | External database user. | string | "postgres" |
| extraAppConfig | Additional app-config files from existing ConfigMaps. | list | [] |
| extraArgs | Extra arguments appended after the system config flags. | list | [] |
| extraContainers | Additional sidecar containers. These are ADDED to system containers (e.g. Lightspeed Core sidecar), never replacing them. | list | [] |
| extraDeploy | Array of extra objects to deploy with the release (rendered through tpl). Each element is a complete Kubernetes manifest; Helm template expressions are supported. |
list | [] |
| extraEnv | Extra environment variables appended after the system env vars. | list | [] |
| extraEnvFrom | Extra envFrom entries appended to the container. Accepts raw Kubernetes envFrom entries (configMapRef, secretRef, prefix). | list | [] |
| extraInitContainers | Additional init containers. These are ADDED after system init containers (install-dynamic-plugins), never replacing them. | list | [] |
| extraVolumeMounts | Additional volume mounts to add to the main container. These are ADDED to system-required mounts, never replacing them. | list | [] |
| extraVolumes | Additional volumes to add to the pod. These are ADDED to system-required volumes (dynamic-plugins-root, temp, npmcacache, etc.), never replacing them. | list | [] |
| fullnameOverride | Override the full resource name. | string | "" |
| global | Global parameters shared with bitnami subcharts (postgresql, common). | object | {"defaultStorageClass":"","imagePullSecrets":[],"imageRegistry":""} |
| global.defaultStorageClass | Global default StorageClass for PVCs. | string | "" |
| global.imagePullSecrets | Global Docker registry secret names. | list | [] |
| global.imageRegistry | Global Docker image registry. Overrides per-image registries for all containers. | string | "" |
| host | Custom hostname. Overrides openshift.clusterRouterBase for URL generation. | string | "" |
| hostAliases | Host aliases for /etc/hosts entries. | list | [] |
| httpRoute | Gateway API HTTPRoute configuration. | object | {"annotations":{},"enabled":false,"hostnames":[],"labels":{},"parentRefs":[],"rules":[]} |
| httpRoute.labels | Additional labels for the HTTPRoute resource. | object | {} |
| httpRoute.rules | HTTPRoute rules. Each rule may set option port (backend service port). Defaults to service.port when omitted. |
list | [] |
| image | Container image configuration. | object | {"digest":"","pullPolicy":"IfNotPresent","registry":"quay.io","repository":"rhdh-community/rhdh","tag":"next"} |
| image.digest | Overrides the image tag with an image digest. | string | "" |
| imagePullSecrets | Secrets for pulling images from private registries (merged with global.imagePullSecrets). | list | [] |
| ingress | Kubernetes Ingress configuration. | object | {"annotations":{},"className":"","enabled":false,"hosts":[{"host":"{{ .Values.host }}","paths":[{"path":"/","pathType":"Prefix"}]}],"tls":[]} |
| ingress.hosts | Ingress hosts. Each path may set option port (backend service port). Defaults to service.port when omitted (e.g. use 4180 for an oauth2-proxy sidecar). |
list | [{"host":"{{ .Values.host }}","paths":[{"path":"/","pathType":"Prefix"}]}] |
| intelligentAssistant | Built-in Intelligent Assistant feature configuration. | object | {"config":{"profile":{"existingConfigMap":{"key":"","name":""}},"stack":{"existingConfigMap":{"key":"","name":""}}},"core":{"argsOverride":[],"commandOverride":[],"extraArgs":[],"extraEnv":[],"extraVolumeMounts":[],"image":{"digest":"","registry":"quay.io","repository":"lightspeed-core/lightspeed-stack","tag":"0.7.1"},"imagePullPolicy":"IfNotPresent","resources":{"limits":{"cpu":"1000m","memory":"2Gi"},"requests":{"cpu":"100m","memory":"512Mi"}},"securityContext":{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true,"runAsNonRoot":true,"seccompProfile":{"type":"RuntimeDefault"}}},"enabled":true,"existingSecret":"","okp":{"affinity":{},"embeddingModel":"ibm-granite/granite-embedding-small-english-r2","enabled":false,"httpd":{"compressed":"true","encrypt":"false","serverName":"localhost"},"image":{"digest":"","pullPolicy":"IfNotPresent","registry":"registry.redhat.io","repository":"offline-knowledge-portal/rhokp-rhel9","tag":"1.3.0-1792010339"},"imagePullSecrets":[],"ingress":{"annotations":{},"className":"","enabled":true,"host":"","tls":{"enabled":false,"secretName":""}},"livenessProbe":{"initialDelaySeconds":30,"periodSeconds":30},"nodeSelector":{},"readinessProbe":{"initialDelaySeconds":10,"periodSeconds":10},"replicaCount":1,"resources":{"limits":{"cpu":"2","memory":"4Gi"},"requests":{"cpu":"200m","memory":"2Gi"}},"route":{"enabled":true,"tls":{"insecureEdgeTerminationPolicy":"Redirect","termination":"edge"}},"securityContext":{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"runAsNonRoot":true,"seccompProfile":{"type":"RuntimeDefault"}},"service":{"type":"ClusterIP"},"solr":{"hostBind":"0.0.0.0","memory":"1g"},"tolerations":[],"topologySpreadConstraints":[]},"plugins":[{"enabled":true,"package":"ref://red-hat-developer-hub-backstage-plugin-intelligent-assistant"},{"enabled":true,"package":"ref://red-hat-developer-hub-backstage-plugin-intelligent-assistant-backend"}],"runtimeVolume":{"emptyDir":{},"persistentVolumeClaim":{},"type":"emptyDir"}} |
| intelligentAssistant.config | Configuration files mounted into the sidecar. By default, the chart creates ConfigMaps from bundled source files. Set existingConfigMap to use a pre-existing ConfigMap instead. | object | {"profile":{"existingConfigMap":{"key":"","name":""}},"stack":{"existingConfigMap":{"key":"","name":""}}} |
| intelligentAssistant.config.profile | Python profile with prompt templates (rhdh-profile.py). | object | {"existingConfigMap":{"key":"","name":""}} |
| intelligentAssistant.config.profile.existingConfigMap | Use an existing ConfigMap instead of the bundled default. | object | Created from bundled rhdh-profile.py |
| intelligentAssistant.config.profile.existingConfigMap.key | Key within the ConfigMap that holds the file content. Defaults to the bundled filename (rhdh-profile.py) if not set. | string | "" |
| intelligentAssistant.config.profile.existingConfigMap.name | Name of the existing ConfigMap. | string | "" |
| intelligentAssistant.config.stack | Lightspeed Core service configuration (lightspeed-stack.yaml). | object | {"existingConfigMap":{"key":"","name":""}} |
| intelligentAssistant.config.stack.existingConfigMap | Use an existing ConfigMap instead of the bundled default. | object | Created from bundled lightspeed-stack.yaml |
| intelligentAssistant.config.stack.existingConfigMap.key | Key within the ConfigMap that holds the file content. Defaults to the bundled filename (lightspeed-stack.yaml) if not set. | string | "" |
| intelligentAssistant.config.stack.existingConfigMap.name | Name of the existing ConfigMap. | string | "" |
| intelligentAssistant.core | Lightspeed Core sidecar container. | object | {"argsOverride":[],"commandOverride":[],"extraArgs":[],"extraEnv":[],"extraVolumeMounts":[],"image":{"digest":"","registry":"quay.io","repository":"lightspeed-core/lightspeed-stack","tag":"0.7.1"},"imagePullPolicy":"IfNotPresent","resources":{"limits":{"cpu":"1000m","memory":"2Gi"},"requests":{"cpu":"100m","memory":"512Mi"}},"securityContext":{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true,"runAsNonRoot":true,"seccompProfile":{"type":"RuntimeDefault"}}} |
| intelligentAssistant.core.argsOverride | Override the container's default args. Leave empty to use the chart defaults. | list | [] |
| intelligentAssistant.core.commandOverride | Override the container's default command. Leave empty to use the image entrypoint. | list | [] |
| intelligentAssistant.core.extraArgs | Extra arguments appended after the default arguments. Ignored when argsOverride is set. | list | [] |
| intelligentAssistant.existingSecret | Name of an existing Secret that supplies credentials and settings to the lightspeed-core container. Configure the provider itself in lightspeed-stack.yaml. If empty, no Secret is loaded. Expected keys (all optional; include only keys used by the configured provider): VLLM_URL, VLLM_API_KEY, VLLM_MAX_TOKENS, VLLM_TLS_VERIFY, OPENAI_API_KEY, VERTEX_AI_PROJECT, VERTEX_AI_LOCATION, GOOGLE_APPLICATION_CREDENTIALS, ENABLE_VALIDATION, VALIDATION_PROVIDER, VALIDATION_MODEL_NAME Set ENABLE_VALIDATION to question_validity to enable validation. GOOGLE_APPLICATION_CREDENTIALS must be a path to a credentials file mounted in the sidecar. See files/intelligent-assistant/secret.example.yaml for a reference template. | string | "" |
| intelligentAssistant.okp | OKP (Offline Knowledge Portal) configuration. Deploys an OKP instance for RHDH documentation retrieval when explicitly enabled. The image is large and can significantly increase initial installation time. | object | {"affinity":{},"embeddingModel":"ibm-granite/granite-embedding-small-english-r2","enabled":false,"httpd":{"compressed":"true","encrypt":"false","serverName":"localhost"},"image":{"digest":"","pullPolicy":"IfNotPresent","registry":"registry.redhat.io","repository":"offline-knowledge-portal/rhokp-rhel9","tag":"1.3.0-1792010339"},"imagePullSecrets":[],"ingress":{"annotations":{},"className":"","enabled":true,"host":"","tls":{"enabled":false,"secretName":""}},"livenessProbe":{"initialDelaySeconds":30,"periodSeconds":30},"nodeSelector":{},"readinessProbe":{"initialDelaySeconds":10,"periodSeconds":10},"replicaCount":1,"resources":{"limits":{"cpu":"2","memory":"4Gi"},"requests":{"cpu":"200m","memory":"2Gi"}},"route":{"enabled":true,"tls":{"insecureEdgeTerminationPolicy":"Redirect","termination":"edge"}},"securityContext":{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"runAsNonRoot":true,"seccompProfile":{"type":"RuntimeDefault"}},"service":{"type":"ClusterIP"},"solr":{"hostBind":"0.0.0.0","memory":"1g"},"tolerations":[],"topologySpreadConstraints":[]} |
| intelligentAssistant.okp.affinity | Pod scheduling: affinity rules for the OKP pod. | object | {} |
| intelligentAssistant.okp.embeddingModel | Sentence Transformers model ID used by Lightspeed Core for query embeddings. Must match the model used to index OKP content. | string | "ibm-granite/granite-embedding-small-english-r2" |
| intelligentAssistant.okp.enabled | Enable OKP-backed RHDH documentation retrieval. Disabled by default on all platforms. | bool | false |
| intelligentAssistant.okp.imagePullSecrets | Image pull secrets for the OKP container image. Required on vanilla Kubernetes to authenticate with registry.redhat.io. Not needed on OpenShift where the cluster-wide pull secret covers Red Hat registries. | list | [] |
| intelligentAssistant.okp.livenessProbe | Liveness probe configuration for the OKP container. | object | {"initialDelaySeconds":30,"periodSeconds":30} |
| intelligentAssistant.okp.nodeSelector | Pod scheduling: node selector for the OKP pod. | object | {} |
| intelligentAssistant.okp.readinessProbe | Readiness probe configuration for the OKP container. | object | {"initialDelaySeconds":10,"periodSeconds":10} |
| intelligentAssistant.okp.securityContext | OKP container security context. readOnlyRootFilesystem is not set because Solr (Lucene) writes a lock file into its pre-built index directory at runtime. | object | {"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"runAsNonRoot":true,"seccompProfile":{"type":"RuntimeDefault"}} |
| intelligentAssistant.okp.tolerations | Pod scheduling: tolerations for the OKP pod. | list | [] |
| intelligentAssistant.okp.topologySpreadConstraints | Pod scheduling: topology spread constraints for the OKP pod. | list | [] |
| intelligentAssistant.plugins | Intelligent Assistant dynamic plugin packages. | list | [{"enabled":true,"package":"ref://red-hat-developer-hub-backstage-plugin-intelligent-assistant"},{"enabled":true,"package":"ref://red-hat-developer-hub-backstage-plugin-intelligent-assistant-backend"}] |
| intelligentAssistant.runtimeVolume | Writable scratch volume for the sidecar (/tmp). | object | {"emptyDir":{},"persistentVolumeClaim":{},"type":"emptyDir"} |
| intelligentAssistant.runtimeVolume.type | Volume type: "emptyDir" or "persistentVolumeClaim". | string | "emptyDir" |
| lifecycleHooks | Lifecycle hooks for the main RHDH container. Ref: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/ | object | {} |
| livenessProbe | Liveness probe configuration. | object | {"failureThreshold":3,"httpGet":{"path":"/.backstage/health/v1/liveness","port":"backend","scheme":"HTTP"},"periodSeconds":10,"successThreshold":1,"timeoutSeconds":4} |
| metrics | Prometheus metrics configuration. | object | {"serviceMonitor":{"annotations":{},"enabled":false,"interval":"","labels":{},"path":"/metrics","port":"http-metrics"}} |
| nameOverride | Override the chart name used in resource naming. | string | "developer-hub" |
| nodeSelector | Node labels for pod assignment. | object | {} |
| openshift | OpenShift-specific configuration. | object | {"clusterRouterBase":"apps.example.com","route":{"annotations":{},"enabled":true,"host":"{{ .Values.host }}","path":"/","targetPort":"http-backend","tls":{"caCertificate":"","certificate":"","destinationCACertificate":"","enabled":true,"insecureEdgeTerminationPolicy":"Redirect","key":"","termination":"edge"},"wildcardPolicy":"None"}} |
| openshift.clusterRouterBase | Cluster router base domain used to auto-generate the hostname. | string | "apps.example.com" |
| openshift.route | OpenShift Route configuration. | object | {"annotations":{},"enabled":true,"host":"{{ .Values.host }}","path":"/","targetPort":"http-backend","tls":{"caCertificate":"","certificate":"","destinationCACertificate":"","enabled":true,"insecureEdgeTerminationPolicy":"Redirect","key":"","termination":"edge"},"wildcardPolicy":"None"} |
| orchestrator | Orchestrator (Serverless workflows) configuration. | object | {"enabled":false,"plugins":[{"enabled":true,"package":"ref://red-hat-developer-hub-backstage-plugin-orchestrator-backend"},{"enabled":true,"package":"ref://red-hat-developer-hub-backstage-plugin-orchestrator-form-widgets"},{"enabled":true,"package":"ref://red-hat-developer-hub-backstage-plugin-orchestrator"},{"enabled":true,"package":"ref://red-hat-developer-hub-backstage-plugin-scaffolder-backend-module-orchestrator"}],"serverlessLogicOperator":{"enabled":true},"serverlessOperator":{"enabled":true},"sonataflowPlatform":{"dataIndex":{"image":{"digest":"","registry":"","repository":"","tag":""}},"dbCreationJob":{"activeDeadlineSeconds":120,"backoffLimit":2,"image":{"digest":"{{ .Values.postgresql.image.digest }}","registry":"{{ .Values.postgresql.image.registry }}","repository":"{{ .Values.postgresql.image.repository }}","tag":"{{ .Values.postgresql.image.tag }}"},"ttlSecondsAfterFinished":null},"eventing":{"broker":{"name":"","namespace":""}},"externalDB":{"existingSecret":"","host":"","name":"","port":""},"jobService":{"image":{"digest":"","registry":"","repository":"","tag":""}},"monitoring":{"enabled":true},"resources":{"limits":{"cpu":"500m","memory":"1Gi"},"requests":{"cpu":"250m","memory":"64Mi"}}}} |
| orchestrator.sonataflowPlatform.dataIndex | SonataFlow Data Index service configuration. | object | {"image":{"digest":"","registry":"","repository":"","tag":""}} |
| orchestrator.sonataflowPlatform.dataIndex.image | Override the Data Index container image. If empty, the operator default is used. | object | {"digest":"","registry":"","repository":"","tag":""} |
| orchestrator.sonataflowPlatform.dbCreationJob | Database creation Job configuration. | object | {"activeDeadlineSeconds":120,"backoffLimit":2,"image":{"digest":"{{ .Values.postgresql.image.digest }}","registry":"{{ .Values.postgresql.image.registry }}","repository":"{{ .Values.postgresql.image.repository }}","tag":"{{ .Values.postgresql.image.tag }}"},"ttlSecondsAfterFinished":null} |
| orchestrator.sonataflowPlatform.dbCreationJob.image | Container image for the create-db Job. | object | {"digest":"{{ .Values.postgresql.image.digest }}","registry":"{{ .Values.postgresql.image.registry }}","repository":"{{ .Values.postgresql.image.repository }}","tag":"{{ .Values.postgresql.image.tag }}"} |
| orchestrator.sonataflowPlatform.externalDB | External database connection. Used when postgresql.enabled is false. | object | {"existingSecret":"","host":"","name":"","port":""} |
| orchestrator.sonataflowPlatform.externalDB.existingSecret | Name of a Secret containing POSTGRES_HOST, POSTGRES_PORT, POSTGRES_USER, POSTGRES_PASSWORD keys. | string | "" |
| orchestrator.sonataflowPlatform.externalDB.host | Database host (used in JDBC URLs). | string | "" |
| orchestrator.sonataflowPlatform.externalDB.name | Database name to connect to for the CREATE DATABASE command. | string | "" |
| orchestrator.sonataflowPlatform.externalDB.port | Database port (used in JDBC URLs). | string | "" |
| orchestrator.sonataflowPlatform.jobService | SonataFlow Job Service configuration. | object | {"image":{"digest":"","registry":"","repository":"","tag":""}} |
| orchestrator.sonataflowPlatform.jobService.image | Override the Job Service container image. If empty, the operator default is used. | object | {"digest":"","registry":"","repository":"","tag":""} |
| podAnnotations | Annotations to add to the pod. | object | {} |
| podDisruptionBudget | Pod Disruption Budget configuration. | object | {"create":false,"maxUnavailable":1,"minAvailable":""} |
| podLabels | Labels to add to the pod. | object | {} |
| podSecurityContext | Pod-level security context. | object | {} |
| postgresql | Built-in PostgreSQL database (bitnami subchart). | object | {"auth":{"secretKeys":{"adminPasswordKey":"postgres-password","userPasswordKey":"password"}},"enabled":true,"image":{"digest":"","registry":"quay.io","repository":"fedora/postgresql-18","tag":"latest"},"postgresqlDataDir":"/var/lib/pgsql/data/userdata","primary":{"containerSecurityContext":{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"enabled":false},"extraEnvVars":[{"name":"POSTGRESQL_ADMIN_PASSWORD","valueFrom":{"secretKeyRef":{"key":"{{- include \"rhdh.postgresql.adminPasswordKey\" . }}","name":"{{- include \"rhdh.postgresql.secretName\" . }}"}}}],"networkPolicy":{"enabled":false},"persistence":{"enabled":true,"mountPath":"/var/lib/pgsql/data","size":"1Gi"},"podSecurityContext":{"enabled":false},"resources":{"limits":{"cpu":"250m","ephemeral-storage":"20Mi","memory":"1024Mi"},"requests":{"cpu":"250m","memory":"256Mi"}}},"readReplicas":{"networkPolicy":{"enabled":false}},"serviceBindings":{"enabled":true}} |
| preInitContainers | Init containers to run BEFORE the system init containers (e.g. inject auth credentials before install-dynamic-plugins runs). | list | [] |
| priorityClassName | Priority class name for pod scheduling. Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ | string | "" |
| readinessProbe | Readiness probe configuration. | object | {"failureThreshold":3,"httpGet":{"path":"/.backstage/health/v1/readiness","port":"backend","scheme":"HTTP"},"periodSeconds":10,"successThreshold":2,"timeoutSeconds":4} |
| replicaCount | Number of desired pods. | int | 1 |
| resources | Resource requests and limits for the main RHDH container. | object | {"limits":{"cpu":"1000m","ephemeral-storage":"5Gi","memory":"2.5Gi"},"requests":{"cpu":"250m","memory":"1Gi"}} |
| revisionHistoryLimit | Number of old ReplicaSets to retain. | int | 10 |
| service | Service configuration. | object | {"annotations":{},"clusterIP":"","externalTrafficPolicy":"","extraPorts":[{"name":"http-metrics","port":9464,"targetPort":9464}],"ipFamilies":[],"ipFamilyPolicy":"","loadBalancerIP":"","loadBalancerSourceRanges":[],"nodePort":"","port":7007,"sessionAffinity":"","type":"ClusterIP"} |
| service.extraPorts | Additional service ports. | list | [{"name":"http-metrics","port":9464,"targetPort":9464}] |
| service.ipFamilies | IP families for dual-stack networking. | list | [] |
| service.ipFamilyPolicy | IP family policy for dual-stack networking. | string | "" |
| service.nodePort | Node port for NodePort/LoadBalancer service types (range 30000-32767). | string | "" |
| serviceAccount | ServiceAccount configuration. | object | {"annotations":{},"automount":true,"create":false,"labels":{},"name":""} |
| serviceAccount.labels | Additional labels for the ServiceAccount. | object | {} |
| serviceAccount.name | The name of the service account to use. If not set and create is true, a name is generated using the fullname template. | string | "" |
| startupProbe | Startup probe configuration. Gives the application time to start before liveness/readiness probes kick in. | object | {"failureThreshold":3,"httpGet":{"path":"/.backstage/health/v1/liveness","port":"backend","scheme":"HTTP"},"initialDelaySeconds":30,"periodSeconds":20,"successThreshold":1,"timeoutSeconds":4} |
| strategy | Deployment update strategy. | object | {} |
| terminationGracePeriodSeconds | Duration in seconds the pod needs to terminate gracefully before being force killed. Ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination | string | nil |
| test | Test pod configuration for helm test. |
object | {"enabled":true,"image":{"digest":"","pullPolicy":"IfNotPresent","registry":"quay.io","repository":"curl/curl","tag":"8.23.0"},"securityContext":{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true}} |
| tolerations | Tolerations for pod assignment. | list | [] |
| topologySpreadConstraints | Topology spread constraints for pod scheduling. | list | [] |
| workload | Kubernetes workload controller for Backstage pod. | object | {"kind":"Deployment","statefulSet":{"annotations":{},"extraVolumeClaimTemplates":[],"persistentVolumeClaimRetentionPolicy":{},"podManagementPolicy":"","serviceName":"","updateStrategy":{}}} |
| workload.kind | Workload kind: Deployment (default) or StatefulSet. | string | "Deployment" |
| workload.statefulSet.annotations | Annotations on the StatefulSet resource. | object | {} |
| workload.statefulSet.extraVolumeClaimTemplates | Extra volumeClaimTemplates appended after the chart-managed dynamic-plugins-root claim (when type=statefulSetPVC). | list | [] |
| workload.statefulSet.persistentVolumeClaimRetentionPolicy | Optional PVC retention policy for the StatefulSet. | object | {} |
| workload.statefulSet.podManagementPolicy | Pod management policy for the StatefulSet. | string | "" |
| workload.statefulSet.serviceName | service name for the StatefulSet. Defaults to headless when empty. Service ({fullname}-headless) must match an existing service. | string | "" |
| workload.statefulSet.updateStrategy | StatefulSet update strategy. | object | {} |
This chart defaults to an opinionated deployment of Red Hat Developer Hub that provides users with a usable instance out of the box.
Features enabled by the default chart configuration:
- Uses rhdh that pre-loads a lot of useful plugins and features
- Exposes a
Routefor easy access to the instance - Enables OpenShift-compatible PostgreSQL database storage
- Built-in Intelligent Assistant feature (enabled by default)
- Dynamic plugins system with catalog index support
For additional instance features please consult the documentation for rhdh.
Additional features can be enabled by extending the default configuration at:
appConfig:
# Inline app-config.yaml for the instance
extraEnv:
# Additional environment variables (appended to system defaults)
extraVolumes:
# Additional volumes (appended to system defaults)
extraVolumeMounts:
# Additional volume mounts (appended to system defaults)
extraDeploy:
# Additional Kubernetes resources deployed with the releaseThis charts defaults to using the RHDH image that is OpenShift compatible:
quay.io/rhdh-community/rhdh:nextSystem-required volumes, volume mounts, environment variables, init containers, and sidecar containers are hardcoded in the Backstage pod template (used by both Deployment and StatefulSet). User-provided extra* values are always appended after the system defaults:
extraVolumes— appended after dynamic-plugins-root, temp, npmcacache, extensions-catalog, etc.extraVolumeMounts— appended after dynamic-plugins-root, extensions, temp mountsextraEnv— appended after APP_CONFIG_backend_listen_port, BACKEND_SECRET, POSTGRES_* varsextraInitContainers— appended after install-dynamic-plugins and Intelligent Assistant RAG initextraContainers— appended after the Lightspeed Core sidecar
This means you never need to copy system defaults to add your own entries.
If you need full control, the corresponding *Override fields (envOverride, envFromOverride, commandOverride, argsOverride) replace the system defaults entirely — nothing is auto-injected when an override is set.
By default, the chart creates a Kubernetes Deployment (workload.kind: Deployment). To use a StatefulSet instead:
# values.yaml
workload:
kind: StatefulSetBoth kinds render the same Backstage pod (containers, volumes, probes, dynamic plugins, Intelligent Assistant, init containers). Only the workload controller changes; the pod definition is shared.
When to use StatefulSet
- You need StatefulSet-specific settings (
workload.statefulSet.updateStrategy,podManagementPolicy,extraVolumeClaimTemplates, or optional PVC retention policy). - You want parity with the RHDH Operator, which supports
spec.deployment.kind: StatefulSet(operator documentation).
Most installs should keep the default Deployment (no app StatefulSet and no headless Service).
Values that behave the same for both kinds
replicaCount, autoscaling, probes, resources, dynamicPlugins, extraVolumes, podLabels, podAnnotations, and other pod-level settings apply regardless of workload.kind. When autoscaling.enabled is true, the HorizontalPodAutoscaler scales the selected kind (Deployment or StatefulSet).
Values that depend on the workload kind
| Helm value | Deployment | StatefulSet |
|---|---|---|
| Update strategy | strategy |
workload.statefulSet.updateStrategy |
| ReplicaSet revision history | revisionHistoryLimit |
Not used |
| Controller annotations (not the pod) | deploymentAnnotations |
workload.statefulSet.annotations |
| Shared controller annotations | commonAnnotations |
commonAnnotations |
| PVC claim templates | Not used | workload.statefulSet.extraVolumeClaimTemplates |
| PVC retention policy | Not used | workload.statefulSet.persistentVolumeClaimRetentionPolicy |
StatefulSet-only settings
When workload.kind is StatefulSet, the chart also renders a headless Service ({fullname}-headless, clusterIP: None). The StatefulSet serviceName points at that Service (stable pod network identity).
The existing ClusterIP Service (templates/service.yaml) is unchanged — OpenShift Routes, Ingress, and helm test still use it for application traffic.
Override workload.statefulSet.serviceName only if you create your own governing Service.
# values.yaml
workload:
kind: StatefulSet
statefulSet:
serviceName: "" # default: {fullname}-headless
podManagementPolicy: OrderedReady # or Parallel; omit when empty
updateStrategy:
type: RollingUpdate
extraVolumeClaimTemplates: []
persistentVolumeClaimRetentionPolicy: {}
annotations: {}Stable dynamic-plugins storage
To give each pod a StatefulSet-owned PVC for dynamic-plugins-root:
workload:
kind: StatefulSet
dynamicPlugins:
volume:
type: statefulSetPVC
statefulSetPVC:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5GiThe chart injects a volumeClaimTemplate named dynamic-plugins-root and mounts it on the pod. Kubernetes creates one PVC per pod named dynamic-plugins-root-{fullname}-{ordinal} (for example dynamic-plugins-root-my-rhdh-developer-hub-0). This requires workload.kind=StatefulSet.
To add additional StatefulSet-owned PVCs, use workload.statefulSet.extraVolumeClaimTemplates.
Changing workload.kind on an existing release
For a normal Helm-managed release, changing workload.kind and running helm upgrade replaces the rendered manifest: Helm removes the old workload kind (Deployment or StatefulSet) and creates the new one. Expect a brief cutover while pods are recreated. If you applied manifests outside Helm (for example GitOps that keeps both objects) or an old controller was left behind, delete the obsolete Deployment or StatefulSet so two controllers do not target the same selector.
Example install
helm upgrade -i my-rhdh redhat-developer/redhat-developer-hub \
--set workload.kind=StatefulSetThis chart offers an OpenShift Route resource enabled by default. In order to use the chart without it, please set openshift.route.enabled to false and switch to the Ingress resource via ingress values.
Routes can be further configured via the openshift.route field.
To manually provide the Backstage pod with the right context, please add the following value:
# values.yaml
openshift:
clusterRouterBase: apps.example.comTip: you can use
helm upgrade -i --set openshift.clusterRouterBase=apps.example.com ...instead of a value file
Custom hosts are also supported via the following shorthand:
# values.yaml
host: backstage.example.comNote: The hostname is derived from
hostif set, otherwise fromopenshift.clusterRouterBase(as<release-name>-<chart-name>.<clusterRouterBase>). When both fields are set,hosttakes precedence. These are templating shorthands. For full manual control, configure the values under theopenshift.routekey directly.
Any custom modifications to how backstage is being exposed may require additional changes to the values.yaml:
# values.yaml
appConfig:
app:
baseUrl: 'https://{{- include "rhdh.hostname" . }}'
backend:
baseUrl: 'https://{{- include "rhdh.hostname" . }}'
cors:
origin: 'https://{{- include "rhdh.hostname" . }}'The chart supports automatic plugin discovery through a catalog index OCI image. This is configured via catalogIndex.image (with registry, repository, and tag fields) and lets you use a pre-defined set of dynamic plugins.
You can also configure additional catalog index images via catalogIndex.extraImages to make plugins from other sources discoverable in the Extensions UI. Each extra image contributes catalog entities only (no dynamic-plugins.default.yaml handling).
For detailed information on configuring the catalog index, including how to override the default image, use a private registry, or add extra catalog index images, see the Catalog Index Configuration documentation.
Use extraDeploy to ship arbitrary Kubernetes resources alongside the chart release — NetworkPolicies, ConfigMaps, Secrets, Jobs, or any other kind. Each entry is rendered through tpl, so Helm template expressions are supported.
These resources are fully managed by Helm: they are created on install, updated on upgrade, and deleted on uninstall. Removing an entry from extraDeploy and running helm upgrade will delete the corresponding resource. This is in contrast to resources applied externally (e.g., via kubectl apply), which Helm does not track.
Entries can be objects (plain YAML) or strings (block scalars for multi-line template helpers like include).
Object form — template expressions work in individual string values:
extraDeploy:
- apiVersion: v1
kind: ConfigMap
metadata:
name: '{{ include "rhdh.fullname" . }}-my-config'
data:
key: valueString form — required when using helpers that expand into multiple YAML keys (e.g., rhdh.selectorLabels, rhdh.labels):
extraDeploy:
- |
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "rhdh.fullname" . }}-allow-http-egress
labels:
{{- include "rhdh.labels" . | nindent 8 }}
spec:
podSelector:
matchLabels:
{{- include "rhdh.selectorLabels" . | nindent 10 }}
policyTypes:
- Egress
egress:
- ports:
- port: 80
protocol: TCPThis chart deploys a default-deny NetworkPolicy for the RHDH backend pod, blocking all ingress and egress traffic that is not explicitly allowed. When the built-in PostgreSQL is enabled (postgresql.enabled=true), the database pods also get their own default-deny policy with selective allow rules. When OKP is active, its pods receive a separate default-deny ingress and egress policy with rules allowing only the required HTTP ingress.
When orchestrator.enabled=true, the chart also creates NetworkPolicies for SonataFlow Data Index and Jobs Service pods (default-deny, then selective allow) and for other pods labeled app.kubernetes.io/managed-by: sonataflow-operator. These include RHDH ↔ SonataFlow access, scoped SonataFlow egress (DNS, HTTPS, Kubernetes API, PostgreSQL, and RHDH on port 7007), and (when built-in PostgreSQL is enabled) PostgreSQL ingress from SonataFlow-managed pods. The Orchestrator create-db Job uses rhdh.selectorLabels, so it is covered by the existing RHDH → PostgreSQL allow. These policies are label-scoped and do not use namespace-wide podSelector: {}.
The following traffic is allowed out of the box:
| Direction | Port | Destination / Source | Purpose |
|---|---|---|---|
| Egress | 53, 5353 (UDP/TCP) | Any | DNS resolution |
| Egress | 443 (TCP) | Any | HTTPS (Git forges, auth providers, external APIs) |
| Egress | 6443 (TCP) | Any | Kubernetes API server — used by plugins that query cluster resources (Tekton, ArgoCD/GitOps, Topology, KServe model catalog, etc.). On some managed platforms the API server listens on 443 instead. |
| Egress | 5432 (TCP) | Built-in PostgreSQL pods (scoped) or any (external DB) | Database access |
| Egress | 6379 (TCP) | Any | Redis (BYO — no pod/namespace selector) |
| Egress | 80 (TCP) | Any destination | LCORE access to an HTTP OKP Ingress (NP created only when OKP is enabled; disabled by default) |
| Egress | 8080 (TCP) | OKP pods | LCORE access to the internal OKP Service fallback (NP created only when OKP is enabled; disabled by default) |
| Ingress | 7007 (TCP) | OpenShift router namespace or any namespace (non-OCP) | User traffic via Route / Ingress |
| Ingress | 8080 (TCP) | RHDH pods and the OpenShift router namespace or any namespace (non-OCP) | OKP queries and product-document citations via Route / Ingress |
| Ingress | 9464 (TCP) | openshift-monitoring, openshift-user-workload-monitoring, gmp-system, gke-gmp-system, monitoring |
Prometheus metrics scraping |
| Ingress | any | OpenShift router namespace → SonataFlow pods | Orchestrator / SonataFlow Routes (NP created only when orchestrator.enabled=true) |
| Ingress | any | RHDH pods → SonataFlow pods | Orchestrator backend to Data Index / workflows (NP created only when orchestrator.enabled=true) |
| Ingress | any | SonataFlow pods → SonataFlow pods (same or other namespaces) | SonataFlow inter-pod ingress and egress (NP created only when orchestrator.enabled=true) |
| Ingress | any | knative-eventing, knative-serving, openshift-serverless-logic → SonataFlow pods |
Orchestrator infra namespaces (NP created only when orchestrator.enabled=true) |
| Ingress | 8080 (TCP) | openshift-monitoring, openshift-user-workload-monitoring, gmp-system, gke-gmp-system, monitoring → SonataFlow pods |
SonataFlow metrics scrape (NP created only when orchestrator.enabled=true and orchestrator.sonataflowPlatform.monitoring.enabled=true) |
| Ingress | 7007 (TCP) | SonataFlow pods → RHDH pods | Workflows calling RHDH APIs (NP created only when orchestrator.enabled=true) |
| Egress | any | RHDH pods → SonataFlow pods | Orchestrator backend to SonataFlow / Data Index (NP created only when orchestrator.enabled=true) |
| Egress | 53, 5353 (UDP/TCP) | SonataFlow pods → any | SonataFlow DNS (NP created only when orchestrator.enabled=true) |
| Egress | 443, 6443 (TCP) | SonataFlow pods → any | SonataFlow HTTPS and Kubernetes API (NP created only when orchestrator.enabled=true) |
| Egress | 5432 (TCP) | SonataFlow pods → built-in PostgreSQL (scoped) or any (external DB) | SonataFlow database access (NP created only when orchestrator.enabled=true) |
| Egress | 7007 (TCP) | SonataFlow pods → RHDH pods | Workflows calling RHDH APIs (NP created only when orchestrator.enabled=true) |
| Ingress | 5432 (TCP) | SonataFlow pods (same or other namespaces) → built-in PostgreSQL primary | Orchestrator / SonataFlow database access (NP created only when orchestrator.enabled=true and postgresql.enabled=true) |
OKP initiates no outbound connections: its HTTP server and Solr process run in the same pod and serve the documentation embedded in the image. Its default-deny policy therefore blocks all new egress connections. NetworkPolicy is stateful, so response traffic for allowed ingress connections remains permitted.
RHDH already permits HTTPS egress on port 443. When OKP uses HTTP instead, the chart permits RHDH egress on port 80 for a Kubernetes Ingress or restricts port 8080 egress to OKP pods for the internal Service fallback. No additional RHDH egress policy is created when OKP_SERVICE_URL uses HTTPS.
When Orchestrator is enabled (orchestrator.enabled=true), the chart first creates default-deny NetworkPolicies for the SonataFlow Data Index and Jobs Service pods, then selective allow policies for pods labeled app.kubernetes.io/managed-by: sonataflow-operator. These allow ingress from the Knative Eventing/Serving and OpenShift Serverless Logic namespaces, from the OpenShift router (any port), from other SonataFlow pods in the same or other namespaces (ingress and egress), from RHDH (any port), and (when orchestrator.sonataflowPlatform.monitoring.enabled=true) from known monitoring namespaces on port 8080. SonataFlow egress is scoped to DNS, HTTPS (443), the Kubernetes API (6443), PostgreSQL (5432), and RHDH on port 7007; additional destinations must be allowed explicitly. Separately, the RHDH backend pod (selected via rhdh.selectorLabels) is allowed to reach SonataFlow pods (any port), and SonataFlow pods are allowed to reach RHDH on port 7007. When the built-in PostgreSQL is enabled (postgresql.enabled=true), Postgres admits TCP/5432 from SonataFlow-managed pods in the same or other namespaces; the Orchestrator create-db Job uses rhdh.selectorLabels and is covered by the existing RHDH backend PostgreSQL allow.
Redis egress is intentionally unscoped. RHDH does not deploy Redis; users bring their own instance, which may live in the same namespace, a different namespace, or an external managed service. The rule therefore allows egress on port 6379 to any destination.
Bitnami subchart NetworkPolicies are disabled (postgresql.primary.networkPolicy.enabled=false, postgresql.readReplicas.networkPolicy.enabled=false) because this chart provides its own tighter policies for the built-in database pods.
Helm test NetworkPolicies (allow-test-connection and allow-test-connection-ingress) are Helm test hooks: they are created just before helm test runs and cleaned up automatically on success.
Adding your own NetworkPolicies. Kubernetes evaluates NetworkPolicies additively — if any policy allows a connection, the connection is permitted. You cannot tighten an existing allow rule by adding another NetworkPolicy; you can only broaden access. To add extra allow rules (for example, allowing HTTP egress to an internal auth provider), use extraDeploy or apply your own NetworkPolicy resources externally:
extraDeploy:
- |
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "rhdh.fullname" . }}-allow-http-egress
spec:
podSelector:
matchLabels:
{{- include "rhdh.selectorLabels" . | nindent 10 }}
policyTypes:
- Egress
egress:
- ports:
- port: 80
protocol: TCPFor more information on user workload monitoring on OpenShift, see Configuring user workload monitoring.
Use intelligentAssistant.enabled to enable or disable the built-in Intelligent Assistant feature.
When enabled, the chart adds the default Intelligent Assistant dynamic plugins (ref://red-hat-developer-hub-backstage-plugin-intelligent-assistant and ref://red-hat-developer-hub-backstage-plugin-intelligent-assistant-backend), a Lightspeed Core sidecar listening on port 8080, chart-generated ConfigMaps, and a writable runtime volume. Override intelligentAssistant.plugins for disconnected environments. Plugin app-config uses the intelligent-assistant: namespace (not lightspeed:).
The bundled lightspeed-stack.yaml does not enable an LLM inference provider. Create a provider-enabled stack in a ConfigMap and reference it with intelligentAssistant.config.stack.existingConfigMap. Supply that provider's credentials and settings with intelligentAssistant.existingSecret; the chart does not create the Secret.
This is a breaking change from chart 1.x: rename lightspeed: to intelligentAssistant: in your values. Chart-generated ConfigMaps are named {fullname}-ia-{stack,profile} instead of {fullname}-lightspeed-*, so Helm replaces those objects on upgrade. The ia infix is used because Kubernetes names are limited to 63 characters.
Chart 2.1.0 removes intelligentAssistant.config.server and the separate Llama Stack config.yaml. Remove the server entry from existing values. Move custom Llama Stack settings into the lightspeed-stack.yaml supplied through intelligentAssistant.config.stack.existingConfigMap.
Use intelligentAssistant.runtimeVolume to change the writable /tmp runtime storage between emptyDir and an existing PVC reference. The chart mounts that volume at /tmp so both generated temporary files and /tmp/data remain writable.
When using the built-in Intelligent Assistant feature, do not also keep those plugin packages in dynamicPlugins.plugins. Existing installations that previously configured Lightspeed or Intelligent Assistant there should remove those entries if the built-in defaults are sufficient, or move their custom package definitions to intelligentAssistant.plugins; otherwise the rendered dynamic-plugins.yaml will contain duplicate plugin entries.
The Lightspeed Core sidecar loads intelligentAssistant.existingSecret as environment variables. If you update that Secret outside of Helm, Kubernetes does not guarantee that the Backstage Pod restarts automatically. Use a no-op helm upgrade or manually restart the Backstage workload (Deployment or StatefulSet) after changing the secret data.
RHDH documentation retrieval is disabled by default in Intelligent Assistant. To enable it, set intelligentAssistant.okp.enabled=true. This deploys the OKP workload and uses the OKP-enabled Lightspeed Stack configuration. The OKP container image is large, so enabling it can significantly increase the initial installation time while the image is downloaded. On vanilla Kubernetes, you must also configure the OKP Ingress and Red Hat registry credentials. See Intelligent Assistant and OKP integration for platform-specific configuration.
To deploy this chart on vanilla Kubernetes or any other non-OCP platform, apply the following changes. Note that further customizations might be required, depending on your exact Kubernetes setup:
# values.yaml
host: # Specify your own Ingress host
openshift:
route:
enabled: false # OpenShift Routes do not exist on vanilla Kubernetes
ingress:
enabled: true # Use Kubernetes Ingress instead of OpenShift Route
podSecurityContext: # Vanilla Kubernetes doesn't feature OpenShift default SCCs with dynamic UIDs, adjust accordingly to the deployed image
fsGroup: 1001
postgresql:
primary:
podSecurityContext:
enabled: true
fsGroup: 26
volumePermissions:
enabled: trueOrchestrator brings serverless workflows into Backstage, focusing on the journey for application migration to the cloud, onboarding developers, and user-made workflows of Backstage actions or external systems. Orchestrator is a flavor of RHDH, and can be installed alongside RHDH in the same namespace and in the following way:
- Have an admin install the orchestrator-infra Helm Chart, which will install the prerequisites required to deploy the Orchestrator-flavored RHDH. This process will include installing cluster-wide resources, so should be done with admin privileges:
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add redhat-developer https://redhat-developer.github.io/rhdh-chart
helm install <release_name> redhat-developer/redhat-developer-hub-orchestrator-infra
- Manually approve the Install Plans created by the chart, and wait for the Openshift Serverless and Openshift Serverless Logic Operators to be deployed. To do so, follow the post-install notes given by the chart, or see them here
- Install the
redhat-developer-hubchart with Helm, enabling orchestrator, like so:
helm install <release_name> redhat-developer/redhat-developer-hub --set orchestrator.enabled=true
Enabling Orchestrator also creates label-scoped NetworkPolicies for SonataFlow-managed pods (default-deny for Data Index and Jobs Service, then selective allows), for RHDH ↔ SonataFlow access, and (when built-in PostgreSQL is enabled) for SonataFlow access to Postgres on TCP/5432; they are not namespace-wide. See NetworkPolicies for details.
Note that serverlessLogicOperator, and serverlessOperator are enabled by default. They can be disabled together or seperately by passing the following flags:
--set orchestrator.serverlessLogicOperator.enabled=false --set orchestrator.serverlessOperator.enabled=false
Workflows running with Orchestrator may use the Notifications plugin.
For this, you must enable the Notifications and Signals plugins.
To do so, add the plugins listed below to the dynamicPlugins.plugins list in your values file.
Do this before installing the Helm Chart, or upgrade the Helm release with the new values file.
dynamicPlugins:
plugins:
- enabled: true
package: "./dynamic-plugins/dist/backstage-plugin-notifications"
- enabled: true
package: "./dynamic-plugins/dist/backstage-plugin-signals"
- enabled: true
package: "./dynamic-plugins/dist/backstage-plugin-notifications-backend-dynamic"
- enabled: true
package: "./dynamic-plugins/dist/backstage-plugin-signals-backend-dynamic"Enabling these plugins will allow you to receive notifications from workflows running with Orchestrator.
To use orchestrator with an external DB, please follow the instructions in our documentation and populate the following values in the values.yaml:
orchestrator:
sonataflowPlatform:
externalDB:
existingSecret: <cred-secret>
name: ""
host: ""
port: ""The values for host and port should match the ones configured in the credential secret.
Please note that externalDB.name is the name of the user-configured existing database, not the database that the orchestrator and sonataflow resources will use.
A Job will run to create the 'sonataflow' database in the external database for the workflows to use.
Finally, install the Helm Chart (including setting up the external DB):
helm install <release_name> redhat-developer/redhat-developer-hub \
--set orchestrator.enabled=true \
--set orchestrator.sonataflowPlatform.externalDB.existingSecret=<cred-secret> \
--set orchestrator.sonataflowPlatform.externalDB.name=example \
--set orchestrator.sonataflowPlatform.externalDB.host=example \
--set orchestrator.sonataflowPlatform.externalDB.port=example