Zum Inhalt springen
Kostenlos in Cloud starten

HitKeep mit Helm auf Kubernetes bereitstellen

Stelle HitKeep mit dem offiziellen Helm-Chart in deinem Kubernetes-Cluster bereit, wenn dein Cluster bereits Helm nutzt. Der Chart erstellt ein StatefulSet, einen ClusterIP-Service, einen Headless-Service für das Clustering, optionalen Ingress und persistenten Speicher für HitKeeps DuckDB-Dateien und lokale Assets.

Verwende das einfache Manifest weiter unten, wenn du Helm nicht nutzt oder alle Kubernetes-Objekte sichtbar in einer Datei haben möchtest.

Der Chart wird als OCI-Artefakt in der GitHub Container Registry veröffentlicht:

Terminal-Fenster
helm show values oci://ghcr.io/pascalebeier/charts/hitkeep --version 2.13.18

Erstelle vor der Installation einen Namespace und speichere das JWT-Signaturgeheimnis:

Terminal-Fenster
kubectl create namespace analytics
kubectl -n analytics create secret generic hitkeep-secrets \
--from-literal=jwt-secret="$(openssl rand -hex 32)"

Speichere Folgendes als hitkeep-values.yaml:

domain: analytics.example.com
ingress:
enabled: true
className: ""
persistence:
enabled: true
size: 10Gi
accessMode: ReadWriteOnce
env:
HITKEEP_PUBLIC_URL: "https://analytics.example.com"
extraEnv:
- name: HITKEEP_JWT_SECRET
valueFrom:
secretKeyRef:
name: hitkeep-secrets
key: jwt-secret

Installiere den Chart:

Terminal-Fenster
helm install hitkeep oci://ghcr.io/pascalebeier/charts/hitkeep --namespace analytics --version 2.13.18 -f hitkeep-values.yaml
kubectl -n analytics rollout status statefulset/hitkeep

Wenn dein Cluster keinen Ingress-Controller besitzt, lasse ingress.enabled auf false und veröffentliche den Service nach dem üblichen Muster deines Clusters.

Prüfe, ob das StatefulSet vollständig ausgerollt und der PersistentVolumeClaim gebunden ist:

Terminal-Fenster
kubectl -n analytics rollout status statefulset/hitkeep
kubectl -n analytics get pods,pvc
kubectl -n analytics port-forward service/hitkeep 8080:80

Prüfe bei laufendem Port-Forward beide Endpunkte in einem weiteren Terminal:

Terminal-Fenster
curl --fail http://localhost:8080/healthz
curl --fail http://localhost:8080/readyz

/healthz bestätigt, dass der Prozess läuft. /readyz bestätigt, dass die gemeinsame Datenbank und jede geöffnete Mandantendatenbank bereit sind.

WertStandardVerwendung
image.repositoryghcr.io/pascalebeier/hitkeepRepository des Container-Images.
image.tagApp-Version des Charts ohne führendes vNur überschreiben, wenn du bewusst ein anderes App-Image als die Chart-Version ausführst.
env.HITKEEP_PUBLIC_URLhttp://localhost:8080Im Browser sichtbare URL für HitKeep. Für den Produktivbetrieb festlegen.
extraEnv[]Aus Secrets bezogene Werte wie HITKEEP_JWT_SECRET, SMTP- oder S3-Zugangsdaten und KI-Provider-Token.
persistence.enabledtrueErstellt eine VolumeClaimTemplate des StatefulSets für HitKeeps Datenverzeichnis.
persistence.mountPath/var/lib/hitkeep/dataPersistenter Mount für die folgenden Chart-Standardwerte.
ingress.enabledfalseErstellt einen Ingress für den Ingress-Controller deines Clusters.
customTrackingDomains.enabledfalseKonfiguriert HitKeeps Laufzeiteinstellungen für eigene Tracking-Domains.
customTrackingDomains.ingress.enabledfalseErstellt einen separaten reinen Tracking-Ingress für statische Tracker-Hostnamen.
service.typeClusterIPNutze LoadBalancer oder NodePort nur, wenn das zu deinem Cluster passt.
replicaCount1Setze den Wert nur dann auf 2 oder höher, wenn du HitKeep-Clustering benötigst.

Der Chart setzt diese Pfade, sofern du sie nicht unter env überschreibst:

UmgebungsvariableChart-Standardwert
HITKEEP_DB_PATH/var/lib/hitkeep/data/hitkeep.db
HITKEEP_DATA_PATH/var/lib/hitkeep/data
HITKEEP_ARCHIVE_PATH/var/lib/hitkeep/data/archive
HITKEEP_SPAM_FILTER_PATH/var/lib/hitkeep/data/spam-filter.json

Mit diesen Standardwerten speichert derselbe PVC die gemeinsame DuckDB-Datenbank, die DuckDB-Datenbanken der Mandanten, QR-Code-Grafiken unter assets/qr-codes, Archivdateien und den optionalen lokalen Spamfilter-Cache.

