Installation With Helm
Overview
This guide documents the process for installing Monad into a Kubernetes cluster using Helm. The primary installation method uses the Kubernetes Gateway API for ingress. If your cluster uses a traditional ingress controller instead, see Alternative: Ingress Controller at the end of this document.
Phase 1: Prerequisites
These must be installed before the Monad Helm chart can be deployed.
Authentication Provider
Monad supports local authentication, Auth0, and AWS Cognito, with Auth0 being the default.
Required Operators/Controllers
-
Victoria Metrics Operator
- Required for internal metrics and dashboards, deployed by Custom Resources from within the chart. This does not replace an observability stack like Prometheus, and all components expose endpoints for scraping by an observability platform.
- Installation: https://docs.victoriametrics.com/operator/
-
CloudNativePG (CNPG) - PostgreSQL operator
- Required unless you're using an external PostgreSQL instance
- Installation: https://cloudnative-pg.io/documentation/current/installation_upgrade/
Gateway or Ingress Controller
- Monad recommends using the Kubernetes Gateway API for ingress and will create HTTPRoute and TCPRoute resources automatically. You need a Gateway API implementation installed in your cluster (e.g., Traefik, Istio, Envoy Gateway, kgateway, or any other conformant implementation).
- Your implementation must support the experimental Gateway API channel, which includes
TCPRoute. - Installation varies by implementation. Refer to your implementation's documentation.
- This guide assumes the use of Gateway API, though alternatives for using an ingress controller are provided at the end of the document.
Create Namespace
Code
Phase 2: Create Required Secrets
These secrets must exist before installing the Helm chart. All secrets are created in the monad namespace.
1. License Secret
The Monad license is a TLS certificate. You should have received a license.crt file from Monad.
Code
2. Encryption Key Secret
Used for encrypting sensitive data within Monad. This is a base64-encoded random 32-byte key.
Code
3. Key Encryption Key (KEK)
The Key Encryption Key (KEK) is the master key that wraps every organization's Data Encryption Key (DEK). All historical KEK versions must be preserved — older versions stay in the Secret because previously-wrapped DEKs still need them to decrypt. Losing the KEK is unrecoverable: Monad has no way to recover wrapped DEKs without it, and every byte of data wrapped under a lost version becomes permanently inaccessible. You are the sole custodian.
Each Monad organization is issued its own DEK, which is wrapped by the active KEK before being stored. Decryption requires the same KEK version that performed the wrap, so the Secret holds every version that has ever been used. The KEK never leaves the cluster.
The Helm chart consumes the KEK from a Kubernetes Secret named monad-master-encryption-key in the monad namespace. Each data field is a numeric version label (1, 2, 3, …) holding a base64-encoded 32-byte random key. The highest numeric field is the active version; new versions are appended on rotation, and old versions are never deleted or overwritten. When a new version appears, Monad automatically re-wraps every organization's DEK to the new active KEK — no manual re-encryption step is required.
Recommended: sync from your secrets manager
In production, the KEK should originate in the secrets manager you already operate — HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, 1Password, etc. — and be reconciled into the cluster via External Secrets Operator (ESO), so that backups, audit, and rotation flow through the same controls as the rest of your secrets.
Store an item in your secrets manager (e.g. named monad-key-encryption-key) with one numeric field per version, each value a base64-encoded 32-byte random key (openssl rand 32 | base64 or any equivalent CSPRNG). Then enable the chart's ExternalSecret in your values-override.yaml:
Code
The extract directive syncs every field of the upstream item into the K8s Secret, so adding a new numeric version upstream needs no chart or values change.
You are the sole custodian of the KEK. Back up every version in your secrets manager, and back up that secrets manager. Never delete or overwrite a numeric field once it has been used to wrap data. Treat KEK exfiltration as a P0 security incident.
Without an external secret store
For air-gapped clusters or environments without ESO (or an equivalent bridge like SealedSecrets / SOPS), you can create the Secret directly with kubectl. Generate and back up the key material in something durable before applying it — once it lives only in the cluster, K8s becomes the source of truth, which is fragile.
Code
Rotations are performed by kubectl patch-ing in a new numeric field, or by re-creating the Secret with all existing versions plus the new one.
Verify
After install, confirm the api and operator pods see the mount:
Code
4. Image Pull Secret
You should have received credentials from Monad support for accessing the images in Docker Hub. Configure those here as default-pull-secret (required name).
Code
5. Authentication Secrets
Using External Secrets Operator
If you're using an external secrets store, you'll need to either save your secrets with the property names found in values.yaml or update the values.yaml to match your secret property names.
Code
Not Using External Secrets Operator
If you're not using an External Secrets operator, you need to create secrets for authentication backend credentials. Below is an example file of key/value environment variable pairs that you can create a secret from.
Note: Some of these variables exist in two forms. Monad is migrating from variable names like AUTH0_CLIENT_ID to MONAD_AUTH_AUTH0_CLIENT_ID. The MONAD_-prefixed variables are the new names, and the old names will be deprecated in a future release. For current and future compatibility, use both until informed that the old names can be removed.
api.env
Code
Create the Secret api from the file:
Code
ui.env
Code
Create the secret ui from the file:
Code
Phase 3: Configure TLS
For the remainder of this guide we will be using monad.example.com as our example domain.
Monad requires two TLS certificates:
1. Gateway Certificate
Used by the Gateway to terminate HTTPS for all web traffic. This is a standard certificate for your Monad hostname (e.g., monad.example.com).
The Secret must be created in the same namespace as your Gateway resource (not the monad namespace). This is a Gateway API requirement: certificate secrets must be co-located with the Gateway that references them.
See TLS with cert-manager for instructions on creating this certificate automatically.
2. HTTP Input TLS Certificate
Monad's HTTP Input workload handles inputs such as Syslog that require direct TLS termination at the Pod level. This certificate is separate from the Gateway certificate and must be named http-input-tls in the monad namespace.
Syslog and other TCP-terminated inputs use the hostname with SNI for routing, with names like cef85707-4b6e-405a-aea9-3237d520e805.l4.monad.example.com. It should answer to both l4.monad.example.com and the wildcard domain *.l4.monad.example.com. You will also need a DNS record pointing *.l4.monad.example.com to the load balancer address that handles TCP traffic into your cluster.
The full FQDN that you use for L4 traffic doesn't have to be connected to the hostname that you use for Monad itself (such as *.l4.monad.example.com and monad.example.com). As long as the FQDN you choose lands on the Gateway and is routed to the correct Service, the Pod that receives it performs a handshake, retrieves the requested FQDN from SNI, and then uses the hostname portion to route to the corresponding pipeline.
See TLS with cert-manager for instructions on creating this certificate automatically.
Configure Your Gateway
Your Gateway resource needs an HTTPS listener that references the Gateway certificate Secret. The exact configuration depends on your implementation, but the Gateway API spec looks like:
Code
Refer to your Gateway implementation's documentation for how to configure listeners.
Phase 4: Configure values.yaml Overrides
Create a values-override.yaml file with the following configurations.
With Gateway API enabled, Monad's chart generates HTTPRoute resources automatically for all components. You do not need to configure ingress per-component — setting hostnames and routing at the top level is sufficient for all components to be reachable.
Code
The hostnames value replaces what previously required per-component BACKEND_URL environment variables and per-component ingress host configuration. Setting it once here propagates to all components automatically.
Phase 5: Install Monad
Log in to OCI Registry
Before you can pull the Helm chart, authenticate to the Docker registry:
Code
Note: Credentials are provided by Monad support. The login persists in ~/.docker/config.json.
Install with Custom Values
Code
You can perform upgrades of Monad using the same command above. Simply remove the --install directive to perform an upgrade.
Verify Installation
Some pods will initially come up in an Error state as they wait for the database to be ready. They should all be Running (or Completed) within a few minutes.
Code
Phase 6: Post-Installation
At this point, Monad should be up and running. Access it at your designated hostname.
Alternative Installation Options
TLS with cert-manager
If you're using cert-manager for certificate management, create a ClusterIssuer and two Certificate resources as described below.
ClusterIssuer
Code
Gateway Certificate
Create this in your Gateway's namespace (e.g., kube-system for Traefik on k3s):
Code
HTTP Input TLS Certificate
Create this in the monad namespace:
Code
External Postgres
If you want to use an external Postgres instance, you can disable the CloudNativePG operator and provide the connection details in the monad-db-app Secret.
Below is an example file you can create a Secret from. It uses the following values:
dbname: monaduser: monadpassword: somereallylongandcomplexpasswordhost: monad-db-rw.postgres / monad-db-rw.postgres.svc.cluster.local (default CNPG service structure for a database in thepostgresnamespace)port: 5432
Code
Create the Secret monad-db-app from the file:
Code
Disable the CloudNativePG operator in your values-override.yaml:
Code
Local Authentication
Local authentication creates an admin user of admin@monad.local and a random password. To activate this, remove the Auth0 and Cognito configuration from the api and ui secrets, and set MONAD_AUTH_TYPE to local for both components in your values-override.yaml:
Code
After completing the installation, retrieve the Secret with the username and password:
Code
This secret is only used to deliver credentials after installation and can be deleted after retrieval.
Local authentication does not allow the creation of more users than the local admin. If you wish to have multiple users, log in as the admin user and set up SSO from the Settings menu.
Ingress Controller
If your cluster uses a traditional Kubernetes Ingress resource rather than Gateway API, use the following values-override.yaml instead of the one in Phase 4. All other phases remain the same, except:
- The TLS certificate Secret should be created in the
monadnamespace instead of the Gateway namespace - Use
kubectl get ingress -n monadinstead ofkubectl get httproute -n monadto verify routing
Remove the routing and routes keys from the earlier example of values-override.yaml and add the following:
Code
The hostnames value at the top automatically configures backend URLs and the UI origin for all components. Per-component ingress.hosts configuration is handled by the chart defaults and does not need to be set explicitly.