Use the helm-databases umbrella chart to deploy Pulse database dependencies on Kubernetes by using supported operators and charts.
Supported databases
Database | Operator or chart | Version |
|---|
MongoDB | MongoDB Controllers for Kubernetes (MCK) | 1.7.0 |
PostgreSQL | CloudNativePG | 0.27.1 |
Elasticsearch | ECK Operator | 3.3.0 |
VictoriaMetrics | VictoriaMetrics Operator | 0.58.1 |
Redis | OpsTree Redis Operator | 0.23.0 |
NATS | NATS Helm Chart | 1.3.16 |
Before You Begin
Make sure you have:
Add Helm repositories
Before installing, you must register the Helm repositories that this chart references. Run all six commands, or only the ones for the databases you plan to install:
helm repo add mongodb https://mongodb.github.io/helm-charts
helm repo add cnpg https://cloudnative-pg.github.io/charts
helm repo add elastic https://helm.elastic.co
helm repo add victoriametrics https://victoriametrics.github.io/helm-charts/
helm repo add ot-container-kit https://ot-container-kit.github.io/helm-charts
helm repo add nats https://nats-io.github.io/k8s/helm/charts/
helm repo update
Then pull the dependency charts:
helm dependency update ./helm-databases
Installation Modes
This chart supports two install modes using the same chart.
Fresh Install: Two-Step Process
On a fresh cluster where operator CRDs do not exist yet, Helm cannot create custom resources (database instances) in the same transaction that installs the CRDs. The chart handles this automatically.
Step 1 -- Install operators + CRDs (database instances are auto-deferred)
helm install pulse-db ./helm-databases \
--namespace pulse-databases \
--create-namespace
Step 2 -- Create database instances (CRDs now exist in the API)
helm upgrade pulse-db ./helm-databases \
--namespace pulse-databases \
--reuse-values
The post-install notes will tell you when step 2 is needed. On subsequent upgrades, everything works in a single command.
Mode 1: All databases in a single namespace
Deploy every enabled database into one shared namespace. Follow the two-step process above on a fresh cluster.
helm install pulse-db ./helm-databases \
--namespace pulse-databases \
--create-namespace
Disable specific databases at install time:
helm install pulse-db ./helm-databases \
--namespace pulse-databases \
--create-namespace \
--set nats.enabled=false \
--set victoriametrics.enabled=false
Mode 2: Each database in its own namespace
Use the convenience value files in values/ to install one database per release. The same two-step process applies per release.
MongoDB
helm install pulse-mongo ./helm-databases \
-n pulse-mongo --create-namespace \
-f helm-databases/values/mongo-only.yaml
PostgreSQL
helm install pulse-pg ./helm-databases \
-n pulse-postgres --create-namespace \
-f helm-databases/values/postgres-only.yaml
Elasticsearch
helm install pulse-es ./helm-databases \
-n pulse-elastic --create-namespace \
-f helm-databases/values/elastic-only.yaml
VictoriaMetrics
helm install pulse-vm ./helm-databases \
-n pulse-victoriametrics --create-namespace \
-f helm-databases/values/victoriametrics-only.yaml
Redis
helm install pulse-redis ./helm-databases \
-n pulse-redis --create-namespace \
-f helm-databases/values/redis-only.yaml
NATS
helm install pulse-nats ./helm-databases \
-n pulse-nats --create-namespace \
-f helm-databases/values/nats-only.yaml
MongoDB Sharded Cluster
The chart supports deploying a sharded MongoDB cluster by using the Community Operator. No Ops Manager is required. Set mongodb.cr.type=shardedCluster to enable it.
Info
The MongoDBCommunity operator does not officially support sharding. It works by deploying separate MongoDBCommunity CRs for config servers, shard servers, and mongos routers, wired together through a shared keyfile and a mongos command override. Test thoroughly before production use.
What Gets Created
When type: shardedCluster is set, the chart creates:
Resource | Name | Description |
|---|
Secret | {name}-shared-keyfile
| Master keyfile (auto-generated or user-provided) |
Secret (×N) | {name}-configsvr-keyfile, {name}-shard-N-keyfile, {name}-mongos-keyfile
| Per-component copies of the keyfile |
Secret | admin password secret | Shared admin credentials |
MongoDBCommunity | {name}-configsvr
| Config server replica set (clusterRole: configsvr) |
MongoDBCommunity (×shardCount) | {name}-shard-0, {name}-shard-1, ...
| Shard replica sets (clusterRole: shardsvr) |
MongoDBCommunity | {name}-mongos
| mongos query routers (command override)
|
Job (Helm hook) | {name}-add-shards
| Post-install job that runs sh.addShard() for each shard |
Deploy
Step 1 -- Install the operator (same two-step process as a fresh install)
helm install pulse-db ./helm-databases \
--namespace pulse-databases \
--create-namespace \
--set mongodb.cr.type=shardedCluster
Step 2 -- Create the sharded cluster CRs
helm upgrade pulse-db ./helm-databases \
--namespace pulse-databases \
--reuse-values
Deploy with Custom Settings
Override shard count, router count, or provide your own keyfile:
helm install pulse-db ./helm-databases \
--namespace pulse-databases \
--create-namespace \
--set mongodb.cr.type=shardedCluster \
--set mongodb.cr.shardedCluster.shardCount=3 \
--set mongodb.cr.shardedCluster.membersPerShard=3 \
--set mongodb.cr.shardedCluster.mongosCount=3 \
--set mongodb.cr.shardedCluster.configServerCount=3 \
--set mongodb.cr.auth.adminPassword=mySecurePassword
To supply your own keyfile (must be at least 6 characters):
helm install pulse-db ./helm-databases \
--namespace pulse-databases \
--create-namespace \
--set mongodb.cr.type=shardedCluster \
--set mongodb.cr.shardedCluster.keyfile.value="$(openssl rand -base64 756)"
If keyfile.value is left empty, which is the default, a random keyfile is auto-generated on first install and preserved across upgrades.
Verify the Cluster
After the install and upgrade cycle, check the status of all components:
kubectl get mongodbcommunity -n pulse-databases
kubectl get job pulse-mongodb-add-shards -n pulse-databases
kubectl logs job/pulse-mongodb-add-shards -n pulse-databases
kubectl exec -it pulse-mongodb-mongos-0 -n pulse-databases -- \
mongosh --eval "sh.status()"
Architecture Overview
┌──────────────────────────────────────────────────────────┐
│ Client Applications │
└───────────────────────────┬──────────────────────────────┘
│
┌─────────────▼─────────────┐
│ pulse-mongodb-mongos │
│ (mongos query routers) │
│ N = mongosCount │
└──┬──────────────────────┬──┘
│ │
┌────────────▼──────────┐ ┌───────▼────────────────┐
│ pulse-mongodb-shard-0 │ │ pulse-mongodb-shard-N │
│ (shardsvr replset) │ │ (shardsvr replset) │
│ M = membersPerShard │ │ M = membersPerShard │
└───────────────────────┘ └─────────────────────────┘
│ │
┌───────────────────────────────────┐
│ pulse-mongodb-configsvr │
│ (configsvr replica set) │
│ N = configServerCount │
└───────────────────────────────────┘
All components share a common keyfile for internal authentication.
Using a Self-Hosted / Mirror Chart Repository
The Chart.yaml references repositories by using Helm aliases, such as @mongodb. To point at your own mirror, re-add the alias before running helm dependency update.
helm repo add mongodb https://my-internal-registry.example.com/mongodb-charts
helm repo add elastic https://my-internal-registry.example.com/elastic-charts
helm dependency update ./helm-databases
Each repository alias can be overridden independently, which gives you per-database control over the chart source.
Configuration
Global Values
Parameter | Description | Default |
|---|
global.imagePullSecrets
| Image pull secrets for operator pods | []
|
global.openshift
| Enable OpenShift-compatible security contexts | false
|
Per-Database Values
Each database section in values.yaml has the same structure:
<database>:
enabled: true
<operator-alias>: {}
cr:
create: true
name: pulse-<database>
...
MongoDB
Parameter | Description | Default |
|---|
mongodb.enabled
| Deploy MCK operator + instance | true
|
mongodb.cr.create
| Create MongoDB CR(s) | true
|
mongodb.cr.name
| Instance name (base name for sharded components) | pulse-mongodb
|
mongodb.cr.type
| replicaSet or shardedCluster
| replicaSet
|
mongodb.cr.version
| MongoDB version | 7.0.14
|
mongodb.cr.replicaSet.members
| Replica set members (replicaSet mode) | 3
|
mongodb.cr.shardedCluster.shardCount
| Number of shard replica sets | 2
|
mongodb.cr.shardedCluster.membersPerShard
| Members per shard replica set | 3
|
mongodb.cr.shardedCluster.mongosCount
| mongos router instances
| 2
|
mongodb.cr.shardedCluster.configServerCount
| Config server replica set members | 3
|
mongodb.cr.shardedCluster.keyfile.value
| Custom keyfile string (auto-generated if empty) | ""
|
mongodb.cr.storage.size
| Data volume size | 10Gi
|
mongodb.cr.auth.adminUser
| Default admin username | pulse-admin
|
mongodb.cr.auth.adminPassword
| Default admin password (CHANGE THIS) | changeme
|
PostgreSQL (CloudNativePG)
Parameter | Description | Default |
|---|
postgresql.enabled
| Deploy CNPG operator + cluster | true
|
postgresql.cr.create
| Create Cluster CR | true
|
postgresql.cr.name
| Cluster name | pulse-postgresql
|
postgresql.cr.instances
| Number of instances | 3
|
postgresql.cr.version
| PostgreSQL major version | 16
|
postgresql.cr.storage.size
| Data volume size | 10Gi
|
postgresql.cr.bootstrap.database
| Initial database name | pulse
|
postgresql.cr.bootstrap.owner
| Database owner | pulse
|
Elasticsearch (ECK)
Parameter | Description | Default |
|---|
elasticsearch.enabled
| Deploy ECK operator + cluster | true
|
elasticsearch.cr.create
| Create Elasticsearch CR | true
|
elasticsearch.cr.name
| Cluster name | pulse-elasticsearch
|
elasticsearch.cr.version
| Elasticsearch version | 8.17.0
|
elasticsearch.cr.nodeSets
| Node set definitions (count, storage, resources) | 1 set, 3 nodes, 20Gi |
VictoriaMetrics
Parameter | Description | Default |
|---|
victoriametrics.enabled
| Deploy VM operator + instance | true
|
victoriametrics.cr.create
| Create VMSingle CR | true
|
victoriametrics.cr.name
| Instance name | pulse-victoriametrics
|
victoriametrics.cr.retentionPeriod
| Data retention period | 30d
|
victoriametrics.cr.storage.size
| Data volume size | 10Gi
|
Redis (OpsTree)
Parameter | Description | Default |
|---|
redis.enabled
| Deploy Redis operator + instance | true
|
redis.cr.create
| Create Redis CR | true
|
redis.cr.name
| Instance name | pulse-redis
|
redis.cr.mode
| Deployment mode: standalone, cluster, replication, sentinel | standalone
|
redis.cr.clusterSize
| Nodes (for cluster, replication, sentinel modes) | 3
|
redis.cr.version
| Redis image tag | v7.2.6
|
redis.cr.storage.size
| Data volume size | 5Gi
|
redis.cr.exporter.enabled
| Enable Prometheus exporter sidecar | true
|
NATS
|
|
|
|---|
nats.enabled
| Deploy NATS | true
|
nats.global.image.pullSecretNames
| List of image pull secret names for private registries | []
|
nats.container.image.repository
| Custom NATS server image repository | nats
|
nats.container.image.tag
| Custom NATS server image tag | 2.11.10-alpine
|
nats.container.image.registry
| Custom image registry (overrides Docker Hub) | ""
|
nats.container.image.fullImageName
| Full image name (overrides registry + repo + tag) | ""
|
nats.natsBox.enabled
| Deploy nats-box diagnostic toolbox | false
|
nats.config.cluster.enabled
| Enable NATS clustering | true
|
nats.config.cluster.replicas
| Cluster replicas | 3
|
nats.config.jetstream.enabled
| Enable JetStream persistence | true
|
nats.config.jetstream.fileStore.pvc.size
| JetStream PVC size | 5Gi
|
For full NATS chart values, see the official NATS chart docs.
Operator-Only Install
To deploy only the operators without creating database instances, which is useful for shared operator installations, set cr.create: false:
helm install pulse-db ./helm-databases \
--namespace pulse-databases \
--create-namespace \
--set mongodb.cr.create=false \
--set postgresql.cr.create=false \
--set elasticsearch.cr.create=false \
--set victoriametrics.cr.create=false \
--set redis.cr.create=false
You can then create database instances separately by applying CRs with kubectl.
Skipping CRD Installation
If CRDs already exist in the cluster, from a prior install or another release, you may get ownership conflicts. Use these options to skip CRD installation:
helm install pulse-db ./helm-databases \
--namespace pulse-databases \
--create-namespace \
--skip-crds \
--set postgresql.crds.create=false \
--set elasticsearch.installCRDs=false \
--set victoriametrics.crds.enabled=false
Database | How to skip CRDs |
|---|
MongoDB | --skip-crds flag (MCK uses Helm-native crds/ directory)
|
PostgreSQL | postgresql.crds.create=false
|
Elasticsearch | elasticsearch.installCRDs=false
|
VictoriaMetrics | victoriametrics.crds.enabled=false
|
Redis | --skip-crds flag (CRDs are in Helm-native crds/ directory)
|
NATS | No CRDs |
Alternatively, adopt existing CRDs into your release:
kubectl label crd mongodbcommunity.mongodbcommunity.mongodb.com \
app.kubernetes.io/managed-by=Helm
kubectl annotate crd mongodbcommunity.mongodbcommunity.mongodb.com \
meta.helm.sh/release-name=<RELEASE_NAME> \
meta.helm.sh/release-namespace=<NAMESPACE>
OpenShift Support
Set global.openshift=true to enable OpenShift-compatible settings:
helm install pulse-db ./helm-databases \
--namespace pulse-databases \
--create-namespace \
--set global.openshift=true \
--set postgresql.containerSecurityContext.runAsUser=null \
--set postgresql.containerSecurityContext.runAsGroup=null
This will:
Remove fixed runAsUser / runAsGroup settings from all CR pod templates (MongoDB, Elasticsearch, Redis, VictoriaMetrics, PostgreSQL)
Let OpenShift Security Context Constraints (SCCs) assign UIDs
Avoid privileged: true in all workloads
Built-in OpenShift support per operator
Operator | OpenShift handling |
|---|
ECK | Auto-detects OpenShift via config.setDefaultSecurityContext: "auto-detect" |
VictoriaMetrics | Auto-adapts via global.compatibility.openshift.adaptSecurityContext: "auto" |
OpsTree Redis | Built-in OpenShift support (openshift chart keyword) |
CloudNativePG | Set postgresql.containerSecurityContext.runAsUser=null to clear hardcoded UID |
MongoDB | CR template clears securityContext when global.openshift=true |
NATS | Standard securityContext, runs as non-root by default |
Upgrading
helm dependency update ./helm-databases
helm upgrade pulse-db ./helm-databases \
--namespace pulse-databases
Uninstalling
helm uninstall pulse-db --namespace pulse-databases
CRDs are not automatically removed by Helm.
To remove CRDs manually, which deletes all custom resources, run:
kubectl get crd | grep -E 'mongodb|cnpg|elastic|victoriametrics|redis.opstreelabs' | awk '{print $1}' | xargs kubectl delete crd
Commands to Install Databases on OpenShift
MongoDB
helm install pulse-mongo ./helm-databases \
-n pulse-mongo --create-namespace \
-f helm-databases/values/mongo-only.yaml \
--set mongodb.cr.version="8.0.18" \
--set mongodb.cr.mongodImage="191579300362.dkr.ecr.us-east-1.amazonaws.com/acceldata/ad-database:testBuild-k8sqe" \
--set global.openshift=true \
--set mongodb.managedSecurityContext=true \
--set global.imagePullSecrets[0]=adreg \
--set mongodb.registry.imagePullSecrets=adreg \
--set global.imagePullPolicy=IfNotPresent \
--set global.storageClassName='gp2-csi' \
--set global.imageCredentials.password='<REDACTED_EXISTING_SECRET>' \
--set mongodb.cr.storage.size=50Gi \
--set mongodb.cr.resources.limits.cpu=1 \
--set mongodb.cr.resources.limits.memory=4Gi \
--set mongodb.cr.resources.requests.cpu=500m \
--set mongodb.cr.resources.requests.memory=2Gi \
--timeout 20m
VictoriaMetrics
helm install pulse-vm ./helm-databases \
-n pulse-vm --create-namespace \
--set victoriametrics.cr.auth.username=acceldata \
--set 'victoriametrics.cr.auth.password=D@t@Ops' \
--set mongodb.managedSecurityContext=true \
--set global.openshift=true \
--set nats.global.image.pullSecretNames[0]=adreg \
--set global.storageClassName='gp2-csi' \
--set global.imageCredentials.password='<REDACTED_EXISTING_SECRET>' \
--set victoriametrics.cr.vmstorage.storage.size=50Gi \
--set victoriametrics.cr.vmstorage.resources.limits.cpu=1 \
--set victoriametrics.cr.vmstorage.resources.limits.memory=4Gi \
--set victoriametrics.cr.vmstorage.resources.requests.cpu=500m \
--set victoriametrics.cr.vmstorage.resources.requests.memory=1Gi \
--set victoriametrics.cr.vmselect.storage.size=10Gi \
--set victoriametrics.cr.vmselect.resources.limits.cpu=1 \
--set victoriametrics.cr.vmselect.resources.limits.memory=2Gi \
--set victoriametrics.cr.vmselect.resources.requests.cpu=500m \
--set victoriametrics.cr.vmselect.resources.requests.memory=512Mi \
--set victoriametrics.cr.vminsert.resources.limits.cpu=1 \
--set victoriametrics.cr.vminsert.resources.limits.memory=2Gi \
--set victoriametrics.cr.vminsert.resources.requests.cpu=500m \
--set victoriametrics.cr.vminsert.resources.requests.memory=512Mi \
-f helm-databases/values/victoriametrics-only.yaml
helm upgrade pulse-vm ./helm-databases \
--namespace pulse-vm \
--reuse-values
Elastic
helm install pulse-es ./helm-databases \
-n pulse-es --create-namespace \
--set global.openshift=true \
--set mongodb.managedSecurityContext=true \
--set elasticsearch.cr.auth.password="admin_password" \
--set elasticsearch.cr.auth.additionalUsers[0].username=pulse \
--set elasticsearch.cr.auth.additionalUsers[0].password="data@ops" \
--set elasticsearch.cr.auth.additionalUsers[0].roles[0]=superuser \
--set elasticsearch.cr.tls.p12.password="" \
--set elasticsearch.cr.httpPort=19013 \
--set elasticsearch.cr.tls.disabled=true \
--set nats.global.image.pullSecretNames[1]=adreg \
--set global.storageClassName='gp2-csi' \
--set global.imageCredentials.password='<REDACTED_EXISTING_SECRET>' \
--set elasticsearch.cr.nodeSets[0].name=default \
--set elasticsearch.cr.nodeSets[0].count=3 \
--set elasticsearch.cr.nodeSets[0].config.node\\.store\\.allow_mmap=true \
--set elasticsearch.cr.nodeSets[0].storage.size=50Gi \
--set elasticsearch.cr.nodeSets[0].resources.limits.cpu=1 \
--set elasticsearch.cr.nodeSets[0].resources.limits.memory=8Gi \
--set elasticsearch.cr.nodeSets[0].resources.requests.cpu=500m \
--set elasticsearch.cr.nodeSets[0].resources.requests.memory=4Gi \
-f helm-databases/values/elastic-only.yaml
helm upgrade pulse-es ./helm-databases \
--namespace pulse-es \
--reuse-values
kubectl port-forward svc/pulse-elasticsearch-es-http -n pulse-es 19013:19013
curl -s -u "pulse:data@ops" \
-X PUT "http://localhost:19013/_index_template/pulse-defaults" \
-H 'Content-Type: application/json' \
-d '{
"index_patterns": ["*"],
"priority": 1,
"template": {
"settings": {
"number_of_replicas": 1
}
}
}'
Postgres
helm install pulse-pg ./helm-databases \
-n pulse-pg --create-namespace \
-f helm-databases/values/postgres-only.yaml \
--set postgresql.useOperator=false \
--set nats.global.image.pullSecretNames[0]=adreg \
--set global.storageClassName='gp2-csi' \
--set global.imageCredentials.password='<REDACTED_EXISTING_SECRET>' \
--set postgresql.standalone.storage.size=50Gi \
--set postgresql.standalone.resources.limits.cpu=1 \
--set postgresql.standalone.resources.limits.memory=4Gi \
--set postgresql.standalone.resources.requests.cpu=500m \
--set postgresql.standalone.resources.requests.memory=2Gi \
-f helm-databases/values/openshift.yaml
NATS
helm install pulse-nats ./helm-databases \
-n pulse-nats --create-namespace \
--set global.openshift=true \
-f helm-databases/values/nats-only.yaml \
--set nats.global.image.pullSecretNames[0]=adreg \
--set nats.loadBalancer.enabled=true \
--set global.storageClassName='gp2-csi' \
--set nats.config.jetstream.fileStore.pvc.storageClassName='gp2-csi' \
--set global.imageCredentials.password='<REDACTED_EXISTING_SECRET>' \
--set nats.config.jetstream.fileStore.pvc.size=20Gi \
--set nats.container.merge.resources.limits.cpu=1 \
--set nats.container.merge.resources.limits.memory=4Gi \
--set nats.container.merge.resources.requests.cpu=500m \
--set nats.container.merge.resources.requests.memory=2Gi