Skip to main content

GeoPulse Helm Chart Deployment & Configuration

This comprehensive guide covers manual Helm installation, configuration options, and advanced customization for GeoPulse on Kubernetes.

Quick Start

For an interactive, guided installation experience, see the Kubernetes Quick Start Guide which uses automated scripts.


Overview

The GeoPulse Helm chart provides a production-ready deployment with the following features:

  • Full Stack Deployment: Backend (Java/Quarkus in Native mode), Frontend (Vue.js), PostgreSQL with PostGIS
  • Optional MQTT Support: Conditional Mosquitto MQTT broker deployment
  • Production Ready: Health checks, resource limits, persistent storage
  • Flexible Configuration: Extensive values.yaml for customization
  • Security: Automatic JWT key generation, secrets management
  • Ingress Support: Optional ingress with TLS
  • High Availability Ready: Support for replicas and pod disruption budgets

Prerequisites

  • Kubernetes 1.19+
  • Helm 3.2.0+
  • PV provisioner support in the underlying infrastructure (for persistence)
  • (Optional) Ingress controller for external access
  • (Optional) cert-manager for TLS certificates

Manual Installation

This section describes how to install the chart directly with Helm for advanced users or automated workflows.

1. Clone the Repository

git clone https://github.com/tess1o/GeoPulse.git
cd GeoPulse

2. Install the Chart

You can install the chart using the helm install command. You must provide your own values, either with --set flags or a custom values file (-f my-values.yaml).

# Example installing with a custom values file from the examples
helm install geopulse ./helm/geopulse -f helm/examples/medium-deployment.yaml

3. Verify Installation

# Check pod status
kubectl get pods -l app.kubernetes.io/instance=geopulse

# Run Helm tests
helm test geopulse

Configuration

The GeoPulse Helm chart provides two ways to configure the application:

  1. Built-in Values - Common configuration options available in values.yaml
  2. Custom Environment Variables - For advanced settings not included in the default chart

Understanding values.yaml

The Helm chart uses a hierarchical values.yaml file to configure GeoPulse. When you install or upgrade the chart, you can override these values:

# Option 1: Command-line flags
helm install geopulse ./helm/geopulse --set config.admin.email="admin@example.com"

# Option 2: Custom values file (recommended)
helm install geopulse ./helm/geopulse -f my-values.yaml

Built-in Configuration Options

Core Configuration

Featurevalues.yaml PathEnvironment VariableDefault
Frontend URLconfig.uiUrlGEOPULSE_UI_URLhttp://localhost:5555
Cookie Domainconfig.cookieDomainGEOPULSE_COOKIE_DOMAIN"" (empty)
Secure Cookiesconfig.authSecureCookiesGEOPULSE_AUTH_SECURE_COOKIESfalse

Admin Configuration

Featurevalues.yaml PathEnvironment VariableDefault
Admin Emailconfig.admin.emailGEOPULSE_ADMIN_EMAIL""

Example:

config:
admin:
email: "admin@example.com"

Authentication & Registration

Featurevalues.yaml PathEnvironment VariableDefault
Registration Enabledconfig.auth.registrationEnabledGEOPULSE_AUTH_REGISTRATION_ENABLEDtrue
Password Registrationconfig.auth.passwordRegistrationEnabledGEOPULSE_AUTH_PASSWORD_REGISTRATION_ENABLEDtrue
OIDC Registrationconfig.auth.oidcRegistrationEnabledGEOPULSE_AUTH_OIDC_REGISTRATION_ENABLEDtrue

Example:

config:
auth:
registrationEnabled: false # Disable all registration
passwordRegistrationEnabled: false # Disable password registration only
oidcRegistrationEnabled: true # Allow OIDC registration

JWT Configuration

