ODK Central is a powerful open-source platform for creating and managing digital forms for data collection. While ODK offers cloud-hosted solutions, many organizations require self-hosted deployments for data sovereignty, custom integrations, or compliance requirements.
In this post, we’ll walk through how to self-host ODK Central using the Caktus Helm chart on DigitalOcean Kubernetes (DOKS) with PostgreSQL 18. This setup provides a production-ready, scalable deployment with automatic Let’s Encrypt SSL certificates.
Why Self-Host ODK Central?
Self-hosting ODK Central offers several advantages:
- Data sovereignty: Keep all data within your infrastructure
- Compliance: Meet strict data governance requirements
- Cost control: Better predict and manage costs for large-scale deployments
- Integration: Connect with existing systems hosted behind corporate firewalls
Architecture Overview
Our deployment uses the following managed services from DigitalOcean. However, the same steps can be applied to any Kubernetes cluster and managed PostgreSQL database:
- Kubernetes cluster as the infrastructure foundation
- PostgreSQL 18 database cluster for data persistence
Prerequisites
Before we begin, you’ll need:
- A DigitalOcean account
- Helm and kubectl
- A domain name with DNS you can manage (e.g., Route 53, Cloudflare, DigitalOcean)
- The
kubeconfigfile for the Kubernetes cluster (downloaded in Step 1) - The PostgreSQL CA certificate (downloaded in Step 2)
Step 1: Create a Kubernetes Cluster
- In the DigitalOcean Control Panel, click Create → Kubernetes
- Choose the latest stable Kubernetes version
- Select the same region as your database
- Under Choose cluster capacity, configure the node pool:
- Shared CPU - Basic for dev and testing, Dedicated CPU - General Purpose for production
- 2 nodes minimum for production (1 is okay for testing)
- Ensure High Availability is enabled for the control plane for production (disable for testing to save costs)
- Enter a unique cluster name, e.g.,
odk-cluster - Click Create Kubernetes Cluster
The Kubernetes cluster takes 5-10 minutes to provision.
Get Your Kubeconfig
After creation, on the Kubernetes cluster page, download the kubeconfig for the cluster:
- Click Download Config File
- Save it to a file, such as
odk-cluster-kubeconfig.yaml - Open a Terminal shell on your computer, and test the connection:
export KUBECONFIG=/path/to/odk-cluster-kubeconfig.yaml
kubectl get node
You should see the nodes that were created for your cluster, for example:
NAME STATUS ROLES AGE VERSION
pool-qly76y290-3cx0db Ready <none> 46h v1.36.0
pool-qly76y290-3cx0dr Ready <none> 46h v1.36.0
Step 2: Create a PostgreSQL 18 Database Cluster
DigitalOcean’s managed PostgreSQL service provides high availability, automated backups, and easy scaling. We’ll use PostgreSQL 18 in this post; however, we recommend using the latest version of PostgreSQL supported by ODK Central.
Create the Database
- In the DigitalOcean Control Panel, click Create → Managed Database
- Choose PostgreSQL as the engine and select v18 as the version
- Choose Standard Edition (for most use cases) and a Basic - Shared CPU plan (e.g., 1 vCPU / 1 GB for ~$13/mo)
- Set storage to at least 10 GiB and enable Storage Autoscaling
- Select a datacenter region – use the same region as your Kubernetes cluster for private hostname connectivity
- Give your database cluster a unique name, such as
odk-central-db - Click Create Database Cluster
The database takes 5-10 minutes to provision. You can work on this step in parallel with Step 1.
Get Connection Details
After creation, on the database page, click on VPC network under Connection Details to review the private connection details:
- Private Hostname: For same-region connections (e.g.,
private-db-pgsql-<snip>.m.db.ondigitalocean.com) – use this from your Kubernetes cluster - Port:
25060 - Database:
defaultdb - Username:
doadmin - Password: Auto-generated – save it securely
Download the CA Certificate
At the bottom of the Connection Details section:
- Click Download CA certificate
- Save it to
ca-certificate.crt
You’ll need this file in Step 5 to create a Kubernetes ConfigMap.
Step 2.5: Allow Kubernetes to Access the Database
For better data security, you should add your Kubernetes cluster as a trusted source to lock down the database and prevent public access:
- Click the Network Access tab
- Click Add Trusted Sources
- Under Quick select, find Kubernetes, and then select your cluster
- Click Add Trusted Sources
Note: Selecting the cluster adds its network; there is no need to manually specify IP ranges.
Step 3: Install Traefik with Let’s Encrypt
Traefik is a modern ingress controller with built-in ACME/Let’s Encrypt support. We use the TLS-ALPN challenge, which obtains SSL certificates automatically – no DNS API credentials or port 80 needed.
Create Traefik Values File
Create traefik-values.yaml, replacing all required placeholders:
# traefik-values.yaml
certificatesResolvers:
letsencrypt:
acme:
email: "[email protected]" # Replace with your email
storage: "/data/acme.json"
tlsChallenge: true
additionalArguments:
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--entrypoints.web.http.redirections.entrypoint.permanent=true"
- "--entrypoints.websecure.http.tls.certresolver=letsencrypt"
persistence:
enabled: true
name: data
accessMode: ReadWriteOnce
size: 128Mi
path: /data
podSecurityContext:
runAsGroup: 65532
runAsNonRoot: true
runAsUser: 65532
fsGroup: 65532
fsGroupChangePolicy: OnRootMismatch
ports:
web:
expose:
default: true
exposedPort: 80
port: 80
websecure:
expose:
default: true
exposedPort: 443
port: 443
providers:
kubernetesCRD:
enabled: true
allowEmptyServices: true
kubernetesIngress:
enabled: true
allowEmptyServices: true
api:
dashboard: false
Install Traefik
helm repo add traefik https://traefik.github.io/charts
helm repo update
helm install traefik traefik/traefik \
--namespace traefik \
--create-namespace \
--values traefik-values.yaml
Verify
kubectl get pods -n traefik
You should see one Traefik pod running and ready, for example:
NAME READY STATUS RESTARTS AGE
traefik-88f847c66-snnv7 1/1 Running 0 87s
Reconfiguring Traefik
In case you need to update traefik-values.yaml, use helm upgrade to apply the changes without reinstalling:
helm upgrade traefik traefik/traefik \
--namespace traefik \
--values traefik-values.yaml
Step 4: Configure DNS
Create an A record pointing your domain (e.g., odk.example.com) at the Traefik LoadBalancer IP.
# Get the Traefik LoadBalancer IP
kubectl get svc -n traefik
# Look for the EXTERNAL-IP column -- it may take a minute to appear
Then create the A record in your DNS provider (Route 53, Cloudflare, DigitalOcean, etc.) pointing your domain to that IP. Wait for DNS propagation before proceeding:
dig +short odk.your-domain.com
Step 5: Install ODK Central
Add the Helm Repository
helm repo add caktus https://caktus.github.io/helm-charts
helm repo update
Create Kubernetes Resources
# CA certificate for PostgreSQL SSL - this command requires the file you downloaded in Step 2
kubectl create ns odk
kubectl create configmap postgres-ca-cert \
-n odk \
--from-file=ca.crt=/path/to/ca-certificate.crt
Create the Values File
Create odk-values.yaml with your actual values. Replace all placeholders. For the Enketo secrets, you can use openssl to generate random values, for example:
openssl rand -hex 32 # Generates 64 ASCII characters
openssl rand -hex 16 # Generates 32 ASCII characters
openssl rand -hex 64 # Generates 128 ASCII characters
# odk-values.yaml
global:
centralDomain: "odk.example.com" # Your domain
enketoSecretName: "central-enketo-secrets"
enketoSecrets:
enketoSecret: "YOUR_64_CHAR_SECRET" # Output of `openssl rand -hex 32`
enketoLessSecret: "YOUR_32_CHAR_SECRET" # Output of `openssl rand -hex 16`
enketoApiKey: "YOUR_128_CHAR_SECRET" # Output of `openssl rand -hex 64`
backend:
environmentVariables:
PGHOST: "private-db-pgsql-<region>-<id>.m.db.ondigitalocean.com" # Your private hostname
PGPORT: "25060"
PGDATABASE: "defaultdb"
PGUSER: "doadmin"
PGPASSWORD: "your-database-password" # Your database password
PGSSLMODE: "require"
# Node.js pg library needs NODE_EXTRA_CA_CERTS (not PGSSLROOTCERT)
NODE_EXTRA_CA_CERTS: "/etc/ssl/certs/pg-ca/ca.crt"
DB_POOL_SIZE: "10"
SESSION_LIFETIME: "86400"
DOMAIN: "odk.example.com" # Your domain
EMAIL_FROM: "[email protected]"
# Your SMTP settings
EMAIL_HOST: "smtp.example.com"
EMAIL_PORT: "587"
EMAIL_IGNORE_TLS: "false"
EMAIL_SECURE: "false"
EMAIL_USER: "your-smtp-username"
EMAIL_PASSWORD: "your-smtp-password"
HTTPS_PORT: "443"
SYSADMIN_EMAIL: "[email protected]" # Your sysadmin email
S3_SERVER: ""
S3_ACCESS_KEY: ""
S3_SECRET_KEY: ""
S3_BUCKET_NAME: ""
SENTRY_DSN: ""
SENTRY_TRACE_RATE: "0"
volumes:
- name: postgres-ca-cert
configMap:
name: postgres-ca-cert
volumeMounts:
- name: postgres-ca-cert
mountPath: /etc/ssl/certs/pg-ca
readOnly: true
frontend:
ingressApp:
enabled: false
ingress:
enabled: true
className: "traefik"
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/tls.certresolver: letsencrypt
traefik.ingress.kubernetes.io/buffering: |
maxRequestBodyBytes: 104857600
hosts:
- host: "odk.example.com" # Your domain
paths:
- path: /
pathType: ImplementationSpecific
tls:
- hosts:
- "odk.example.com" # Your domain
environmentVariables:
DOMAIN: "odk.example.com" # Your domain
SSL_TYPE: "upstream"
OIDC_ENABLED: "false"
# Simplified Redis (no haproxy)
enketo-redis-main:
replicas: 1
haproxy:
enabled: false
enketo-redis-cache:
replicas: 1
haproxy:
enabled: false
enketo:
config:
redisMainHost: "odk-central-enketo-redis-main"
redisCacheHost: "odk-central-enketo-redis-cache"
Key notes:
- Your domain name needs to be configured in several places; be sure you’ve replaced all instances of
odk.example.com. - Other common issues involve the database settings; be sure you’ve copied these exactly from the VPC Network connection details for your database, including a private hostname that starts with
private-....
Install
helm install odk-central caktus/odk-central \
--namespace odk \
--values odk-values.yaml
Verify
helm list -n odk
kubectl get pods -n odk
kubectl logs -n odk -l app.kubernetes.io/name=backend -f
Wait 3-5 minutes for the pods to settle and for Traefik to obtain the Let’s Encrypt certificate:
kubectl logs -n traefik -l app.kubernetes.io/name=traefik -f
# Look for "Register..." and certificate issuance
Reconfiguring ODK Central
In case you need to update odk-values.yaml, use helm upgrade to apply the changes without reinstalling:
helm upgrade odk-central caktus/odk-central \
--namespace odk \
--values odk-values.yaml
Step 6: Create Your First Admin User
ODK Central has no self-service sign-up. The first admin must be created via the command line.
Create and Promote the Admin
# Create the user (you'll be prompted for a password)
kubectl exec -it -n odk deployment/odk-central-backend \
-- odk-cmd --email [email protected] user-create
# Promote to administrator (required -- user-create does NOT auto-promote)
kubectl exec -it -n odk deployment/odk-central-backend \
-- odk-cmd --email [email protected] user-promote
After this, visit your configured domain in a web browser and log in. You can create additional users from the web interface.
Troubleshooting
“no pg_hba.conf entry for host”: Add your Kubernetes cluster’s network to the database’s trusted sources in the DigitalOcean Control Panel.
Pods stuck in Pending: Check cluster resource availability in the DigitalOcean Control Panel.
Enketo secrets errors: Ensure secrets are the correct length (64, 32, and 128 hex characters).
Resources
- Official ODK Central DigitalOcean Guide – uses Docker Compose instead of Kubernetes (review this for next steps and related configuration!)
- ODK Central Documentation
- Caktus Helm Chart Repository
- DigitalOcean Kubernetes
- Traefik ACME
Alternatives
If self-hosting ODK Central isn’t for you, several alternatives exist:
- ODK Cloud – Get ODK, Inc. offers a managed hosting solution for ODK Central on AWS, providing a turnkey option with official support
- Caktus Group – as maintainers of the unofficial Helm chart, we can help deploy and manage ODK Central for organizations that want it as part of their Kubernetes-based infrastructure
Conclusion
Self-hosting ODK Central on DigitalOcean Kubernetes with PostgreSQL gives you a production-ready platform with managed infrastructure, automatic SSL certificates, and Helm-based deployment. The combination of DigitalOcean’s managed services and modern Kubernetes tooling keeps ongoing maintenance minimal while giving you full control over your data.
