A Helm chart for deploying Inngest on Kubernetes clusters.
- Kubernetes 1.20+
- Helm 3.0+
- Persistent Volume support for PostgreSQL and Redis
- LoadBalancer or Ingress controller for external access (optional)
- Storage class supporting ReadWriteOnce volumes
- KEDA operator (optional, for autoscaling)
# Install latest version
helm install inngest oci://ghcr.io/inngest/inngest-helm/inngest \
--set inngest.signingKey="your-signing-key" \
--set inngest.eventKey="your-event-key" \
--create-namespace
# Install specific version
helm install inngest oci://ghcr.io/inngest/inngest-helm/inngest --version 0.2.0 \
--set inngest.signingKey="your-signing-key" \
--set inngest.eventKey="your-event-key" \
--create-namespace
# Install with custom values file
helm install inngest oci://ghcr.io/inngest/inngest-helm/inngest -f my-values.yaml --create-namespaceTip
Review the self-hosting documentation for configuration options including valid signing key generation.
Deploy Inngest with bundled PostgreSQL and Redis instances:
# Install with required secrets (creates "inngest" namespace automatically)
helm install inngest . \
--set inngest.eventKey="your_event_key_here" \
--set inngest.signingKey="your_signing_key_here" \
--create-namespaceImportant: The eventKey and signingKey are required and must be hexadecimal strings for Inngest to function properly.
Create a my-values.yaml file:
inngest:
eventKey: "your_event_key_here" # Must be a hexadecimal string
signingKey: "your_signing_key_here" # Must be a hexadecimal string
# Customize resource limits (defaults: requests cpu=500m, memory=1Gi)
resources:
limits:
cpu: 2000m
memory: 2Gi
requests:
cpu: 500m
memory: 1Gi
# Adjust liveness/readiness probes for slower database connections
livenessProbe:
initialDelaySeconds: 90
readinessProbe:
initialDelaySeconds: 90Install with custom values:
helm install inngest . -f my-values.yaml --create-namespaceKey Concept: This chart uses consistent resource naming regardless of your chosen Helm release name.
- Helm Release Name: The first argument (
inngest) is your chosen release name for Helm tracking - Kubernetes Resource Names: Always consistent:
inngest,inngest-postgresql,inngest-redis
Examples:
# All of these create identical Kubernetes resource names
helm install my-production-inngest . --create-namespace
helm install dev-environment . --create-namespace
helm install company-inngest . --create-namespace
# All result in the same resources:
# - service/inngest
# - deployment/inngest
# - configmap/inngest
# - service/inngest-postgresql
# - service/inngest-redisBenefits:
- Consistent resource names across all environments
- Documentation examples work for everyone
- Scripts and automation can rely on predictable names
- Easy to reference services from applications
This is the simplest setup with bundled dependencies:
# values-internal.yaml
inngest:
eventKey: "your_event_key_here" # Must be a hexadecimal string
signingKey: "your_signing_key_here" # Must be a hexadecimal string
# Internal PostgreSQL (enabled by default)
postgresql:
enabled: true
auth:
database: inngest
username: inngest
password: secure_password
persistence:
enabled: true
size: 20Gi
# Internal Redis (enabled by default)
redis:
enabled: true
persistence:
enabled: true
size: 8Gi
# Resource limits (defaults: requests cpu=500m, memory=1Gi)
resources:
limits:
cpu: 2000m
memory: 2Gi
requests:
cpu: 1000m
memory: 1GiDeploy:
helm install inngest . -f values-internal.yaml --create-namespaceFor production deployments with external managed databases:
# values-external.yaml
inngest:
eventKey: "your_event_key_here" # Must be a hexadecimal string
signingKey: "your_signing_key_here" # Must be a hexadecimal string
postgres:
uri: "postgres://username:password@postgres.example.com:5432/inngest"
redis:
uri: "redis://redis.example.com:6379"
# Disable internal dependencies
postgresql:
enabled: false
redis:
enabled: falseDeploy:
helm install inngest-prod . -f values-external.yaml --create-namespaceEnable KEDA-based autoscaling using Prometheus metrics from Inngest:
Important: KEDA scaling uses a Prometheus sidecar container to scrape the Inngest /metrics endpoint. The sidecar handles Bearer token authentication using the signingKey, and KEDA queries the sidecar's Prometheus API for scaling decisions based on inngest_queue_depth.
# values-keda.yaml
inngest:
eventKey: "your_event_key_here" # Must be a hexadecimal string
signingKey: "your_signing_key_here" # Must be a hexadecimal string
# Enable KEDA autoscaling
keda:
enabled: true
minReplicas: 2
maxReplicas: 20
pollingInterval: 30
cooldownPeriod: 300
triggers:
- type: prometheus
metadata:
metricName: inngest_queue_depth
threshold: "10"
query: inngest_queue_depthDeploy with KEDA:
# Install KEDA using Helm (recommended method)
helm repo add kedacore https://kedacore.github.io/charts
helm repo update
helm install keda kedacore/keda --namespace keda-system --create-namespace
# Verify KEDA installation
kubectl get pods -n keda-system
# Deploy Inngest with KEDA
helm install inngest . -f values-keda.yaml --create-namespaceAlternative KEDA Installation Methods:
# Method 1: Using kubectl (if Helm method fails)
kubectl apply --server-side -f https://github.com/kedacore/keda/releases/download/v2.12.0/keda-2.12.0.yaml
# Method 2: Using specific version via Helm
helm install keda kedacore/keda --version 2.12.0 --namespace keda-system --create-namespaceA comprehensive production setup with external dependencies, ingress, and monitoring:
# values-production.yaml
replicaCount: 3
inngest:
eventKey: "your_production_event_key" # Must be a hexadecimal string
signingKey: "your_production_signing_key" # Must be a hexadecimal string
logLevel: "info"
queueWorkers: 200
postgres:
uri: "postgres://inngest:secure_password@postgres-prod.example.com:5432/inngest"
redis:
uri: "redis://redis-prod.example.com:6379"
# Use external managed databases
postgresql:
enabled: false
redis:
enabled: false
# Liveness/readiness probes - increase for external databases that take longer to initialize
livenessProbe:
initialDelaySeconds: 120
readinessProbe:
initialDelaySeconds: 120
# External access (configure ingress separately if needed)
# KEDA autoscaling configuration
keda:
enabled: true
minReplicas: 3
maxReplicas: 50
triggers:
- type: prometheus
metadata:
metricName: inngest_queue_depth
threshold: "10"
query: inngest_queue_depth
# Resource limits
resources:
limits:
cpu: 2000m
memory: 4Gi
requests:
cpu: 1000m
memory: 2Gi
# Security context
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
podSecurityContext:
fsGroup: 1000
# Network policy for security
networkPolicy:
enabled: trueDeploy production setup:
helm install inngest-prod . -f values-production.yaml --create-namespaceA complete ingress setup using internal PostgreSQL and Redis for external access:
Security Warning: When exposing Inngest through ingress, the web UI and GraphQL endpoints are publicly accessible unless protected. This example disables the UI for security. If you need the UI, implement proper authentication/authorization at the ingress level.
# values-ingress.yaml
inngest:
eventKey: "your_event_key_here" # Must be a hexadecimal string
signingKey: "your_signing_key_here" # Must be a hexadecimal string
logLevel: "info"
queueWorkers: 150
noUI: true # Disable UI for security when exposed via ingress
# Internal PostgreSQL (enabled by default)
postgresql:
enabled: true
auth:
database: inngest
username: inngest
password: secure_password
persistence:
enabled: true
size: 30Gi
# Internal Redis (enabled by default)
redis:
enabled: true
persistence:
enabled: true
size: 10Gi
# Liveness/readiness probes - increase for external databases that take longer to initialize
livenessProbe:
initialDelaySeconds: 90
readinessProbe:
initialDelaySeconds: 90
# Ingress configuration for external access
ingress:
enabled: true
className: "nginx" # Use your ingress controller class
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
cert-manager.io/cluster-issuer: "letsencrypt-prod" # Required for automatic Let's Encrypt certificates
hosts:
- host: inngest.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: inngest-tls
hosts:
- inngest.example.com
# Security context
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
# Network policy for additional security (optional)
networkPolicy:
enabled: trueDeploy with ingress:
helm install inngest . -f values-ingress.yaml --create-namespaceAccess URLs after deployment:
- Event API:
https://inngest.example.com - API Endpoints:
https://inngest.example.com/api/* - Connect Gateway:
https://inngest.example.com:8289 - UI Dashboard: Disabled for security (noUI: true)
Note: Replace inngest.example.com with your actual domain and ensure DNS points to your ingress controller.
For automatic SSL certificate provisioning using Let's Encrypt, you need to install and configure cert-manager.
- Ingress Controller: You need an ingress controller (like nginx-ingress) already installed
- cert-manager: For automatic SSL certificate management
- Valid Domain: A domain name pointing to your ingress controller's IP address
Install cert-manager using Helm (recommended):
# Add the cert-manager repository
helm repo add jetstack https://charts.jetstack.io
helm repo update
# Install cert-manager with CRDs
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set installCRDs=trueVerify cert-manager installation:
kubectl get pods -n cert-managerCreate a ClusterIssuer for Let's Encrypt certificate provisioning:
# letsencrypt-clusterissuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: your-email@example.com # CHANGE THIS TO YOUR EMAIL
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: your-email@example.com # CHANGE THIS TO YOUR EMAIL
privateKeySecretRef:
name: letsencrypt-staging
solvers:
- http01:
ingress:
class: nginxApply the ClusterIssuer:
# Update the email address in the file first
kubectl apply -f letsencrypt-clusterissuer.yamlHere's a complete example with automatic SSL certificate provisioning:
# values-https-ingress.yaml
inngest:
eventKey: "your_event_key_here" # Must be a hexadecimal string
signingKey: "your_signing_key_here" # Must be a hexadecimal string
noUI: true # Disable UI for security when exposed via ingress
# Ingress configuration with Let's Encrypt
ingress:
enabled: true
className: "nginx"
annotations:
# Let's Encrypt annotations for automatic SSL certificate provisioning
cert-manager.io/cluster-issuer: "letsencrypt-prod"
# Use letsencrypt-staging for testing, letsencrypt-prod for production
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
hosts:
- host: inngest.yourdomain.com # CHANGE TO YOUR DOMAIN
paths:
- path: /
pathType: Prefix
tls:
- secretName: inngest-tls
hosts:
- inngest.yourdomain.com # CHANGE TO YOUR DOMAINDeploy with HTTPS:
helm install inngest . -f values-https-ingress.yaml --create-namespaceCheck certificate status:
# Check certificate resource
kubectl get certificates -n inngest
kubectl describe certificate inngest-tls -n inngest
# Check certificate request
kubectl get certificaterequests -n inngest
# Check cert-manager logs
kubectl logs -n cert-manager deployment/cert-managerCommon Issues:
-
Certificate shows "False" for READY: Usually DNS or HTTP-01 challenge issues
- Verify your domain points to the ingress controller IP
- Check ingress controller logs
- Ensure port 80 is accessible for Let's Encrypt validation
-
Rate limiting from Let's Encrypt: Use
letsencrypt-stagingissuer for testing -
DNS propagation delays: Wait for DNS changes to propagate globally
Testing with Staging:
For testing, use the staging ClusterIssuer to avoid rate limits:
ingress:
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-staging" # Use staging for testingOnce working, switch to letsencrypt-prod for the trusted certificate.
The chart creates and manages its own namespace by default. You can customize this behavior:
# Uses default "inngest" namespace
helm install inngest . --create-namespace# values-custom-namespace.yaml
namespace:
create: true
name: "my-inngest-ns"
inngest:
eventKey: "your_event_key_here" # Must be a hexadecimal string
signingKey: "your_signing_key_here" # Must be a hexadecimal stringhelm install inngest . -f values-custom-namespace.yaml --create-namespace# values-existing-namespace.yaml
namespace:
create: false
name: "existing-namespace"
inngest:
eventKey: "your_event_key_here" # Must be a hexadecimal string
signingKey: "your_signing_key_here" # Must be a hexadecimal string# Create namespace first if it doesn't exist
kubectl create namespace existing-namespace
helm install inngest . -f values-existing-namespace.yamlNote: When using an existing namespace, don't use --create-namespace flag.
After deployment, you can access Inngest through:
Resource Names: Regardless of your chosen release name, the Kubernetes resources are always named:
- Main service:
inngest - PostgreSQL:
inngest-postgresql - Redis:
inngest-redis
kubectl port-forward svc/inngest 8288:8288 -n inngest
# Access UI at http://localhost:8288Configure your applications to send events to:
- Internal:
http://inngest:8288(within cluster) - External:
https://inngest.example.com(see ingress example above)
kubectl get pods -l app.kubernetes.io/name=inngest -n inngestkubectl logs -l app.kubernetes.io/name=inngest -f -n inngestkubectl get configmap inngest -o yaml -n inngest
kubectl get secret inngest -o yaml -n inngestkubectl get scaledobject -n inngest
kubectl describe scaledobject inngest -n inngestCheck for resource constraints or storage issues:
kubectl describe pods -l app.kubernetes.io/name=inngest -n inngest
kubectl get pvc -n inngest
kubectl get storageclasshelm upgrade inngest . -f your-values.yaml -n inngestNote: Always specify the namespace when upgrading to ensure Helm finds the correct release.
Inngest handles database migrations automatically on startup. Ensure you have backups before upgrading.
helm uninstall inngest -n inngestNote: This will not delete persistent volumes. To delete all data:
kubectl delete pvc -l app.kubernetes.io/name=inngest -n inngest| Parameter | Description | Default | Required |
|---|---|---|---|
namespace.create |
Create namespace | true |
No |
namespace.name |
Namespace name | "inngest" |
No |
inngest.eventKey |
Event key for sending events | "" |
Yes |
inngest.signingKey |
Signing key for validation | "" |
Yes |
postgresql.enabled |
Enable bundled PostgreSQL | true |
No |
redis.enabled |
Enable bundled Redis | true |
No |
ingress.enabled |
Enable ingress | false |
No |
keda.enabled |
Enable KEDA autoscaling | false |
No |
Security: This chart implements security best practices by default:
- Non-root user execution (UID 1000 for Inngest, 999 for PostgreSQL/Redis)
- Read-only root filesystem with temporary volumes for writable directories
- Dropped capabilities and disabled privilege escalation
- Database credentials stored in Kubernetes Secrets (not plain text)
- Network policies available for additional isolation
Resource Names: All Kubernetes resources use consistent names regardless of Helm release name:
- Main application:
inngest(service, deployment, configmap, secret) - PostgreSQL:
inngest-postgresql(service, deployment, pvc) - Redis:
inngest-redis(service, deployment, pvc)
For a complete list of configuration options, see values.yaml.