Migration from Helm Chart¶
This guide helps translate a deployment from the official Apache Superset Helm
chart to a Superset custom resource. It focuses on feature parity and on the
places where the operator intentionally uses a different model.
The operator covers the chart's core application features: web server, Celery workers, Celery Beat, Celery Flower, websocket server, database migration, application initialization, Python configuration, env vars, services, ingress, autoscaling, disruption budgets, probes, security contexts, scheduling constraints, sidecars, init containers, and custom volumes.
The main differences are deliberate:
- The operator does not install PostgreSQL or Redis/Valkey. Bring your own
database and cache, then reference them from the
SupersetCR. - Production mode rejects inline operator-managed secrets. Use Kubernetes
Secrets through
secretKeyFrom,metastore.uriFrom,metastore.passwordFrom, andvalkey.passwordFrom. - Lifecycle work is split into managed
migrateandinittasks instead of a Helm hook Job. This gives the operator status, retries, upgrade gating, and maintenance-page support. - Gateway API is the preferred way to route multiple Superset services. The built-in Ingress reconciler targets the web server service only.
Migration Workflow¶
- Export the Helm values used by the existing release:
-
Identify external dependencies. If the Helm release currently installed Bitnami PostgreSQL or Redis, provision replacement services before removing the chart. Do not delete the Helm release until database persistence and backups are understood.
-
Create Kubernetes Secrets for the Superset
SECRET_KEY, database connection string or password, and Valkey/Redis password. Prefer a full SQLAlchemy URI inmetastore.uriFromwhen migrating from Helm because it preserves the exact connection string. -
Translate application configuration from
configOverridesintospec.config. Move sensitive values out of Python source and into Secret backed env vars. -
Translate enabled chart components into operator components. In the operator, presence means enabled:
- Decide how the first operator reconciliation should handle lifecycle tasks.
If the existing Helm deployment already migrated and initialized the
database, either allow the operator to run its idempotent
migrateandinittasks during a planned window, or disable lifecycle initially:
Re-enable lifecycle after the first cutover if you want the operator to manage future upgrades.
-
Create the
SupersetCR with a name that will not collide with existing Helm resources, wait for it to become ready, then move traffic to the operator-managed Service, Ingress, or HTTPRoute. -
Remove the old Helm release only after the operator-managed deployment is serving traffic and background workers are healthy.
Value Mapping¶
Global Settings¶
| Helm chart value | Operator equivalent | Notes |
|---|---|---|
nameOverride, fullnameOverride |
metadata.name |
The operator derives child and workload names from the CR name. Generated names are not independently overridden. |
extraLabels |
Resource-specific labels | Use podTemplate.labels, component service.labels, networking.ingress.labels, or networking.gateway.labels. There is no single global label field for every generated object. |
image.repository, image.tag, image.pullPolicy |
spec.image.repository, spec.image.tag, spec.image.pullPolicy |
Per-component image overrides are available under each component's image field. |
imagePullSecrets |
spec.image.pullSecrets |
Applied to all component pods. |
runAsUser |
spec.podTemplate.podSecurityContext.runAsUser |
Prefer a full pod or container security context for production hardening. |
serviceAccountName, serviceAccount.create, serviceAccount.annotations |
spec.serviceAccount.name, spec.serviceAccount.create, spec.serviceAccount.annotations |
The operator can create or reference a ServiceAccount. |
bootstrapScript |
Custom image, init container, or command override | The operator does not generate a bootstrap script. Build dependencies into the image when possible. |
forceReload on chart components |
spec.forceReload |
Operator forceReload is global. For component-only restarts, change a component pod annotation. |
Secrets, Env Vars, and Config Files¶
| Helm chart value | Operator equivalent | Notes |
|---|---|---|
secretEnv.create |
External Kubernetes Secret | The operator does not create a general-purpose env Secret from CR fields. |
envFromSecret, envFromSecrets |
spec.podTemplate.container.envFrom |
Can also be set per component. |
extraEnv, extraEnvRaw |
spec.podTemplate.container.env |
env entries merge by name between top-level and per-component templates. |
extraSecretEnv |
Kubernetes Secret plus envFrom or env.valueFrom.secretKeyRef |
For operator-owned secrets, use first-class fields such as secretKeyFrom, metastore.uriFrom, and valkey.passwordFrom. |
configOverrides, configOverridesFiles |
spec.config or per-component config |
The operator appends this Python after generated SECRET_KEY, metastore, Valkey, Celery, and SQLAlchemy config. |
configFromSecret |
spec.config plus Secret-backed env vars |
The operator renders superset_config.py into ConfigMaps. Avoid putting secret values directly in Python config. |
extraConfigs |
External ConfigMap plus podTemplate.volumes and volumeMounts |
If you used import_datasources.yaml, add a custom lifecycle init command to import it. |
extraSecrets |
External Secret plus podTemplate.volumes and volumeMounts |
Mount additional files explicitly where your config expects them. |
extraVolumes, extraVolumeMounts |
spec.podTemplate.volumes, spec.podTemplate.container.volumeMounts |
Can be shared top-level or overridden per component. |
Database and Valkey/Redis¶
| Helm chart value | Operator equivalent | Notes |
|---|---|---|
supersetNode.connections.db_* |
spec.metastore |
Use uriFrom for an exact SQLAlchemy URI, or structured host, database, username, and passwordFrom. |
supersetNode.connections.redis_* |
spec.valkey |
valkey works with Redis-compatible services and renders cache, Celery broker/backend, and SQL Lab results backend config. |
supersetNode.connections.redis_ssl |
spec.valkey.ssl |
Mount client certs or CA bundles with podTemplate.volumes if needed. |
postgresql.* |
Not managed by this operator | Use a managed database, CloudNativePG, another PostgreSQL operator, or keep an existing PostgreSQL instance. |
redis.* |
Not managed by this operator | Use a managed Redis/Valkey service, Redis/Valkey operator, or separate Helm chart. |
Components¶
| Helm chart value | Operator equivalent | Notes |
|---|---|---|
supersetNode.* |
spec.webServer.* |
Web server Deployment, Service, config, probes, resources, HPA, and PDB. |
supersetNode.replicas.replicaCount |
spec.webServer.replicas |
If autoscaling is set, the HPA manages replicas. |
supersetNode.command |
spec.webServer.podTemplate.container.command |
The operator default is /usr/bin/run-server.sh. Gunicorn settings are structured under webServer.gunicorn. |
supersetWorker.* |
spec.celeryWorker.* |
Worker Deployment and config. Celery process settings are structured under celeryWorker.celery. |
supersetWorker.replicas.replicaCount |
spec.celeryWorker.replicas |
Top-level spec.replicas can provide a shared default. |
supersetCeleryBeat.enabled |
spec.celeryBeat: {} |
Celery Beat is always a singleton. |
supersetCeleryFlower.enabled |
spec.celeryFlower: {} |
Flower gets its own Deployment and Service. |
supersetCeleryFlower.service.* |
spec.celeryFlower.service.* |
Supports service type, port, nodePort, labels, and annotations. |
supersetWebsockets.enabled |
spec.websocketServer: {} |
Use a websocket image such as oneacrefund/superset-websocket, or your own image. |
supersetWebsockets.config |
Env vars or mounted config file | The operator does not render websocket config.json. Mount one with podTemplate.volumes, or configure the websocket server through env vars. |
init.enabled |
spec.lifecycle.disabled or task-level disabled |
Lifecycle is enabled by default. Set lifecycle.disabled: true to skip all tasks. |
init.command, init.initscript |
spec.lifecycle.migrate.command, spec.lifecycle.init.command |
The operator splits database migration and application initialization into separate tasks. |
init.createAdmin, init.adminUser, init.loadExamples |
spec.lifecycle.init.adminUser, spec.lifecycle.init.loadExamples |
These are allowed only in environment: Development. In production, create users through your normal identity and admin process or a custom task command using Secret-backed env vars. |
init.jobAnnotations |
Not applicable | Lifecycle tasks are CRD-managed pods, not Helm hook Jobs. |
Pod and Deployment Customization¶
| Helm chart value | Operator equivalent | Notes |
|---|---|---|
resources |
spec.podTemplate.container.resources |
Top-level resources apply to all components unless overridden. |
Component resources |
Component podTemplate.container.resources |
For example, spec.celeryWorker.podTemplate.container.resources. |
nodeSelector, tolerations, affinity, hostAliases, topologySpreadConstraints, priorityClassName |
spec.podTemplate.* |
Top-level values merge with per-component values according to the configuration guide. |
Component podAnnotations, podLabels |
Component podTemplate.annotations, podTemplate.labels |
Operator-managed labels are protected and cannot be overridden. |
Component deploymentAnnotations, deploymentLabels |
No direct equivalent | Prefer pod labels/annotations, service labels/annotations, or a cluster policy that mutates Deployments if you need Deployment metadata. |
Component strategy |
Component deploymentTemplate.strategy |
Also supports revisionHistoryLimit, minReadySeconds, and progressDeadlineSeconds. |
Component startupProbe, livenessProbe, readinessProbe |
Component podTemplate.container.startupProbe, livenessProbe, readinessProbe |
The operator provides defaults for served components. |
Component podSecurityContext, containerSecurityContext |
Component podTemplate.podSecurityContext, podTemplate.container.securityContext |
Can also be set top-level. |
Component extraContainers |
Component podTemplate.sidecars |
Sidecars merge by container name. |
Component initContainers |
Component podTemplate.initContainers |
Init containers merge by container name. |
Services and Networking¶
| Helm chart value | Operator equivalent | Notes |
|---|---|---|
service.type, service.port, service.nodePort.http, service.annotations |
spec.webServer.service.type, port, nodePort, annotations |
loadBalancerIP is not currently modeled; use provider annotations where possible. |
ingress.enabled, ingress.ingressClassName, ingress.annotations, ingress.hosts, ingress.tls, ingress.path, ingress.pathType |
spec.networking.ingress |
Operator-managed Ingress targets the web server. |
ingress.extraHostsRaw |
spec.networking.ingress.hosts for normal web routes, or a custom Ingress |
Use a separate Ingress for non-web backends or unusual raw rules. |
supersetWebsockets.ingress.* |
spec.networking.gateway or a custom Ingress |
The operator's Gateway API integration routes /ws to the websocket service. Built-in Ingress does not route websocket paths. |
| Flower or MCP external paths | spec.networking.gateway |
Gateway API can route /flower and /mcp to their services. |
Scaling and Availability¶
| Helm chart value | Operator equivalent | Notes |
|---|---|---|
supersetNode.autoscaling.* |
spec.webServer.autoscaling |
Uses Kubernetes autoscaling/v2 metrics, so CPU, memory, custom, and external metrics are supported. |
supersetWorker.autoscaling.* |
spec.celeryWorker.autoscaling |
Same HPA model as web server. |
Component podDisruptionBudget.* |
Component podDisruptionBudget |
Supported for web server, Celery worker, Flower, websocket server, and MCP server. |
supersetCeleryBeat.podDisruptionBudget.* |
No direct equivalent | Celery Beat is a singleton in the operator and currently has no PDB field. |
Example Translation¶
Helm values:
image:
tag: 5.0.0
supersetNode:
replicas:
replicaCount: 2
connections:
db_host: superset-db
db_port: "5432"
db_user: superset
db_pass: superset
db_name: superset
redis_host: valkey
redis_port: "6379"
supersetWorker:
replicas:
replicaCount: 2
supersetCeleryBeat:
enabled: true
configOverrides:
feature_flags: |
FEATURE_FLAGS = {"ALERT_REPORTS": True}
Operator CR:
apiVersion: superset.apache.org/v1alpha1
kind: Superset
metadata:
name: superset
spec:
image:
tag: "5.0.0"
secretKeyFrom:
name: superset-secrets
key: secret-key
metastore:
type: PostgreSQL
host: superset-db
port: 5432
database: superset
username: superset
passwordFrom:
name: superset-db
key: password
valkey:
host: valkey
port: 6379
config: |
FEATURE_FLAGS = {"ALERT_REPORTS": True}
webServer:
replicas: 2
celeryWorker:
replicas: 2
celeryBeat: {}
lifecycle:
migrate: {}
init: {}
Importing Datasources¶
The Helm chart's default init script imports
/app/configs/import_datasources.yaml when that file exists. The operator does
not run this import automatically. To keep the behavior, mount the file and add
the import to the init command:
spec:
lifecycle:
podTemplate:
volumes:
- name: datasource-import
configMap:
name: superset-datasources
container:
volumeMounts:
- name: datasource-import
mountPath: /app/configs
readOnly: true
migrate: {}
init:
command:
- /bin/sh
- -c
- |
superset init
if [ -f /app/configs/import_datasources.yaml ]; then
superset import_datasources -p /app/configs/import_datasources.yaml
fi
Websocket Routing¶
If you used the Helm chart's supersetWebsockets.ingress path, prefer Gateway
API with the operator:
spec:
websocketServer:
image:
repository: oneacrefund/superset-websocket
tag: latest
service:
gatewayPath: /ws
networking:
gateway:
gatewayRef:
name: shared-gateway
namespace: gateway-system
hostnames:
- superset.example.com
If your cluster only supports Ingress, create a separate Ingress for the
websocket service (<superset-name>-websocket-server) or route websocket
traffic outside the operator-managed Ingress.