Deploy Countly on Kubernetes with Helmfile

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 kubectl access 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 default direct for the public registry, or switch to gcpArtifactRegistry and set global.imageSource.gcpArtifactRegistry.repositoryPrefix to 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 an ExternalSecret resource backed by your Secret Manager store. Set secrets.externalSecret.remoteRefs to 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.yaml

This 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.

  1. 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
  2. Confirm that the ingress has an external address:

    kubectl get ingress -n countly
  3. Point a DNS record for your ingress.hostname at that address.
  4. 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

Pods stuck in ImagePullBackOff when using a private registry
This usually means the namespaced image pull secret is missing. Confirm 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 certificate is not provisioned
For 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. 
Operator reconciliation errors after install
Verify the operator versions in your cluster match the pinned versions listed in the Prerequisites callout above. Strimzi, the ClickHouse Operator, and the MongoDB Operator each ship Custom Resource Definitions whose schema may change between major versions.
The Countly API cannot reach MongoDB
The MongoDB Community Operator creates its connection secret in the 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. 

Related Resources

Was this page helpful?
Reach out to us for any other questions.
Helpful?

Looking For More Help?