Der Helm-Chart unterstützt eigene Tracking-Domains, ohne einen Ingress-Controller mitzuliefern. Nutze denselben Dashboard-Ablauf wie bei anderen selbst gehosteten Installationen: Füge die Domain in den Team-Einstellungen hinzu, veröffentliche den TXT-Eigentumsnachweis, richte DNS auf das Ingress-Ziel und verifiziere die Domain, sobald TLS bereit ist.

Nutze für übliche Kubernetes-Ingress-Controller den externen TLS-Modus:

customTrackingDomains:
enabled: true
tlsMode: external
# Empty defaults to the host from env.HITKEEP_PUBLIC_URL.
# Set this when tracker domains point at a separate ingress hostname or IP.
dnsTarget: ""
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: tracker.customer-one.example
- host: tracker.customer-two.example
tls:
- hosts:
- tracker.customer-one.example
- tracker.customer-two.example
secretName: hitkeep-tracking-tls

Der Chart erzeugt einen separaten Tracking-Ingress, der nach dem Helm-Release benannt ist, beispielsweise hitkeep-tracking. Er leitet ausschließlich /hk.js, /hk-vitals.js, /ingest, /ingest/event und /ingest/web-vitals an HitKeep weiter. Dashboard-, API-, Auth-, Share-, MCP-, QR- und SPA-Fallback-Routen gehören nicht zu diesem Ingress. HitKeep setzt die Beschränkung auf Tracking-Hosts zusätzlich intern durch.

Stelle Caddy für On-Demand-TLS separat bereit und speichere das Ask-Token in einem Kubernetes-Secret:

Terminal-Fenster
kubectl -n analytics create secret generic hitkeep-caddy-ask \
--from-literal=token="$(openssl rand -hex 32)"
customTrackingDomains:
enabled: true
tlsMode: caddy-on-demand
caddyAskToken:
existingSecret: hitkeep-caddy-ask
existingSecretKey: token
ingress:
enabled: false

Konfiguriere den externen Caddy-Listener mit ask http://hitkeep.analytics.svc.cluster.local/internal/caddy/on-demand-tls/<token>. Caddy sendet ?domain= an diese URL, bevor es ein Zertifikat ausstellt. HitKeep erlaubt nur aktivierte, per DNS verifizierte eigene Tracking-Domains.

Bewahre deine hitkeep-values.yaml in der Versionsverwaltung oder deinem Bereitstellungssystem auf. Aktualisiere Chart und App-Image gemeinsam, indem du ausschließlich die Chart-Version änderst:

Terminal-Fenster
helm upgrade hitkeep oci://ghcr.io/pascalebeier/charts/hitkeep --namespace analytics --version 2.13.18 -f hitkeep-values.yaml
kubectl -n analytics rollout status statefulset/hitkeep

Lies vor Upgrades über Feature-Releases hinweg die Release Notes, insbesondere wenn du optionale Integrationen wie MCP, KI-Modellkonfiguration, Google Search Console, SMTP oder S3-Backups nutzt.

MCP und KI-gestützte Produktfunktionen bleiben deaktiviert, bis du sie aktivierst und konfigurierst. Nutze env für nicht geheime Einstellungen und extraEnv für Token.

env:
HITKEEP_MCP_ENABLED: "true"
HITKEEP_MCP_PATH: "/mcp"
HITKEEP_MCP_MAX_RANGE_DAYS: "366"
HITKEEP_AI_ENABLED: "true"
HITKEEP_AI_PROVIDER: "openai-compatible"
HITKEEP_AI_MODEL: "opportunities-json"
extraEnv:
- name: HITKEEP_AI_API_KEY
valueFrom:
secretKeyRef:
name: hitkeep-ai
key: api-key

Lies vor der Aktivierung im Produktivbetrieb Offizieller MCP-Server und KI-Modellkonfiguration.

Nutze dieses Manifest, wenn du Helm nicht verwendest. Die HitKeep-Laufzeitpfade entsprechen denen des offiziellen Charts.

Speichere es als hitkeep.yaml:

