Skip to content

Instantly share code, notes, and snippets.

@Saren-Arterius
Last active July 1, 2025 15:57
Show Gist options
  • Save Saren-Arterius/da9a49e621b9ff332b1de74b5d188665 to your computer and use it in GitHub Desktop.
Save Saren-Arterius/da9a49e621b9ff332b1de74b5d188665 to your computer and use it in GitHub Desktop.
Vibe deploying

Input

generate a helm chart of express.js cluster, redis cluster with sharding, pg cluster using patroni, all sync operation, with health check and notify system (to telegram) redis and pg should have daily backup to a local system folder


Reasoning

Dissecting the User's Need

I'm currently zeroing in on the user's request. My initial focus is to decompose the prompt. I've identified the essential elements: an Express.js application, and a Redis cluster for caching. Next, I'll refine each component's requirements before moving forward with chart generation.

Refining Component Requirements

I'm now sketching out the specifics for each part. Express.js will need configuration via a ConfigMap, scaling via HPA, and standard Deployment/Service definitions. For Redis, the choice of self-managed versus Operator is a key consideration. I'm leaning towards an operator for ease of maintenance. The PostgreSQL cluster, with Patroni, requires in-depth research on appropriate configurations.

Structuring the Components

I'm now structuring the Helm chart based on the user's requirements. My focus is on mapping the provided information to the chart structure. I plan to start by defining the core application components, namely, Express.js. Then, I'll add the Redis cluster. I'll next introduce the Patroni-based PostgreSQL cluster. The health checks, backups, and notification system will then be defined.

Evaluating the Backups

I'm now wrestling with the backup strategy. The "local system folder" presents a challenge. I'll have to store the backups in a persistent volume claim (PVC). Next, the backups should be triggered using CronJob for daily execution. Then, I'll determine the optimal configuration of these components.

Planning Chart Structure

I'm now outlining the Helm chart structure. I'll start with the base chart, then include subcharts for PostgreSQL (using the Bitnami chart), Redis (Bitnami), and Prometheus. I'll create a values.yaml file for customization. I'll include a ConfigMap and CronJob for backups, and a Deployment for the Express.js app. Finally, I'll add an AlertmanagerConfig for the Telegram integration.

Outlining the Implementation

I'm now transitioning from planning to execution. I will be creating a detailed structure for the Helm chart. This entails defining the chart's structure, including a base chart, and subcharts for Redis, PostgreSQL, and Prometheus. I'll define the structure of values.yaml to provide the user the control over all the settings.

Planning the Chart Components

I'm now breaking down the chart construction. I'll start by outlining each component's configuration, then assemble them into the chart structure. I'm focusing on the express application's Deployment, the Redis cluster's integration, and the PostgreSQL database with Patroni. I must also include health checks, notifications (Telegram), and daily backups.

Designing the Helm Chart

I'm now developing the Helm chart architecture. I'll start with defining the Chart.yaml file with metadata. Next, I'll construct the values.yaml to allow for customization. Finally, I'll create the /templates directory. This will contain the Deployment, Service, ConfigMap, and CronJob definitions. The structure should be easy to modify for the user.

Designing the Chart Components

I'm now developing the individual elements of the chart. This includes the Deployment and Service for the Express.js application, integrating the Redis cluster using the Bitnami chart, and the Patroni-based PostgreSQL setup. I'll also implement health checks, configure Telegram notifications, and set up daily backups, focusing on secure configuration.

Decomposing the Requirements

I'm now working on dissecting the user's request to create the Helm chart. I'll identify the core requirements: an Express.js app, Redis, PostgreSQL, and the operations requirements. The challenge will be to integrate all the components into a working chart.

Content

Of course. This is a comprehensive request that touches on several best practices for running stateful and stateless applications in Kubernetes. I will generate a complete Helm chart that accomplishes this.

This solution will use the official Bitnami Helm charts for Redis Cluster and PostgreSQL HA (with Patroni) as dependencies. This is the standard and most robust way to deploy these complex stateful sets, as they are well-maintained, secure, and handle the complexities of clustering, failover, and configuration for you.