Featurevalues.yaml PathEnvironment VariableDefault
Access Token Lifespanconfig.jwt.accessTokenLifespanGEOPULSE_JWT_ACCESS_TOKEN_LIFESPAN1800 (30 min)
Refresh Token Lifespanconfig.jwt.refreshTokenLifespanGEOPULSE_JWT_REFRESH_TOKEN_LIFESPAN604800 (7 days)
JWT Issuerconfig.jwt.issuerGEOPULSE_JWT_ISSUERhttp://localhost:8080
Public Key Locationconfig.jwt.publicKeyLocationGEOPULSE_JWT_PUBLIC_KEY_LOCATIONfile:/app/keys/jwt-public-key.pem
Private Key Locationconfig.jwt.privateKeyLocationGEOPULSE_JWT_PRIVATE_KEY_LOCATIONfile:/app/keys/jwt-private-key.pem

Example:

config:
jwt:
accessTokenLifespan: 3600 # 1 hour
refreshTokenLifespan: 2592000 # 30 days
issuer: "https://geopulse.example.com"

GPS Filtering

Featurevalues.yaml PathEnvironment VariableDefault
Filter Inaccurate Dataconfig.gps.filterInaccurateDataGEOPULSE_GPS_FILTER_INACCURATE_DATA_ENABLEDtrue
Max Allowed Accuracyconfig.gps.maxAllowedAccuracyGEOPULSE_GPS_MAX_ALLOWED_ACCURACY100 meters
Max Allowed Speedconfig.gps.maxAllowedSpeedGEOPULSE_GPS_MAX_ALLOWED_SPEED250 m/s

Example:

config:
gps:
filterInaccurateData: true
maxAllowedAccuracy: 50 # More strict filtering
maxAllowedSpeed: 200

Prometheus Metrics

Featurevalues.yaml PathEnvironment VariableDefault
Enabledconfig.prometheus.enabledGEOPULSE_PROMETHEUS_ENABLEDfalse
Refresh Intervalconfig.prometheus.refreshIntervalGEOPULSE_PROMETHEUS_REFRESH_INTERVAL10m
GPS Points Metricsconfig.prometheus.gpsPoints.enabledGEOPULSE_PROMETHEUS_GPS_POINTS_ENABLEDtrue
User Metricsconfig.prometheus.userMetrics.enabledGEOPULSE_PROMETHEUS_USER_METRICS_ENABLEDtrue
Timeline Metricsconfig.prometheus.timeline.enabledGEOPULSE_PROMETHEUS_TIMELINE_ENABLEDtrue
Favorites Metricsconfig.prometheus.favorites.enabledGEOPULSE_PROMETHEUS_FAVORITES_ENABLEDtrue
Geocoding Metricsconfig.prometheus.geocoding.enabledGEOPULSE_PROMETHEUS_GEOCODING_ENABLEDtrue
Memory Metricsconfig.prometheus.memory.enabledGEOPULSE_PROMETHEUS_MEMORY_ENABLEDtrue

Example:

config:
prometheus:
enabled: true
refreshInterval: "5m"
# Optionally disable specific metric classes
memory:
enabled: false

Geocoding Configuration

Featurevalues.yaml PathEnvironment VariableDefault
Primary Providerconfig.geocoding.primaryProviderGEOPULSE_GEOCODING_PRIMARY_PROVIDERnominatim
Fallback Providerconfig.geocoding.fallbackProviderGEOPULSE_GEOCODING_FALLBACK_PROVIDER""
Delay (ms)config.geocoding.delayMsGEOPULSE_GEOCODING_DELAY_MS1000
Nominatim Enabledconfig.geocoding.nominatim.enabledGEOPULSE_GEOCODING_NOMINATIM_ENABLEDtrue
Nominatim URLconfig.geocoding.nominatim.urlGEOPULSE_GEOCODING_NOMINATIM_URLhttps://nominatim.openstreetmap.org
Photon Enabledconfig.geocoding.photon.enabledGEOPULSE_GEOCODING_PHOTON_ENABLEDfalse
Photon URLconfig.geocoding.photon.urlGEOPULSE_GEOCODING_PHOTON_URLhttps://photon.komoot.io
Google Maps Enabledconfig.geocoding.googleMaps.enabledGEOPULSE_GEOCODING_GOOGLE_MAPS_ENABLEDfalse
Google Maps API Keyconfig.geocoding.googleMaps.apiKeyGEOPULSE_GEOCODING_GOOGLE_MAPS_API_KEY"" (secret)
Mapbox Enabledconfig.geocoding.mapbox.enabledGEOPULSE_GEOCODING_MAPBOX_ENABLEDfalse
Mapbox Access Tokenconfig.geocoding.mapbox.accessTokenGEOPULSE_GEOCODING_MAPBOX_ACCESS_TOKEN"" (secret)

