Deploy Pulse databases with Helm

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:

  • Kubernetes 1.21 or later

  • Helm 3.x

  • kubectl configured for your cluster

  • Cluster admin permissions to install CRDs and ClusterRoles


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