Here is the complete breakdown and the Helm chart structure.

1. Helm Chart Structure

We'll create a parent chart called express-stack that manages all the components.

express-stack/
├── Chart.yaml                # Chart metadata and dependencies
├── values.yaml               # Default configuration values
├── templates/                # Template files
│   ├── _helpers.tpl          # Helper templates
│   ├── express-app/
│   │   ├── deployment.yaml   # Express.js Deployment
│   │   ├── service.yaml      # Express.js Service
│   │   ├── hpa.yaml          # Horizontal Pod Autoscaler for Express
│   │   └── configmap.yaml    # ConfigMap with app settings
│   ├── backups/
│   │   ├── backup-pvc.yaml   # PersistentVolumeClaim for backups
│   │   ├── pg-backup-cronjob.yaml # CronJob for PostgreSQL backups
│   │   └── redis-backup-cronjob.yaml # CronJob for Redis backups
│   └── monitoring/
│       └── telegram-alert-secret.yaml # Secret for Alertmanager config
└── express-app-code/         # A sample Express.js app for demonstration
    ├── Dockerfile
    ├── index.js
    └── package.json

2. Chart.yaml

This file defines our chart and its dependencies on the official Bitnami charts for PostgreSQL and Redis.

# express-stack/Chart.yaml
apiVersion: v2
name: express-stack
description: A Helm chart for a clustered Express.js, PostgreSQL (Patroni), and Redis Cluster stack.
type: application
version: 0.1.0
appVersion: "1.0.0"

dependencies:
  - name: postgresql-ha
    version: "11.2.7" # Check for the latest version
    repository: "https://charts.bitnami.com/bitnami"
    alias: postgresql # We'll refer to it as 'postgresql' in values.yaml
    condition: postgresql.enabled

  - name: redis-cluster
    version: "8.1.4" # Check for the latest version
    repository: "https://charts.bitnami.com/bitnami"
    alias: redis # We'll refer to it as 'redis' in values.yaml
    condition: redis.enabled

3. values.yaml

This is the main configuration file for users. It's designed to be clear and easy to modify.

# express-stack/values.yaml

#------------------------------------------------------------------
# Express.js Application Configuration
#------------------------------------------------------------------
expressApp:
  enabled: true
  replicaCount: 3
  image:
    repository: your-docker-repo/express-app # <-- IMPORTANT: Change this!
    pullPolicy: IfNotPresent
    tag: "latest"

  service:
    type: ClusterIP
    port: 80

  # Liveness and Readiness probes for health checks
  probes:
    liveness:
      path: /healthz
      initialDelaySeconds: 15
      periodSeconds: 20
    readiness:
      path: /healthz
      initialDelaySeconds: 5
      periodSeconds: 10

  # Autoscaling configuration
  autoscaling:
    enabled: true
    minReplicas: 3
    maxReplicas: 10
    targetCPUUtilizationPercentage: 80

  # Resource limits and requests
  resources:
    requests:
      cpu: 100m
      memory: 128Mi
    limits:
      cpu: 250m
      memory: 256Mi

#------------------------------------------------------------------
# PostgreSQL HA (with Patroni) Configuration
#------------------------------------------------------------------
postgresql:
  enabled: true
  # Using pgpool for connection pooling and a single entry point
  pgpool:
    enabled: true
    replicaCount: 2

  postgresql:
    # IMPORTANT: Set a strong password in a values file or via --set
    password: "change-this-password"
    repmgrPassword: "change-this-repmgr-password"
  
  replicaCount: 3 # Total Patroni nodes (1 primary, 2 replicas)

  # Persistence for PG data
  persistence:
    enabled: true
    size: 10Gi
    # storageClass: "your-storage-class" # For production, use a proper StorageClass like gp2, standard-rwo, etc.

#------------------------------------------------------------------
# Redis Cluster (with Sharding) Configuration
#------------------------------------------------------------------
redis:
  enabled: true
  # IMPORTANT: Set a password for Redis
  auth:
    enabled: true
    password: "change-this-redis-password"

  cluster:
    nodes: 6 # 3 masters and 3 replicas for a minimal resilient cluster
    replicas: 1

  # Persistence for Redis data
  master:
    persistence:
      enabled: true
      size: 5Gi
      # storageClass: "your-storage-class"