Example:

config:
geocoding:
primaryProvider: "googlemaps"
fallbackProvider: "nominatim"
delayMs: 500
googleMaps:
enabled: true
apiKey: "your-api-key-here" # Stored in Kubernetes Secret
nominatim:
enabled: true
language: "en-US" # Optional: BCP 47 language tag
photon:
language: "en-US" # Optional: BCP 47 language tag

Location Sharing

Featurevalues.yaml PathEnvironment VariableDefault
Base URL Overrideconfig.share.baseUrlGEOPULSE_SHARE_BASE_URL"" (uses window.location.origin)

Example:

config:
share:
baseUrl: "https://share.geopulse.example.com"

User Invitation

Featurevalues.yaml PathEnvironment VariableDefault
Base URL Overrideconfig.invitation.baseUrlGEOPULSE_INVITATION_BASE_URL"" (uses window.location.origin)

Example:

config:
invitation:
baseUrl: "https://invite.geopulse.example.com"

OwnTracks

Featurevalues.yaml PathEnvironment VariableDefault
Ping Timestamp Overrideconfig.owntracks.pingTimestampOverrideGEOPULSE_OWNTRACKS_PING_TIMESTAMP_OVERRIDEfalse

Example:

config:
owntracks:
pingTimestampOverride: true

OIDC / SSO Configuration

Featurevalues.yaml PathEnvironment VariableDefault
OIDC Enabledconfig.oidc.enabledGEOPULSE_OIDC_ENABLEDfalse
Auto-Link Accountsconfig.oidc.autoLinkAccountsGEOPULSE_OIDC_AUTO_LINK_ACCOUNTSfalse
Cleanup Enabledconfig.oidc.cleanupEnabledGEOPULSE_OIDC_CLEANUP_ENABLEDtrue
Google OIDC
config:
oidc:
enabled: true
google:
enabled: true
clientId: "your-google-client-id.apps.googleusercontent.com"
clientSecret: "your-google-client-secret" # Stored in Kubernetes Secret
Microsoft OIDC
config:
oidc:
enabled: true
microsoft:
enabled: true
clientId: "your-microsoft-client-id"
clientSecret: "your-microsoft-client-secret" # Stored in Kubernetes Secret
Generic OIDC (Keycloak, Authentik, Okta, etc.)
config:
oidc:
enabled: true
generic:
enabled: true
name: "Keycloak" # Display name in UI
clientId: "geopulse"
clientSecret: "your-keycloak-client-secret" # Stored in Kubernetes Secret
discoveryUrl: "https://keycloak.example.com/realms/master/.well-known/openid-configuration"

Advanced Configuration: Custom Environment Variables

Some GeoPulse features are not exposed in the default Helm chart values.yaml. For these advanced settings, you can add custom environment variables to the backend deployment.

Method 1: Using extraEnv in values.yaml

This is the recommended approach for most custom configuration needs.

Step 1: Create a custom values file (custom-values.yaml):

backend:
extraEnv:
- name: GEOPULSE_TIMELINE_PROCESSING_THREADS
value: "4"
- name: GEOPULSE_TIMELINE_VIEW_ITEM_LIMIT
value: "200"
- name: GEOPULSE_IMPORT_BULK_INSERT_BATCH_SIZE
value: "1000"

Step 2: Apply the configuration:

helm upgrade geopulse ./helm/geopulse -f custom-values.yaml

Method 2: Using ConfigMap for Multiple Variables

For many custom variables, create a dedicated ConfigMap:

