# Trigger.dev v4 Helm Chart This Helm chart deploys Trigger.dev v4 self-hosting stack to Kubernetes. ## Quick Start ### Prerequisites ```bash # Build Helm dependencies (required for Bitnami charts) helm dependency build # Extract dependency charts for local template testing for file in ./charts/*.tgz; do echo "Extracting $file"; tar -xzf "$file" -C ./charts; done # Alternative: Use --dependency-update flag for template testing helm template trigger . --dependency-update ``` ### Installation ```bash # Deploy with default values (testing/development only) helm install trigger . # Deploy to specific namespace helm install trigger . -n trigger --create-namespace # Deploy with custom values for production helm install trigger . -f values-production.yaml -n trigger --create-namespace ``` ### Upgrading ```bash # Upgrade existing release helm upgrade trigger . # Upgrade with new values helm upgrade trigger . -f values-production.yaml ``` ### Access the dashboard ```bash kubectl port-forward svc/trigger-webapp 3040:3030 --address 0.0.0.0 ``` Dashboard: http://localhost:3040/ ### Deploying your tasks ```bash # The --push arg is required when testing locally npx trigger.dev@latest deploy --push ``` ## ⚠️ Security Requirements ### Secrets Configuration **IMPORTANT**: The default secrets are for **TESTING ONLY** and must be changed for production. #### Required Secrets All secrets must be exactly **32 hexadecimal characters** (16 bytes): - `sessionSecret` - User authentication sessions - `magicLinkSecret` - Passwordless login tokens - `encryptionKey` - Sensitive data encryption - `managedWorkerSecret` - Worker authentication #### Generate Production Secrets ```bash for i in {1..4}; do openssl rand -hex 16; done ``` #### Configure Production Secrets ```yaml # values-production.yaml secrets: sessionSecret: "your-generated-secret-1" magicLinkSecret: "your-generated-secret-2" encryptionKey: "your-generated-secret-3" managedWorkerSecret: "your-generated-secret-4" objectStore: accessKeyId: "your-s3-access-key" secretAccessKey: "your-s3-secret-key" ``` ## Architecture This chart deploys the following components: ### Core Services - **Webapp** - Main Trigger.dev application (port 3030) - **PostgreSQL** - Primary database with logical replication - **Redis** - Cache and job queue - **Electric** - Real-time sync service (ElectricSQL) ### Worker Services - **Supervisor** - Kubernetes worker orchestrator for executing runs ### Supporting Services - **ClickHouse** - Analytics database - **MinIO** - S3-compatible object storage - **Registry** - Private Docker registry for deployed code (EXPERIMENTAL - disabled by default) ## Configuration ### Basic Configuration ```yaml webapp: # Application URLs appOrigin: "https://trigger.example.com" loginOrigin: "https://trigger.example.com" apiOrigin: "https://trigger.example.com" # Bootstrap mode (auto-creates worker group) bootstrap: enabled: true # Enable for combined setups workerGroupName: "bootstrap" ``` ### External Services Use external managed services instead of bundled components: ```yaml # External PostgreSQL postgres: deploy: false external: host: "your-postgres.rds.amazonaws.com" port: 5432 database: "trigger" username: "trigger_user" password: "your-password" # External Redis redis: deploy: false external: host: "your-redis.cache.amazonaws.com" port: 6379 password: "your-password" # External Docker Registry (e.g., Kind local registry) registry: deploy: true external: host: "localhost" port: 5001 username: "" password: "" ``` ### Ingress Configuration ```yaml # Webapp ingress webapp: ingress: enabled: true className: "nginx" annotations: cert-manager.io/cluster-issuer: "letsencrypt-prod" hosts: - host: trigger.example.com paths: - path: / pathType: Prefix tls: - secretName: trigger-tls hosts: - trigger.example.com # Registry ingress registry: ingress: enabled: true className: "nginx" annotations: cert-manager.io/cluster-issuer: "letsencrypt-prod" hosts: - host: registry.example.com paths: - path: / pathType: Prefix tls: - secretName: registry-tls hosts: - registry.example.com ``` ### Resource Configuration ```yaml resources: webapp: limits: cpu: 2000m memory: 4Gi requests: cpu: 1000m memory: 2Gi postgres: primary: resources: limits: cpu: 1000m memory: 2Gi ``` ## Deployment Modes ### Testing/Development - Use default values - Single replica - Lower resource limits - Bootstrap mode enabled ### Production - Custom secrets (required) - Multiple replicas with anti-affinity - Production resource limits - External services recommended - Ingress with TLS - Persistent storage ## Persistence All services support persistent storage and allow you to control the storage class globally or per service. Our internal services (Registry) now support the full Bitnami persistence configuration pattern: ### Basic Persistence Configuration ```yaml global: storageClass: "fast-ssd" # Default for all services # Bitnami chart services (simplified configuration) postgres: primary: persistence: enabled: true size: 10Gi storageClass: "postgres-nvme" # Optional: override for PostgreSQL redis: master: persistence: enabled: true size: 5Gi storageClass: "redis-ssd" # Optional: override for Redis clickhouse: persistence: enabled: true size: 10Gi storageClass: "analytics-hdd" # Optional: override for ClickHouse s3: persistence: enabled: true size: 10Gi storageClass: "objectstore-ssd" # Optional: override for S3 ``` ### Internal Services - Full Bitnami-Style Configuration Our internal services (Registry) support the complete Bitnami persistence configuration pattern: ```yaml # Registry - Full persistence configuration options registry: persistence: enabled: true # Name to assign the volume volumeName: "data" # Name of an existing PVC to use existingClaim: "" # The path the volume will be mounted at mountPath: "/var/lib/registry" # The subdirectory of the volume to mount to subPath: "" # PVC Storage Class for Registry data volume storageClass: "registry-ssd" # PVC Access Mode for Registry volume accessModes: - "ReadWriteOnce" # PVC Storage Request for Registry volume size: 10Gi # Annotations for the PVC annotations: backup.velero.io/backup-volumes: "data" # Labels for the PVC labels: app.kubernetes.io/component: "storage" # Selector to match an existing Persistent Volume selector: matchLabels: tier: "registry" # Custom PVC data source dataSource: name: "registry-snapshot" kind: "VolumeSnapshot" apiGroup: "snapshot.storage.k8s.io" # Shared persistent volume for worker token file persistence: shared: enabled: true size: 5Mi accessMode: ReadWriteOnce # accessMode: ReadWriteMany # Use for cross-node deployment storageClass: "" retain: true # Prevents deletion on uninstall ``` ### Persistence Configuration Rules - **Service-level storageClass** overrides the global value for that service only - **Global storageClass** applies to all services that don't specify their own - **Cluster default** is used if neither global nor service-level storageClass is set - **Internal services** (Registry) support full Bitnami-style configuration - **Bitnami chart services** use their respective chart's configuration patterns ## Monitoring ### Health Checks Health checks are configured for all services: - HTTP endpoints for web services - Database connection tests - Readiness and liveness probes ### Health Probe Configuration All non-Bitnami services support configurable health probes: ```yaml # Webapp health probes webapp: livenessProbe: enabled: true initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 5 failureThreshold: 5 successThreshold: 1 readinessProbe: enabled: true initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 1 failureThreshold: 5 successThreshold: 1 startupProbe: enabled: false initialDelaySeconds: 0 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 60 successThreshold: 1 # Supervisor health probes supervisor: livenessProbe: enabled: true initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 5 failureThreshold: 5 successThreshold: 1 readinessProbe: enabled: true initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 1 failureThreshold: 5 successThreshold: 1 startupProbe: enabled: false initialDelaySeconds: 0 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 60 successThreshold: 1 # Electric health probes electric: livenessProbe: enabled: true initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 5 failureThreshold: 5 successThreshold: 1 readinessProbe: enabled: true initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 1 failureThreshold: 5 successThreshold: 1 startupProbe: enabled: false initialDelaySeconds: 0 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 60 successThreshold: 1 # Registry health probes registry: livenessProbe: enabled: true initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 5 failureThreshold: 5 successThreshold: 1 readinessProbe: enabled: true initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 1 failureThreshold: 5 successThreshold: 1 startupProbe: enabled: false initialDelaySeconds: 0 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 60 successThreshold: 1 ``` ### Prometheus Integration ServiceMonitors are available for webapp and supervisor services: ```yaml webapp: serviceMonitor: enabled: true interval: "30s" path: "/metrics" labels: release: prometheus-stack supervisor: serviceMonitor: enabled: true interval: "30s" path: "/metrics" labels: release: prometheus-stack ``` ## Operations ### Force Pod Restart When you need to force all pods to restart (e.g., to pick up updated secrets or config): ```bash # Force restart using timestamp annotation (Helm-native approach) helm upgrade . --set-string podAnnotations.restartedAt="$(date +%s)" # Example helm upgrade trigger . --set-string podAnnotations.restartedAt="$(date +%s)" ``` This approach: - ✅ Uses Helm's built-in annotation mechanism - ✅ Safe - doesn't recreate immutable resources like PVCs - ✅ Targeted - only restarts pods that need updates - ✅ Trackable - increments Helm revision number ### Configuration Updates After changing secrets or ConfigMaps in your values file: ```bash # 1. Upgrade with new values helm upgrade trigger . -f values-production.yaml # 2. Force pod restart to pick up changes helm upgrade trigger . -f values-production.yaml \ --set-string podAnnotations.restartedAt="$(date +%s)" ``` ## Troubleshooting ### Check Pod Status ```bash kubectl get pods -l app.kubernetes.io/name=trigger.dev ``` ### View Logs ```bash # Webapp logs kubectl logs -l app.kubernetes.io/component=webapp # Database logs kubectl logs -l app.kubernetes.io/component=postgres ``` ### Run Tests ```bash helm test trigger.dev ``` ## Testing ### Validate Deployment ```bash # Check Helm template syntax helm template trigger.dev . --dry-run > /dev/null && echo "Template validation successful" # Test webapp health endpoint (requires port forwarding) curl -s -o /dev/null -w "%{http_code}" http://localhost:3040/healthcheck || echo "Connection failed" # Port forward to access webapp locally kubectl port-forward svc/trigger.dev-webapp 3040:3030 --address 0.0.0.0 ``` ### Common Issues 1. **Secrets errors**: Ensure all secrets are exactly 32 hex characters 2. **Database connection**: Check PostgreSQL is ready before webapp starts 3. **Resource limits**: Increase limits for ClickHouse in constrained environments 4. **Config not applying**: Use the pod restart technique above to force config reload 5. **Image pull errors**: When testing locally, deploy with `npx trigger.dev@latest deploy --push` ## Examples See `values-production-example.yaml` for a complete production configuration example. ## Version Management ### Understanding Versions The Helm chart uses three types of versions: 1. **Chart Version** (`Chart.yaml:version`) - Helm chart packaging version 2. **App Version** (`Chart.yaml:appVersion`) - Trigger.dev application version 3. **Component Versions** (`values.yaml`) - Individual service versions (Electric, ClickHouse, etc.) ### Release Process #### For Chart Maintainers 1. **Update Chart Version** for chart changes: ```bash # Edit Chart.yaml version: 4.1.0 # Increment for chart changes (semver) ``` 2. **Update App Version** when Trigger.dev releases new version: ```bash # Edit Chart.yaml appVersion: "v4.1.0" # Match Trigger.dev release (v-prefixed image tag) ``` 3. **Release via GitHub**: ```bash # Tag and push git tag helm-v4.1.0 git push origin helm-v4.1.0 # GitHub Actions will automatically build and publish to GHCR ``` #### For Users ```bash # Install specific chart version helm upgrade --install trigger \ oci://ghcr.io/triggerdotdev/charts/trigger.dev \ --version 4.1.0 # Install latest chart version helm upgrade --install trigger \ oci://ghcr.io/triggerdotdev/charts/trigger.dev # Override app version (advanced) helm upgrade --install trigger . \ --set webapp.image.tag=v4.0.1 ``` ## Production Readiness Checklist ### 🔒 Security (REQUIRED) - [ ] **Generate unique secrets** (never use defaults): ```bash # Generate 4 secrets for i in {1..4}; do openssl rand -hex 16; done ``` - [ ] **Configure security contexts**: ```yaml webapp: podSecurityContext: fsGroup: 1000 securityContext: runAsNonRoot: true runAsUser: 1000 allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: [ALL] ``` - [ ] **Enable network policies** (if supported by cluster) - [ ] **Configure proper RBAC** for supervisor - [ ] **Use TLS ingress** with cert-manager ### 📊 Resource Management (REQUIRED) - [ ] **Set resource limits and requests** - for example: ```yaml webapp: resources: limits: cpu: 2000m memory: 4Gi requests: cpu: 1000m memory: 2Gi postgres: primary: resources: limits: cpu: 1000m memory: 2Gi requests: cpu: 500m memory: 1Gi redis: master: resources: limits: cpu: 500m memory: 1Gi requests: cpu: 250m memory: 512Mi # ClickHouse can be very resource intensive, so we recommend setting limits and requests accordingly # Note: not doing this can cause OOM crashes which will cause issues across many different features clickhouse: resources: limits: cpu: 4000m memory: 16Gi requests: cpu: 2000m memory: 8Gi supervisor: resources: limits: cpu: 500m memory: 1Gi requests: cpu: 250m memory: 512Mi ``` - [ ] **Configure persistent storage for all services** - for example: ```yaml global: storageClass: "fast-nvme" # Default for all services postgres: primary: persistence: size: 500Gi redis: master: persistence: size: 20Gi clickhouse: persistence: size: 100Gi s3: persistence: size: 200Gi # Internal services support full Bitnami-style configuration registry: persistence: enabled: true size: 100Gi storageClass: "registry-ssd" annotations: backup.velero.io/backup-volumes: "data" ``` ### 🏗️ High Availability (RECOMMENDED) - [ ] **Multiple replicas** with pod anti-affinity - [ ] **Pod disruption budgets** - [ ] **External managed services** (RDS, ElastiCache, etc.) - [ ] **Multi-AZ storage classes** - [ ] **Backup strategies** for databases ### 📈 Monitoring (RECOMMENDED) - [ ] **Enable ServiceMonitors** for Prometheus - [ ] **Configure alerting** for critical services - [ ] **Set up log aggregation** - [ ] **Monitor resource usage** and adjust limits ### 🚀 Performance (OPTIONAL) - [ ] **Horizontal Pod Autoscaler** for webapp - [ ] **Vertical Pod Autoscaler** for data services - [ ] **Node affinity** for data services - [ ] **Separate storage classes** for different workloads ## Support - Documentation: https://trigger.dev/docs/self-hosting - GitHub Issues: https://github.com/triggerdotdev/trigger.dev/issues - Discord: https://discord.gg/untWVke9aH