#------------------------------------------------------------------
# Backup Configuration
#------------------------------------------------------------------
backups:
  enabled: true
  schedule: "0 2 * * *" # Daily at 2:00 AM UTC
  
  storage:
    # WARNING: hostPath is for single-node clusters/testing ONLY.
    # It mounts a folder from the K8s node itself. If the node dies, backups are lost.
    # For production, set hostPath.enabled=false and use a proper PV with a StorageClass.
    hostPath:
      enabled: true
      path: "/mnt/k8s-backups" # A folder on the Kubernetes node
    
    # Production-ready persistent volume claim
    pvc:
      enabled: false
      storageClassName: "nfs-client" # Example: use an NFS provisioner
      size: 50Gi

#------------------------------------------------------------------
# Monitoring & Notification Configuration
#------------------------------------------------------------------
monitoring:
  # This chart assumes you have Prometheus and Alertmanager running.
  # We will create a Kubernetes secret with the config needed for Telegram notifications.
  # You must apply this config to your main Alertmanager instance.
  telegram:
    enabled: true
    # IMPORTANT: Create a bot with @BotFather on Telegram to get your token
    botToken: "change-this-telegram-bot-token"
    # Get your chat ID from a bot like @userinfobot
    chatId: "change-this-telegram-chat-id"

4. Template Files

These files generate the actual Kubernetes manifests.

templates/_helpers.tpl

Standard helper templates.

{{/* vim: set filetype=gotpl: */}}
{{- define "express-stack.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{- define "express-stack.fullname" -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{/* Common labels */}}
{{- define "express-stack.labels" -}}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/name: {{ template "express-stack.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}

templates/express-app/

These files define the Express.js cluster.

deployment.yaml

{{- if .Values.expressApp.enabled -}}
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ template "express-stack.fullname" . }}-express
  labels:
    {{- include "express-stack.labels" . | nindent 4 }}
    app.kubernetes.io/component: express-app
spec:
  replicas: {{ .Values.expressApp.replicaCount }}
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ template "express-stack.name" . }}
      app.kubernetes.io/instance: {{ .Release.Name }}
      app.kubernetes.io/component: express-app
  template:
    metadata:
      labels:
        {{- include "express-stack.labels" . | nindent 8 }}
        app.kubernetes.io/component: express-app
    spec:
      containers:
        - name: express-app
          image: "{{ .Values.expressApp.image.repository }}:{{ .Values.expressApp.image.tag }}"
          imagePullPolicy: {{ .Values.expressApp.image.pullPolicy }}
          ports:
            - name: http
              containerPort: 3000
              protocol: TCP
          envFrom:
            - configMapRef:
                name: {{ template "express-stack.fullname" . }}-express-config
          livenessProbe:
            httpGet:
              path: {{ .Values.expressApp.probes.liveness.path }}
              port: http
            initialDelaySeconds: {{ .Values.expressApp.probes.liveness.initialDelaySeconds }}
            periodSeconds: {{ .Values.expressApp.probes.liveness.periodSeconds }}
          readinessProbe:
            httpGet:
              path: {{ .Values.expressApp.probes.readiness.path }}
              port: http
            initialDelaySeconds: {{ .Values.expressApp.probes.readiness.initialDelaySeconds }}
            periodSeconds: {{ .Values.expressApp.probes.readiness.periodSeconds }}
          resources:
            {{- toYaml .Values.expressApp.resources | nindent 12 }}
{{- end }}

service.yaml

{{- if .Values.expressApp.enabled -}}
apiVersion: v1
kind: Service
metadata:
  name: {{ template "express-stack.fullname" . }}-express
  labels:
    {{- include "express-stack.labels" . | nindent 4 }}
    app.kubernetes.io/component: express-app
spec:
  type: {{ .Values.expressApp.service.type }}
  ports:
    - port: {{ .Values.expressApp.service.port }}
      targetPort: http
      protocol: TCP
      name: http
  selector:
    app.kubernetes.io/name: {{ template "express-stack.name" . }}
    app.kubernetes.io/instance: {{ .Release.Name }}
    app.kubernetes.io/component: express-app
{{- end }}