Step 1: Create custom-config.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
name: geopulse-custom-config
namespace: default
data:
GEOPULSE_TIMELINE_PROCESSING_THREADS: "4"
GEOPULSE_TIMELINE_VIEW_ITEM_LIMIT: "200"
GEOPULSE_IMPORT_BULK_INSERT_BATCH_SIZE: "1000"
GEOPULSE_TIMELINE_STAYPOINT_RADIUS_METERS: "75"

Step 2: Apply the ConfigMap:

kubectl apply -f custom-config.yaml

Step 3: Reference it in your values file:

backend:
extraEnvFrom:
- configMapRef:
name: geopulse-custom-config

Method 3: Using Secrets for Sensitive Data

For sensitive configuration like API keys:

Step 1: Create a Kubernetes Secret:

kubectl create secret generic geopulse-extra-secrets \
--from-literal=GEOPULSE_AI_SOME_API_KEY='your-secret-key'

Step 2: Reference it in values:

backend:
extraEnv:
- name: GEOPULSE_AI_SOME_API_KEY
valueFrom:
secretKeyRef:
name: geopulse-extra-secrets
key: GEOPULSE_AI_SOME_API_KEY

Configuration Examples

Minimal Setup (Local Testing)

# minimal-values.yaml
postgres:
persistence:
enabled: false

keygen:
persistence:
enabled: false
helm install geopulse ./helm/geopulse -f minimal-values.yaml

Production Setup with Ingress

# production-values.yaml
# Resource allocation for production
backend:
replicaCount: 2
resources:
limits:
memory: 2Gi
cpu: 2000m
requests:
memory: 1Gi
cpu: 1000m

frontend:
replicaCount: 2

postgres:
persistence:
enabled: true
size: 50Gi
storageClass: "fast-ssd"
resources:
limits:
memory: 4Gi
cpu: 2000m

# Ingress configuration
ingress:
enabled: true
className: nginx
hostname: geopulse.example.com
tls:
enabled: true
secretName: geopulse-tls
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"

# Application configuration
config:
uiUrl: "https://geopulse.example.com"
cookieDomain: ".example.com"
authSecureCookies: true
admin:
email: "admin@example.com"
oidc:
enabled: true
google:
enabled: true
clientId: "..."
clientSecret: "..."
helm install geopulse ./helm/geopulse -f production-values.yaml

Enable MQTT Broker

# mqtt-values.yaml
mosquitto:
enabled: true
username: mqtt_admin
service:
type: LoadBalancer # Or NodePort for external access
persistence:
enabled: true

config:
# Update UI URL if needed
uiUrl: "http://your-domain:5555"
helm install geopulse ./helm/geopulse -f mqtt-values.yaml

Use External PostgreSQL

# external-db-values.yaml
postgres:
enabled: false

externalPostgres:
host: postgres.example.com
port: 5432
database: geopulse
username: geopulse-user
password: "your-secure-password"
helm install geopulse ./helm/geopulse -f external-db-values.yaml

Self-Hosted Geocoding

config:
geocoding:
primaryProvider: "nominatim"
nominatim:
enabled: true
url: "https://nominatim.mycompany.internal"
language: "de" # Optional: BCP 47 language tag
# Self-hosted Photon as fallback
fallbackProvider: "photon"
photon:
enabled: true
url: "https://photon.mycompany.internal"
language: "de" # Optional: BCP 47 language tag

High-Performance Import Configuration

backend:
resources:
limits:
memory: 4Gi
cpu: 2000m
extraEnv:
- name: GEOPULSE_IMPORT_BULK_INSERT_BATCH_SIZE
value: "2000"
- name: GEOPULSE_IMPORT_MERGE_BATCH_SIZE
value: "1000"
- name: GEOPULSE_IMPORT_LARGE_FILE_THRESHOLD_MB
value: "200"
- name: GEOPULSE_TIMELINE_PROCESSING_THREADS
value: "4"

Monitoring with Prometheus

config:
prometheus:
enabled: true
refreshInterval: "5m"
# All metrics enabled by default