apiVersion: v1
kind: Namespace
metadata:
name: analytics
---
apiVersion: v1
kind: Secret
metadata:
name: hitkeep-secrets
namespace: analytics
type: Opaque
stringData:
jwt-secret: "change-me-to-a-long-random-string"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: hitkeep-data
namespace: analytics
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
name: hitkeep-headless
namespace: analytics
spec:
clusterIP: None
publishNotReadyAddresses: true
selector:
app: hitkeep
ports:
- name: gossip-tcp
port: 7946
protocol: TCP
targetPort: gossip-tcp
- name: gossip-udp
port: 7946
protocol: UDP
targetPort: gossip-udp
---
apiVersion: v1
kind: Service
metadata:
name: hitkeep
namespace: analytics
spec:
type: ClusterIP
selector:
app: hitkeep
ports:
- name: http
protocol: TCP
port: 80
targetPort: http
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: hitkeep
namespace: analytics
spec:
serviceName: hitkeep-headless
replicas: 1
selector:
matchLabels:
app: hitkeep
template:
metadata:
labels:
app: hitkeep
spec:
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
fsGroup: 65532
containers:
- name: hitkeep
image: ghcr.io/pascalebeier/hitkeep:2.13.18
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
name: http
- containerPort: 7946
name: gossip-tcp
protocol: TCP
- containerPort: 7946
name: gossip-udp
protocol: UDP
env:
- name: HITKEEP_HTTP_ADDR
value: ":8080"
- name: HITKEEP_PUBLIC_URL
value: "https://analytics.example.com"
- name: HITKEEP_DB_PATH
value: "/var/lib/hitkeep/data/hitkeep.db"
- name: HITKEEP_DATA_PATH
value: "/var/lib/hitkeep/data"
- name: HITKEEP_ARCHIVE_PATH
value: "/var/lib/hitkeep/data/archive"
- name: HITKEEP_SPAM_FILTER_PATH
value: "/var/lib/hitkeep/data/spam-filter.json"
- name: HITKEEP_JWT_SECRET
valueFrom:
secretKeyRef:
name: hitkeep-secrets
key: jwt-secret
volumeMounts:
- mountPath: /var/lib/hitkeep/data
name: data
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /readyz
port: http
initialDelaySeconds: 5
periodSeconds: 10
volumes:
- name: data
persistentVolumeClaim:
claimName: hitkeep-data

Wende es an:

Terminal-Fenster
kubectl apply -f hitkeep.yaml
kubectl -n analytics rollout status statefulset/hitkeep

Ergänze deine Ingress-Ressource oder einen externen Service passend zum üblichen Ingress-Controller- oder Load-Balancer-Aufbau deines Clusters.

HitKeep stellt zwei Prüfendpunkte für Kubernetes bereit:

EndpunktZweck
GET /healthzLiveness: Der Prozess läuft.
GET /readyzReadiness: Die gemeinsame Datenbank und jede aktuell geöffnete Mandantendatenbank sind fehlerfrei.

Diese Endpunkte bleiben am lokalen Root verfügbar, auch wenn HITKEEP_PUBLIC_URL ein Pfadpräfix enthält.

Während einer Datenbankwiederherstellung bleibt /healthz erfolgreich, damit der Kubelet keinen Prozess beendet, der gerade sichere Wiederherstellungsarbeiten ausführt. /readyz antwortet mit 503, Retry-After: 5 und einem JSON-Grund wie database_recovering oder database_needs_attention. Dadurch wird der Pod aus dem Service genommen, bis die Datenbank wieder fehlerfrei ist.

Wenn dein Cluster einen Ingress-Controller wie nginx-ingress, Traefik oder AWS ALB nutzt, konfiguriere vertrauenswürdige Proxy-CIDRs, damit echte Client-IPs für Webanalyse und Ratenbegrenzung verwendet werden:

env:
HITKEEP_TRUSTED_PROXIES: "10.0.0.0/8"

Weitere Einzelheiten findest du unter Vertrauenswürdige Proxys.

Sichere den vollständigen persistenten Datenpfad. Bei den Standardwerten des Helm-Charts und des einfachen Manifests oben sind diese Laufzeitpfade wichtig:

  • /var/lib/hitkeep/data/hitkeep.db für gemeinsame Control-Plane-Daten
  • /var/lib/hitkeep/data/tenants/*/hitkeep.db für Analysedaten von Teams außerhalb des Standardmandanten
  • /var/lib/hitkeep/data/assets/qr-codes/* für QR-Code-Grafiken
  • /var/lib/hitkeep/data/archive für lokale Archivartefakte
  • /var/lib/hitkeep/data/backups, wenn du HITKEEP_BACKUP_PATH auf diesen lokalen Pfad setzt
  • /var/lib/hitkeep/data/recovery für zugriffsgeschützte automatische Wiederherstellungspakete und fortsetzbare Marker, sofern du HITKEEP_DB_RECOVERY_PATH nicht überschreibst

Aktiviere lokale automatische Backup-Snapshots auf demselben PVC so:

env:
HITKEEP_BACKUP_PATH: "/var/lib/hitkeep/data/backups"
HITKEEP_BACKUP_INTERVAL: "60"
HITKEEP_BACKUP_RETENTION: "24"

Nutze für Backup-Speicher außerhalb des Clusters einen s3://-Wert für HITKEEP_BACKUP_PATH und konfiguriere die S3-Umgebungsvariablen in einem Kubernetes-Secret. Beispiele findest du unter S3-Backups.

Du betreibst HitKeep auf Kubernetes, möchtest aber StatefulSets, PVCs und Cluster-Upgrades nicht selbst verwalten? HitKeep Cloud übernimmt die Infrastruktur in der von dir gewählten verwalteten Region.