This guide walks you through deploying the full Countly stack on a Kubernetes cluster using Helmfile. It covers cloning the reference environment, picking sizing, TLS, observability, and security profiles, filling in the required credentials, and applying the seven Helm charts in a single command. It is intended for DevOps engineers, SREs, and platform teams who self-host Countly on Kubernetes.
What you will deploy
Helmfile orchestrates seven Helm charts, each in its own namespace: the Countly application (countly), MongoDB (mongodb), ClickHouse (clickhouse), Kafka (kafka), the observability stack (observability), and the optional MongoDB-to-ClickHouse migration service (countly-migration).
Prerequisites
Before you begin, you must have the following in place:
- A Kubernetes cluster with
kubectlaccess and permissions to create namespaces and cluster-scoped resources. - Helm 3 and Helmfile installed locally.
-
The required Kubernetes operators installed in the cluster: cert-manager, External Secrets Operator, Strimzi Kafka Operator, ClickHouse Operator, MongoDB Kubernetes Operator, and an NGINX Ingress controller.
- A DNS record you control, so you can point your chosen hostname at the ingress controller after deployment.
- Image pull credentials for any private registry you plan to use (e.g., Google Artifact Registry).
Operator versions are pinned
The Helm charts expect specific operator versions (cert-manager v1.17.2, External Secrets Operator 1.3.1, Strimzi 0.51.0, ClickHouse Operator 0.0.2, MongoDB Operator 1.7.0, NGINX Ingress 2.1.0). Installing a different major version may produce reconciliation errors.
Copying the Reference Environment
The repository ships with an environments/reference directory that contains every value file you need, with inline documentation. Start by copying it under a new name that identifies your deployment:
cp -r environments/reference environments/my-deployment
Replace my-deployment with a stable identifier (for example, the customer name or the cluster name). You will reference this name in every command that follows.
Choosing Profiles in global.yaml
Open environments/my-deployment/global.yaml and select one value for each of the five profile dimensions. Profiles compose freely: you can mix any sizing with any TLS, observability, Kafka Connect, or security mode.
| Dimension | Options | Controls |
|---|---|---|
sizing |
local / small / production
|
CPU and memory requests, replica counts, HPA, PDBs |
tls |
none / letsencrypt / provided / selfSigned
|
How the ingress terminates HTTPS |
observability |
disabled / full / external-grafana / external
|
Which parts of the observability stack are deployed in-cluster |
kafkaConnect |
throughput / balanced / low-latency
|
Batch sizes, write frequency, and Kafka Connect memory |
security |
open / hardened
|
Network policy enforcement level |
In addition to the five profiles, set the following keys in global.yaml:
-
ingress.hostname— the public hostname for your Countly instance. -
global.imageSource.mode— keep the defaultdirectfor the public registry, or switch togcpArtifactRegistryand setglobal.imageSource.gcpArtifactRegistry.repositoryPrefixto your GAR path. -
global.imagePullSecrets— required when pulling Countly images from a private registry.
Picking a sizing profile
Use local for a single-node evaluation, small for a low-traffic team or staging cluster,
and production for customer-facing workloads with HPA, PDBs, and replica counts tuned for resilience.
Filling in the Required Credentials
Each chart has its own credentials file under environments/my-deployment/. Open each file and supply the values listed below.
| File | Required Keys |
|---|---|
credentials-countly.yaml |
secrets.common.* (encryption keys, optional SMTP auth), secrets.clickhouse.password, secrets.mongodb.password
|
credentials-mongodb.yaml |
users.app.password, users.metrics.password
|
credentials-clickhouse.yaml |
auth.defaultUserPassword.password |
credentials-kafka.yaml |
kafkaConnect.clickhouse.password |
image-pull-secrets.example.yaml |
Private registry pull secret manifests for the countly and kafka namespaces (only when using a private registry) |
If you would rather see every required secret in one place, refer to secrets.example.yaml in the reference environment. It lists all required keys across all charts.
Choosing a Secret Mode
Set secrets.mode in each credentials-*.yaml file to one of:
-
values— direct YAML values. Suitable for evaluation or for files encrypted with SOPS. -
existingSecret— reference a Kubernetes secret you have already created out of band. -
externalSecret— have the chart create anExternalSecretresource backed by your Secret Manager store. Setsecrets.externalSecret.remoteRefsto point at the keys.
Recommended naming convention for Secret Manager
When you use externalSecret with Google Secret Manager, follow the <customer>-* convention: <customer>-gar-dockerconfig, <customer>-countly-encryption-reports-key, <customer>-countly-web-session-secret, <customer>-countly-password-secret, <customer>-clickhouse-password, <customer>-mongodb-admin-password, <customer>-mongodb-app-password, and <customer>-mongodb-metrics-password.
The same <customer>-clickhouse-password key is reused by the Countly app, the ClickHouse default user, and the Kafka Connect ClickHouse sink.
Registering the Environment in helmfile.yaml.gotmpl
Helmfile reads its environments from the top-level helmfile.yaml.gotmpl. Add a new entry that matches the directory name you chose:
environments:
my-deployment:
values:
- environments/my-deployment/global.yamlThis makes my-deployment a valid value for the -e flag in the Helmfile command in the next step.
Deploying with Helmfile
Run a single Helmfile command to apply all charts in the correct order:
helmfile -e my-deployment apply
Helmfile installs MongoDB, ClickHouse, and Kafka first, then the Countly application, then the observability stack. The first apply takes several minutes because the operators must provision stateful workloads and wait for them to become healthy.
Manual installation without Helmfile
If you prefer to call helm install directly, the project README documents the per-chart commands and the required value-file ordering: global, sizing, dimension profiles, security, environment, and secrets, in that order. The Helmfile workflow is recommended because it preserves this layering automatically.
Verifying the Deployment
After helmfile apply returns, confirm the stack is healthy before pointing traffic at it.
-
Check that all pods are running across the seven namespaces:
kubectl get pods -n countly kubectl get pods -n mongodb kubectl get pods -n clickhouse kubectl get pods -n kafka kubectl get pods -n observability
-
Confirm that the ingress has an external address:
kubectl get ingress -n countly
- Point a DNS record for your
ingress.hostnameat that address. -
Open
https://<your-hostname>in a browser. Countly should serve the first-time setup screen.
You have a running Countly stack
Once the setup screen loads, complete the initial admin user creation, then start sending events from your SDKs to the /i endpoint on your hostname.
Common Issues and Gotchas
ImagePullBackOff when using a private registry
global.imagePullSecrets is set in global.yaml and that you have created the matching dockerconfigjson secret in the countly and kafka namespaces. If you use External Secrets Operator, set global.imagePullSecretExternalSecret in global.yaml and point its remoteRef.key at a Secret Manager entry whose value is the Docker config JSON for your registry.tls: letsencrypt, cert-manager must be installed and your DNS record must already resolve to the ingress before the certificate request can succeed. For tls: provided, either pre-create a kubernetes.io/tls secret, or enable ingress.tls.externalSecret in countly.yaml to materialize one from Secret Manager. mongodb namespace, but the Countly application runs in the countly namespace. The chart copies the secret automatically when both are bundled, but if you switched MongoDB to external mode, you must provide a secret reference that the Countly chart can read directly.