# Optional: If using Prometheus Operator
serviceMonitor:
enabled: true
interval: 30s
path: /api/prometheus/metrics

Parameters Reference

Global Parameters

ParameterDescriptionDefault
global.imagePullPolicyImage pull policyIfNotPresent
global.imagePullSecretsImage pull secrets[]

Backend Parameters

ParameterDescriptionDefault
backend.image.repositoryBackend image repositorytess1o/geopulse-backend
backend.image.tagBackend image tag. Use 1.9.0-native for optimized builds (modern CPUs). Use 1.9.0-native-compat for older CPUs (x86-64-v2) or Raspberry Pi 3/4. See CPU Compatibility section below for troubleshooting.1.9.0-native
backend.replicaCountNumber of backend replicas1
backend.service.typeBackend service typeClusterIP
backend.service.portBackend service port8080
backend.resources.limits.memoryBackend memory limit1Gi
backend.resources.limits.cpuBackend CPU limit1000m

Frontend Parameters

ParameterDescriptionDefault
frontend.image.repositoryFrontend image repositorytess1o/geopulse-ui
frontend.image.tagFrontend image tag1.9.0
frontend.replicaCountNumber of frontend replicas1
frontend.service.typeFrontend service typeClusterIP
frontend.service.portFrontend service port80

PostgreSQL Parameters

ParameterDescriptionDefault
postgres.enabledDeploy PostgreSQLtrue
postgres.image.repositoryPostgreSQL imagepostgis/postgis
postgres.image.tagPostgreSQL image tag17-3.5
postgres.persistence.enabledEnable persistencetrue
postgres.persistence.sizePVC size10Gi
postgres.persistence.storageClassStorage class""
postgres.databaseDatabase namegeopulse
postgres.usernameDatabase usernamegeopulse-user
postgres.config.sharedBuffersPostgreSQL shared_buffers256MB

MQTT (Mosquitto) Parameters

ParameterDescriptionDefault
mosquitto.enabledDeploy MQTT brokerfalse
mosquitto.image.repositoryMosquitto imageiegomez/mosquitto-go-auth
mosquitto.image.tagMosquitto image tag3.0.0-mosquitto_2.0.18
mosquitto.usernameMQTT admin usernamegeopulse_mqtt_admin
mosquitto.persistence.enabledEnable persistencetrue

Ingress Parameters

ParameterDescriptionDefault
ingress.enabledEnable ingressfalse
ingress.classNameIngress class namenginx
ingress.hostnameHostnamegeopulse.example.com
ingress.tls.enabledEnable TLSfalse
ingress.tls.secretNameTLS secret namegeopulse-tls

Upgrading

# Upgrade to a new version
helm upgrade geopulse ./helm/geopulse

# Upgrade with new values
helm upgrade geopulse ./helm/geopulse -f my-values.yaml

# See what will change
helm diff upgrade geopulse ./helm/geopulse -f my-values.yaml

Uninstalling

helm uninstall geopulse

Note: This will not delete PersistentVolumeClaims. To delete them:

kubectl delete pvc -l app.kubernetes.io/instance=geopulse

Accessing GeoPulse

With Ingress

Access via the configured hostname:

https://geopulse.example.com

Without Ingress (Port Forward)

kubectl port-forward svc/geopulse-frontend 5555:80

Then visit: http://localhost:5555

With LoadBalancer

kubectl get svc geopulse-frontend
# Note the EXTERNAL-IP and access via http://EXTERNAL-IP

Persistence

GeoPulse uses PersistentVolumeClaims for:

  1. PostgreSQL data (10Gi by default)
  2. JWT keys (10Mi)
  3. MQTT data (if enabled, 1Gi for data, logs, config)

Configure storage classes and sizes in values.yaml.


Security

  • Passwords are auto-generated if not provided
  • JWT keys are generated during installation
  • Secrets are stored in Kubernetes Secret objects
  • Use existing secrets with: secrets.useExistingSecret=true

Managing Secrets

