n8n Kubernetes Deployment: Helm Chart Production Guide

I deploy n8n on Kubernetes because containers alone don’t scale. When your workflows hit 1,000+ executions per day, you need autoscaling, rolling updates, and self-healing. Here’s how I do it with Helm charts.

Docker got me started. But when my n8n instance started processing hundreds of workflows across time zones, I hit limits. One pod crashes and everything stops. No horizontal scaling. No graceful updates. That’s when I moved to Kubernetes.

This guide covers what I actually use in production — not theory, not tutorials. Real configurations that handle real traffic.

Which Helm Chart for n8n?

Two options dominate. The official n8n Helm chart from the n8n team, and the community-maintained Bitnami chart. I picked the official chart for one reason: it tracks n8n releases faster.

The Bitnami chart is solid and well-maintained. But when n8n drops a new version, the official chart updates within hours. Bitnami typically takes 1-2 days. For a production system where I’m pushing workflows daily, that gap matters.

Install Helm first if you haven’t:

`bash
curl https://baltocdn.com/helm/install.sh | sh
`

Add the n8n repository:

`bash
helm repo add n8n https://n8nio.github.io/k8s-Helm/
helm repo update
`

Verify the chart is available:

`bash
helm search repo n8n
`

You should see the n8n chart listed with its current version. That version number tracks the n8n release cycle closely.

Production Configuration That Actually Works

A default Helm install works for testing. It fails in production. Here’s my values.yaml that handles real workloads:

`yaml
replicaCount: 3

image:
repository: n8nio/n8n
tag: “latest”
pullPolicy: IfNotPresent

ingress:
enabled: true
className: nginx
annotations:
kubernetes.io/tls-acme: “true”
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
– host: n8n.yourdomain.com
paths:
– path: /
pathType: Prefix
tls:
– secretName: n8n-tls
hosts:
– n8n.yourdomain.com

resources:
limits:
cpu: 2000m
memory: 4Gi
requests:
cpu: 500m
memory: 1Gi

persistence:
enabled: true
storageClass: “standard”
size: 50Gi

env:
– name: N8N_ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: n8n-secrets
key: encryption-key
– name: DB_TYPE
value: “postgresdb”
– name: DB_POSTGRESDB_HOST
valueFrom:
secretKeyRef:
name: n8n-secrets
key: db-host
– name: DB_POSTGRESDB_USER
valueFrom:
secretKeyRef:
name: n8n-secrets
key: db-user
– name: DB_POSTGRESDB_PASSWORD
valueFrom:
secretKeyRef:
name: n8n-secrets
key: db-password

secretKeyRef:
enabled: true
`

Three things matter most here. First, three replicas minimum. Second, persistent storage backed by a proper storage class — not emptyDir. Third, all secrets managed through Kubernetes secrets, not environment variable strings in your YAML.

The encryption key is critical. Rotate it if you move environments. n8n uses it to encrypt credentials stored in the database. Change the key and every credential becomes unreadable.

I store my encryption key and database credentials in a separate secrets manifest:

`yaml
apiVersion: v1
kind: Secret
metadata:
name: n8n-secrets
type: Opaque
stringData:
encryption-key: “$(openssl rand -hex 32)”
db-host: “postgres-service.default.svc.cluster.local”
db-user: “n8n”
db-password: “$(openssl rand -base64 32)”
`

Generate those values once. Back them up. Never commit them to version control.

Autoscaling and Health Checks

This is where Kubernetes earns its keep. Three replicas handle baseline traffic. But when a cron-triggered workflow batch kicks off at 2 AM, you need more pods — then fewer when traffic drops.

Horizontal Pod Autoscaler handles this:

`yaml
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80
`

CPU at 70% triggers scale-up. Memory at 80% does the same. When traffic drops, pods scale down to the minimum of 3. You pay for compute you actually use.

But autoscaling only works if health checks are correct. Kubernetes needs to know when a pod is ready to serve traffic and when it’s actually healthy.

`yaml
livenessProbe:
httpGet:
path: /healthz
port: 5678
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3

readinessProbe:
httpGet:
path: /
port: 5678
initialDelaySeconds: 15
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
`

The liveness probe checks /healthz — n8n’s built-in health endpoint. If it fails 3 times in a row, Kubernetes restarts the pod. The readiness probe checks the main page. Until that passes, the pod doesn’t receive traffic from the ingress controller.

This separation prevents a restarting pod from getting workflow requests. It also prevents a slow-but-alive pod from being killed unnecessarily.

For zero-downtime deployments, use rolling updates:

`yaml
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
`

maxUnavailable: 0 means Kubernetes never kills a running pod until a new one is ready. maxSurge: 1 means it creates one extra pod temporarily. Your cluster needs capacity for that extra pod during updates.

Deploy with:

`bash
helm install n8n n8n/n8n -f values.yaml -n default –create-namespace
`

Watch the rollout:

`bash
kubectl rollout status deployment/n8n -n default
`

When you need to update, change the image tag and run:

`bash
helm upgrade n8n n8n/n8n -f values.yaml
`

Kubernetes handles the rest. New pods start. Old pods drain. Traffic flows through the ingress to whatever’s healthy. No manual intervention. No downtime.

Connecting to Existing Infrastructure

If you already run n8n in Docker, moving to Kubernetes isn’t a rewrite. The database, credentials, and workflows transfer directly. Point your new Kubernetes deployment at the same PostgreSQL database and everything works.

The n8n Docker Deployment guide covers the baseline setup. Kubernetes adds orchestration on top of that foundation.

For environment variables, the n8n Docker Environment Variables reference lists every configurable option. Most map directly to Kubernetes environment variables or secrets.

Understanding how n8n executes workflows helps you size your pods correctly. Queue mode workers, in particular, benefit from horizontal scaling since each worker processes independently.

When Kubernetes Makes Sense (and When It Doesn’t)

I don’t recommend Kubernetes for personal projects with fewer than 50 daily workflow executions. Docker Compose handles that fine. Simpler is better.

But when you hit these thresholds, Kubernetes pays for itself:

  • More than 500 workflow executions per day
  • Multiple team members editing workflows simultaneously
  • Need for zero-downtime deployments
  • Compliance requirements for data residency
  • Multi-region deployment needs
  • My instance processes roughly 2,000 executions daily across 40 workflows. Three n8n pods handle it comfortably. CPU peaks at 60% during batch runs. Memory stays steady around 2Gi per pod. The autoscaler kicks in maybe twice a month during unusual spikes.

    The operational overhead of managing Kubernetes is worth it for that scale. If you’re not comfortable reading kubectl describe pod output, start with Docker and migrate when you feel the pain.

    Action Card: Quick Deploy

    Copy this to deploy n8n on Kubernetes in under 5 minutes:

    `bash

    Add repository

    helm repo add n8n https://n8nio.github.io/k8s-Helm/
    helm repo update

    Install with defaults (good for testing)

    helm install n8n n8n/n8n –namespace n8n –create-namespace

    Check status

    kubectl get pods -n n8n
    kubectl get svc -n n8n

    Get ingress IP

    kubectl get ing -n n8n
    `

    For production, replace defaults with the values.yaml from the Production Configuration section above.

    References

  • n8n Official Documentation
  • Helm Documentation
  • Kubernetes Deployment Documentation
  • n8n Helm Chart Repository
  • Cert-Manager for TLS
  • Leave a Reply

    Your email address will not be published. Required fields are marked *