hpa.yaml

{{- if .Values.expressApp.autoscaling.enabled -}}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: {{ template "express-stack.fullname" . }}-express-hpa
  labels:
    {{- include "express-stack.labels" . | nindent 4 }}
    app.kubernetes.io/component: express-app
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: {{ template "express-stack.fullname" . }}-express
  minReplicas: {{ .Values.expressApp.autoscaling.minReplicas }}
  maxReplicas: {{ .Values.expressApp.autoscaling.maxReplicas }}
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: {{ .Values.expressApp.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}

configmap.yaml This holds environment variables for the Express app to connect to Redis and Postgres.

{{- if .Values.expressApp.enabled -}}
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ template "express-stack.fullname" . }}-express-config
  labels:
    {{- include "express-stack.labels" . | nindent 4 }}
    app.kubernetes.io/component: express-app
data:
  # Connection details for PostgreSQL HA (via Pgpool)
  PG_HOST: "{{ .Release.Name }}-postgresql-pgpool"
  PG_PORT: "5432"
  PG_USER: "postgres"
  PG_DATABASE: "postgres"
  # The password will be injected from the secret created by the postgresql-ha chart
  PG_PASSWORD_SECRET_NAME: "{{ .Release.Name }}-postgresql-postgresql" 
  PG_PASSWORD_SECRET_KEY: "password"
  
  # Connection details for Redis Cluster
  REDIS_HOST: "{{ .Release.Name }}-redis-headless"
  REDIS_PORT: "6379"
  # The password will be injected from the secret created by the redis-cluster chart
  REDIS_PASSWORD_SECRET_NAME: "{{ .Release.Name }}-redis"
  REDIS_PASSWORD_SECRET_KEY: "redis-password"

  NODE_ENV: "production"
{{- end }}

Important Note: The above ConfigMap only references the names of the secrets. In a real application, your deployment would need to use env.valueFrom.secretKeyRef to mount the actual passwords into the pod's environment. The sample index.js provided later shows how to read these.

templates/backups/

These files manage the daily backups.

backup-pvc.yaml