Best Practices

  1. Never commit secrets to Git - Use .gitignore for values files with secrets
  2. Use Kubernetes Secrets - Store sensitive data in Secret objects, not ConfigMaps
  3. Consider External Secret Managers - For production, use AWS Secrets Manager, HashiCorp Vault, or similar
  4. Rotate secrets regularly - Update secrets periodically and restart pods

Using Sealed Secrets

For GitOps workflows, consider using Sealed Secrets:

# Install Sealed Secrets controller
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml

# Create a sealed secret
echo -n 'your-secret-value' | kubectl create secret generic mysecret --dry-run=client --from-file=api-key=/dev/stdin -o yaml | \
kubeseal -o yaml > sealed-secret.yaml

# Commit sealed-secret.yaml to Git safely

Monitoring

Check Pod Status

kubectl get pods -l app.kubernetes.io/instance=geopulse

View Logs

# Backend
kubectl logs -l app.kubernetes.io/component=backend -f

# Frontend
kubectl logs -l app.kubernetes.io/component=frontend -f

# PostgreSQL
kubectl logs -l app.kubernetes.io/component=database -f

Run Tests

helm test geopulse

Troubleshooting

CPU Compatibility Issues

If your backend pods are crashing immediately with errors about missing CPU features, you have an older CPU that needs the compatible image.

Check pod logs:

kubectl logs -l app.kubernetes.io/component=backend

Look for errors like:

  • [AVX2, BMI1, BMI2, FMA, F16C, LZCNT] (AMD64 CPUs)
  • [FP, ASIMD, CRC32, LSE] (ARM64/Raspberry Pi)

Solution: Override the image tag in your values file:

# values-compat.yaml
backend:
image:
tag: "1.9.0-native-compat" # Use compatible image for old CPUs

Then upgrade your deployment:

helm upgrade geopulse ./helm/geopulse -f values-compat.yaml

Who needs the compatible image?

  • Old x86 CPUs: Intel pre-Haswell (before 2013), AMD pre-Excavator (before 2015)
    • Examples: Intel Core i5-3470T (Ivy Bridge), Intel Xeon E5-2670 (Sandy Bridge)
  • Raspberry Pi 3/4: ARM Cortex-A53/A72 processors

Check node CPU features:

# For x86/AMD64 nodes:
kubectl debug node/YOUR-NODE-NAME -it --image=ubuntu -- lscpu | grep -i flags
# Look for: avx2, bmi1, bmi2, fma, f16c

# For ARM64 nodes:
kubectl debug node/YOUR-NODE-NAME -it --image=ubuntu -- cat /proc/cpuinfo | grep Features
# Look for: asimd, crc32, atomics

Pods Not Starting

Check events:

kubectl get events --sort-by='.lastTimestamp'

Database Connection Issues

Verify PostgreSQL is running:

kubectl get pods -l app.kubernetes.io/component=database
kubectl logs -l app.kubernetes.io/component=database

JWT Key Issues

Check keygen job:

kubectl get jobs
kubectl logs job/geopulse-keygen

MQTT Not Working

Ensure MQTT is enabled and check logs:

kubectl logs -l app.kubernetes.io/component=mqtt

Configuration Not Applied

If your configuration changes aren't taking effect:

  1. Verify the values are set:

    helm get values geopulse
  2. Check the ConfigMap:

    kubectl get configmap geopulse-config -o yaml
  3. Restart the backend pod:

    kubectl rollout restart deployment geopulse-backend

Finding Environment Variables

To see all environment variables in the running backend:

kubectl exec -it deployment/geopulse-backend -- env | grep GEOPULSE | sort

Validating Secrets

Check if secrets are properly set:

# List secret keys (values are base64 encoded)
kubectl get secret geopulse-secret -o jsonpath='{.data}' | jq 'keys'

# Decode a specific secret (be careful with sensitive data)
kubectl get secret geopulse-secret -o jsonpath='{.data.GEOPULSE_ADMIN_EMAIL}' | base64 -d

For feature-specific configuration details, see:


Support

License

AGPL-3.0 with Non-Commercial Use Restriction