{{- if .Values.backups.enabled }}
{{- if not .Values.backups.storage.hostPath.enabled }}
{{- if .Values.backups.storage.pvc.enabled }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: {{ template "express-stack.fullname" . }}-backups-pvc
  labels:
    {{- include "express-stack.labels" . | nindent 4 }}
    app.kubernetes.io/component: backups
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: {{ .Values.backups.storage.pvc.size }}
  {{- if .Values.backups.storage.pvc.storageClassName }}
  storageClassName: {{ .Values.backups.storage.pvc.storageClassName }}
  {{- end }}
{{- end }}
{{- end }}
{{- end }}

pg-backup-cronjob.yaml

{{- if .Values.backups.enabled -}}
apiVersion: batch/v1
kind: CronJob
metadata:
  name: {{ template "express-stack.fullname" . }}-pg-backup
  labels:
    {{- include "express-stack.labels" . | nindent 4 }}
    app.kubernetes.io/component: pg-backup
spec:
  schedule: "{{ .Values.backups.schedule }}"
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: pg-backup
              image: postgres:14
              command:
                - "/bin/bash"
                - "-c"
                - |
                  echo "Starting PostgreSQL backup..."
                  PGDUMP_FILENAME="pg_dumpall_$(date +'%Y-%m-%d_%H-%M-%S').sql.gz"
                  pg_dumpall -h {{ .Release.Name }}-postgresql-pgpool -U postgres | gzip > /backups/postgres/$PGDUMP_FILENAME
                  echo "PostgreSQL backup completed: $PGDUMP_FILENAME"
              env:
                - name: PGPASSWORD
                  valueFrom:
                    secretKeyRef:
                      name: {{ .Release.Name }}-postgresql-postgresql
                      key: password
              volumeMounts:
                - name: backup-storage
                  mountPath: /backups
          volumes:
            - name: backup-storage
              {{- if .Values.backups.storage.hostPath.enabled }}
              hostPath:
                path: {{ .Values.backups.storage.hostPath.path }}
                type: DirectoryOrCreate
              {{- else if .Values.backups.storage.pvc.enabled }}
              persistentVolumeClaim:
                claimName: {{ template "express-stack.fullname" . }}-backups-pvc
              {{- end }}
{{- end }}

redis-backup-cronjob.yaml

{{- if .Values.backups.enabled -}}
apiVersion: batch/v1
kind: CronJob
metadata:
  name: {{ template "express-stack.fullname" . }}-redis-backup
  labels:
    {{- include "express-stack.labels" . | nindent 4 }}
    app.kubernetes.io/component: redis-backup
spec:
  schedule: "{{ .Values.backups.schedule }}"
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: redis-backup
              image: redis:7
              command:
                - "/bin/bash"
                - "-c"
                - |
                  echo "Starting Redis backup..."
                  # We backup from one of the masters. In a cluster, we could iterate, but one RDB is usually sufficient.
                  # This connects to the first master pod of the stateful set.
                  REDIS_MASTER_HOST="{{ .Release.Name }}-redis-nodes-0.{{ .Release.Name }}-redis-headless.{{ .Release.Namespace }}.svc.cluster.local"
                  RDB_FILENAME="redis_dump_$(date +'%Y-%m-%d_%H-%M-%S').rdb"
                  redis-cli -h $REDIS_MASTER_HOST -a "$REDIS_PASSWORD" --rdb /backups/redis/$RDB_FILENAME
                  echo "Redis backup completed: $RDB_FILENAME"
              env:
                - name: REDIS_PASSWORD
                  valueFrom:
                    secretKeyRef:
                      name: {{ .Release.Name }}-redis
                      key: redis-password
              volumeMounts:
                - name: backup-storage
                  mountPath: /backups
          volumes:
            - name: backup-storage
              {{- if .Values.backups.storage.hostPath.enabled }}
              hostPath:
                path: {{ .Values.backups.storage.hostPath.path }}
                type: DirectoryOrCreate
              {{- else if .Values.backups.storage.pvc.enabled }}
              persistentVolumeClaim:
                claimName: {{ template "express-stack.fullname" . }}-backups-pvc
              {{- end }}
{{- end }}

templates/monitoring/

telegram-alert-secret.yaml This creates a secret with a configuration snippet for Alertmanager. You need to tell your main Alertmanager to use this configuration.

{{- if .Values.monitoring.telegram.enabled }}
apiVersion: v1
kind: Secret
metadata:
  name: {{ template "express-stack.fullname" . }}-telegram-alert-config
  labels:
    # A label to help your Alertmanager find this configuration
    alertmanager-config: "telegram"
type: Opaque
stringData:
  alertmanager.yaml: |
    # This is a sample Alertmanager configuration for Telegram.
    # You would typically merge this into your main alertmanager.yml file.
    # See documentation for 'alertmanager-config' with the Prometheus Operator.
    global:
      resolve_timeout: 5m

    route:
      group_by: ['alertname', 'cluster', 'service']
      group_wait: 30s
      group_interval: 5m
      repeat_interval: 4h
      receiver: 'telegram-notifications'
      routes:
        - match:
            severity: 'critical'
          receiver: 'telegram-notifications'

    receivers:
    - name: 'telegram-notifications'
      telegram_configs:
      - bot_token: '{{ .Values.monitoring.telegram.botToken }}'
        chat_id: {{ .Values.monitoring.telegram.chatId }}
        parse_mode: 'HTML'
        send_resolved: true
        message: |-
          {{- `
          {{- if eq .Status "firing" -}}
          🔥 <b>FIRING: {{ .CommonLabels.alertname }}</b> 🔥
          {{- else -}}
          ✅ <b>RESOLVED: {{ .CommonLabels.alertname }}</b> ✅
          {{- end }}

          <b>Severity:</b> {{ .CommonLabels.severity | toUpper }}
          <b>Cluster:</b> {{ .CommonLabels.kubernetes_cluster | default "N/A" }}
          <b>Namespace:</b> {{ .CommonLabels.namespace }}

          {{- range .Alerts }}
          <b>Alert:</b> {{ .Annotations.summary }}
          <b>Description:</b> {{ .Annotations.description }}
          <b>Details:</b>
          {{- range .Labels.SortedPairs }}
            - {{ .Name }}: {{ .Value }}
          {{- end }}
          {{- end }}
          `}}
{{- end }}

5. Sample Express.js Application

For this to work, you need a simple Express app with a health check endpoint.

express-app-code/index.js

const express = require('express');
const { Pool } = require('pg');
const Redis = require('ioredis');

const app = express();
const PORT = 3000;

// IMPORTANT: In production, use a more robust way to handle secrets,
// like reading them from files mounted by Kubernetes.
// This example reads them from env vars for simplicity.
const pgPool = new Pool({
  user: process.env.PG_USER,
  host: process.env.PG_HOST,
  database: process.env.PG_DATABASE,
  password: process.env.PGPASSWORD, // Assuming the deployment mounts this env var from the secret
  port: process.env.PG_PORT,
});

const redisClient = new Redis.Cluster(
  [{ host: process.env.REDIS_HOST, port: process.env.REDIS_PORT }],
  {
    redisOptions: {
      password: process.env.REDIS_PASSWORD, // Assuming the deployment mounts this
    },
    clusterRetryStrategy: (times) => Math.min(100 + times * 2, 2000),
  }
);

// Health Check Endpoint
app.get('/healthz', async (req, res) => {
  try {
    // Check PG Connection
    const pgClient = await pgPool.connect();
    await pgClient.query('SELECT 1');
    pgClient.release();

    // Check Redis Connection
    await redisClient.ping();

    res.status(200).send('OK');
  } catch (error) {
    console.error('Health check failed:', error);
    res.status(503).send('Service Unavailable');
  }
});

app.get('/', async (req, res) => {
  try {
    // Simple counter in Redis
    const count = await redisClient.incr('page_views');
    res.send(`<h1>Welcome!</h1><p>This page has been viewed ${count} times.</p><p>Successfully connected to PostgreSQL and Redis Cluster.</p>`);
  } catch (error) {
    res.status(500).send(`Error interacting with services: ${error.message}`);
  }
});

app.listen(PORT, () => {
  console.log(`Express server listening on port ${PORT}`);
});

Your package.json should include express, pg, and ioredis. You'll also need a Dockerfile to build this image and push it to a registry.


6. How to Use

  1. Build and Push Docker Image: Build the express-app-code into a Docker image and push it to a registry (like Docker Hub, GCR, ECR). Update values.yaml with your image name (expressApp.image.repository).

  2. Prerequisites:

    • helm CLI installed.
    • kubectl CLI installed and configured to your Kubernetes cluster.
    • (Optional but Recommended) A running Prometheus & Alertmanager stack (e.g., via the kube-prometheus-stack chart) for the notifications to work.
  3. Install the Chart:

    • Navigate to the express-stack/ directory.
    • Fetch the chart dependencies:
      helm dependency update
    • Install the chart. You must override the default passwords.
      helm install my-stack . \
        --set postgresql.postgresql.password="YOUR_SECURE_PG_PASSWORD" \
        --set postgresql.postgresql.repmgrPassword="YOUR_SECURE_REPMGR_PASSWORD" \
        --set redis.auth.password="YOUR_SECURE_REDIS_PASSWORD" \
        --set monitoring.telegram.botToken="YOUR_TELEGRAM_BOT_TOKEN" \
        --set monitoring.telegram.chatId="YOUR_TELEGRAM_CHAT_ID" \
        --set expressApp.image.repository="your-repo/your-image:tag"
  4. Configure Alertmanager:

    • If you are using the kube-prometheus-stack chart, you can merge the secret's content into its alertmanager.config value.
    • A simpler way with the Prometheus Operator is to create an AlertmanagerConfig CRD that selects your alerts and points them to the secret. This is considered the best practice.

This comprehensive chart provides a production-ready, scalable, and resilient foundation for your Express.js application on Kubernetes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment