Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/lint-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,8 @@ jobs:
- name: Run chart-testing (install)
if: steps.list-changed.outputs.changed == 'true'
run: ct install --target-branch ${{ github.event.repository.default_branch }}

- name: Validate StatefulSet template
if: steps.list-changed.outputs.changed == 'true'
run: |
helm template n8n ./charts/n8n --set main.statefulSet.enabled=true | grep -q 'kind: StatefulSet'
173 changes: 156 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
> If you're interested in making a difference,
> [join the discussion](https://github.com/8gears/n8n-helm-chart/discussions/90).

> [!WARNING]
> Version 1.0.0 of this Chart includes breaking changes and is not backwards compatible with previous versions.
> Please review the migration guide below before upgrading.

# n8n Helm Chart for Kubernetes

Expand Down Expand Up @@ -354,11 +357,137 @@ main:
nodeSelector: {}
tolerations: []
affinity: {}
```

# # # # # # # # # # # # # # # #
#
# Worker related settings
#
## Deployment Modes

### Main Component

The main n8n component can be deployed in two modes:

1. **Deployment (default)**:
- Suitable for Community Edition
- Limited to 1 replica when using persistent storage (PVC)
- Can use multiple replicas only with `emptyDir` storage (data will be lost on pod restart)

```yaml
main:
replicaCount: 1 # Must be 1 with persistent storage
statefulSet:
enabled: false
persistence:
enabled: true
type: dynamic
size: 10Gi
```

2. **StatefulSet**:
- Required for multiple replicas with persistent storage
- Requires n8n Enterprise license
- Each pod gets its own persistent volume

```yaml
main:
replicaCount: 3 # Can be >1 with StatefulSet
statefulSet:
enabled: true
persistence:
enabled: true
type: dynamic
size: 10Gi
extraEnv:
N8N_MULTI_MAIN_SETUP_ENABLED:
value: "true" # Required for multiple replicas
```

### Worker Component

The worker component can be deployed either as Deployment or StatefulSet:

```yaml
worker:
enabled: true
count: 2
statefulSet:
enabled: true # or false for Deployment
persistence:
enabled: true
size: 5Gi
```
Comment thread
aleksandrovpa marked this conversation as resolved.

### Webhook Component

The webhook component is deployed as a Deployment and has the same limitations as the main component when using persistent storage:

1. **With Persistent Storage**:
- Limited to 1 replica when using PVC
- Data is preserved between pod restarts

```yaml
webhook:
enabled: true
replicaCount: 1 # Must be 1 with persistent storage
persistence:
enabled: true
type: dynamic
size: 5Gi
```

2. **Without Persistent Storage or with emptyDir**:
- Can use multiple replicas
- Data is lost on pod restarts

```yaml
webhook:
enabled: true
replicaCount: 3 # Can be >1
persistence:
enabled: true
type: emptyDir # or enabled: false
deploymentStrategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
```

## High Availability Setup

The chart supports two deployment modes for the main n8n component:

> [!IMPORTANT]
> Using StatefulSet with multiple main replicas requires n8n Enterprise license.
> Without Enterprise license, use Deployment with a single main replica.

### StatefulSet Mode (Enterprise)

Requires n8n Enterprise license. Enables running multiple main replicas for high availability:

```yaml
main:
statefulSet:
enabled: true
replicaCount: 2
extraEnv:
N8N_MULTI_MAIN_SETUP_ENABLED:
value: "true" # Only works with Enterprise license
```

### Deployment Mode (Community)

Default mode for Community edition. Uses a single main replica:

```yaml
main:
statefulSet:
enabled: false
replicaCount: 1
```

Note: Worker and webhook components can be scaled independently regardless of the license type.

### Worker related settings
```yaml
worker:
enabled: false

Expand Down Expand Up @@ -482,6 +611,7 @@ worker:

# here you can override the livenessProbe for the main container
# it may be used to increase the timeout for the livenessProbe (e.g., to resolve issues like

livenessProbe:
httpGet:
path: /healthz
Expand All @@ -508,6 +638,12 @@ worker:
# List of initialization containers belonging to the pod. Init containers are executed in order prior to containers being started.
# See https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
initContainers: []
# - name: init-data-dir
# image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
# command: [ "/bin/sh", "-c", "mkdir -p /home/node/.n8n/" ]
# volumeMounts:
# - name: data
# mountPath: /home/node/.n8n

service:
annotations: {}
Expand Down Expand Up @@ -539,11 +675,14 @@ worker:
nodeSelector: {}
tolerations: []
affinity: {}
```
### Webhook related settings

With `.Values.scaling.webhook.enabled=true` you disable Webhooks from the main process, but you enable the processing on a different Webhook instance.
#### See https://github.com/8gears/n8n-helm-chart/issues/39#issuecomment-1579991754 for the full explanation.

# Webhook related settings
# With .Values.scaling.webhook.enabled=true you disable Webhooks from the main process, but you enable the processing on a different Webhook instance.
# See https://github.com/8gears/n8n-helm-chart/issues/39#issuecomment-1579991754 for the full explanation.
# Webhook processes rely on Valkey/Redis too.
### Webhook processes rely on Valkey/Redis too.
```yaml
webhook:
enabled: false
# additional (to main) config for webhook
Expand Down Expand Up @@ -724,14 +863,12 @@ webhook:
nodeSelector: {}
tolerations: []
affinity: {}
```
### User defined supplementary K8s manifests

#
# User defined supplementary K8s manifests
#

# Takes a list of Kubernetes manifests and merges each resource with a default metadata.labels map and
# installs the result.
# Use this to add any arbitrary Kubernetes manifests alongside this chart instead of kubectl and scripts.
Takes a list of Kubernetes manifests and merges each resource with a default metadata.labels map and installs the result.
Use this to add any arbitrary Kubernetes manifests alongside this chart instead of kubectl and scripts.
```yaml
extraManifests: []
# - apiVersion: v1
# kind: ConfigMap
Expand Down Expand Up @@ -760,9 +897,11 @@ extraTemplateManifests: []
# name: my-config
# stringData:
# image_name: {{ .Values.image.repository }}
```

# Bitnami Valkey configuration
# https://artifacthub.io/packages/helm/bitnami/valkey
### Bitnami Valkey configuration
https://artifacthub.io/packages/helm/bitnami/valkey
```yaml
valkey:
enabled: false
#architecture: standalone
Expand Down
4 changes: 2 additions & 2 deletions charts/n8n/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
apiVersion: v2
name: n8n
version: 1.0.14
appVersion: 1.109.1
version: 1.1.0
appVersion: 1.99.1
type: application
description: "Helm Chart for deploying n8n on Kubernetes, a fair-code workflow automation platform with native AI capabilities for technical teams. Easily automate tasks across different services."
home: https://github.com/8gears/n8n-helm-chart
Expand Down
43 changes: 39 additions & 4 deletions charts/n8n/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{- end }}

{{/* PVC existing, emptyDir, Dynamic */}}
{{/* PVC existing, emptyDir, Dynamic for main component */}}
{{- define "n8n.pvc" -}}
{{- if or (not .Values.main.persistence.enabled) (eq .Values.main.persistence.type "emptyDir") -}}
emptyDir: {}
Expand All @@ -69,10 +69,35 @@ app.kubernetes.io/instance: {{ .Release.Name }}
claimName: {{ .Values.main.persistence.existingClaim }}
{{- else if and .Values.main.persistence.enabled (eq .Values.main.persistence.type "dynamic") -}}
persistentVolumeClaim:
claimName: {{ include "n8n.fullname" . }}
claimName: {{ include "n8n.fullname" . }}-main
{{- end }}
{{- end }}

{{/* PVC existing, emptyDir, Dynamic for webhook component */}}
{{- define "n8n.webhook.pvc" -}}
{{- if or (not .Values.webhook.persistence.enabled) (eq .Values.webhook.persistence.type "emptyDir") -}}
emptyDir: {}
{{- else if and .Values.webhook.persistence.enabled .Values.webhook.persistence.existingClaim -}}
persistentVolumeClaim:
claimName: {{ .Values.webhook.persistence.existingClaim }}
{{- else if and .Values.webhook.persistence.enabled (eq .Values.webhook.persistence.type "dynamic") -}}
persistentVolumeClaim:
claimName: {{ include "n8n.fullname" . }}-webhook
{{- end }}
{{- end }}

{{/* PVC existing, emptyDir, Dynamic for worker component */}}
{{- define "n8n.worker.pvc" -}}
{{- if or (not .Values.worker.persistence.enabled) (eq .Values.worker.persistence.type "emptyDir") -}}
emptyDir: {}
{{- else if and .Values.worker.persistence.enabled .Values.worker.persistence.existingClaim -}}
persistentVolumeClaim:
claimName: {{ .Values.worker.persistence.existingClaim }}
{{- else if and .Values.worker.persistence.enabled (eq .Values.worker.persistence.type "dynamic") -}}
persistentVolumeClaim:
claimName: {{ include "n8n.fullname" . }}-worker
{{- end }}
{{- end }}

{{/* Create environment variables from yaml tree */}}
{{- define "toEnvVars" -}}
Expand Down Expand Up @@ -100,6 +125,16 @@ app.kubernetes.io/instance: {{ .Release.Name }}
{{- if and .Values.webhook.enabled (not $envVars.QUEUE_BULL_REDIS_HOST) -}}
{{- fail "Webhook processes rely on Valkey. Please set a Redis/Valkey host when webhook.enabled=true" -}}
{{- end -}}
{{- end -}}

{{- end }}

{{/*
Validate values for n8n deployment
*/}}
{{- define "n8n.validateValues" -}}
{{- if and (not .Values.main.statefulSet.enabled) (gt (int .Values.main.replicaCount) 1) (ne .Values.main.persistence.type "emptyDir") -}}
{{- fail "\nERROR: Invalid configuration\nWhen using Deployment (statefulSet.enabled=false) with persistent storage, replicaCount must be 1.\nTo use multiple replicas either:\n 1. Enable StatefulSet (requires Enterprise license)\n 2. Use emptyDir for persistence (data will be lost on pod restart)" -}}
{{- end -}}
{{- if and .Values.webhook.enabled .Values.webhook.persistence.enabled (ne .Values.webhook.persistence.type "emptyDir") (gt (int .Values.webhook.replicaCount) 1) -}}
{{- fail "\nERROR: Invalid webhook configuration\nWhen using persistent storage for webhook component, replicaCount must be 1.\nTo use multiple replicas either:\n 1. Disable persistence\n 2. Use emptyDir for persistence (data will be lost on pod restart)" -}}
{{- end -}}
{{- end -}}
14 changes: 9 additions & 5 deletions charts/n8n/templates/deployment.webhook.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{{- if .Values.webhook.enabled }}
{{- /* Validate Valkey/Redis configuration */}}
{{- include "n8n.validateValkey" . }}
{{- include "n8n.validateValues" . }}
apiVersion: apps/v1
kind: Deployment
metadata:
Expand Down Expand Up @@ -33,7 +34,7 @@ spec:
template:
metadata:
annotations:
checksum/config: {{ print .Values.webhook .Values.main | sha256sum }}
checksum/config: {{ print .Values.webhook | sha256sum }}
{{- with .Values.webhook.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
Expand All @@ -57,6 +58,8 @@ spec:
{{- end }}
containers:
- name: {{ .Chart.Name }}-webhook
securityContext:
{{- toYaml .Values.webhook.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
{{- if .Values.webhook.command }}
Expand Down Expand Up @@ -101,21 +104,20 @@ spec:
- secretRef:
name: {{ include "n8n.fullname" . }}-webhook-secret
{{- end }}

env: {{ not (empty .Values.webhook.extraEnv) | ternary nil "[]" }}
{{- range $key, $value := .Values.webhook.extraEnv }}
- name: {{ $key }}
{{- toYaml $value | nindent 14 }}
{{- end }}
lifecycle:
{{- toYaml .Values.webhook.lifecycle | nindent 12 }}
securityContext:
{{- toYaml .Values.webhook.securityContext | nindent 12 }}
resources:
{{- toYaml .Values.webhook.resources | nindent 12 }}
{{- if .Values.webhook.persistence.enabled }}
volumeMounts:
- name: data
mountPath: /home/node/.n8n
{{- end }}
{{- if .Values.webhook.extraVolumeMounts }}
{{- toYaml .Values.webhook.extraVolumeMounts | nindent 12 }}
{{- end }}
Comment on lines +116 to 123
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Render volumeMounts only when there is content

Avoid emitting an empty volumeMounts: or orphaned mount items when persistence is disabled.

-          {{- if .Values.webhook.persistence.enabled }}
-          volumeMounts:
-            - name: data
-              mountPath: /home/node/.n8n
-          {{- end }}
-          {{- if .Values.webhook.extraVolumeMounts }}
-            {{- toYaml .Values.webhook.extraVolumeMounts | nindent 12 }}
-          {{- end }}
+          {{- if or .Values.webhook.persistence.enabled .Values.webhook.extraVolumeMounts }}
+          volumeMounts:
+            {{- if .Values.webhook.persistence.enabled }}
+            - name: data
+              mountPath: /home/node/.n8n
+            {{- end }}
+            {{- if .Values.webhook.extraVolumeMounts }}
+{{ toYaml .Values.webhook.extraVolumeMounts | nindent 12 }}
+            {{- end }}
+          {{- end }}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In charts/n8n/templates/deployment.webhook.yaml around lines 116–123, the
template can emit an empty or orphaned volumeMounts block when persistence is
disabled but extraVolumeMounts is absent; update the conditional to render the
entire volumeMounts section only when there is at least one mount by combining
the checks (e.g., if .Values.webhook.persistence.enabled or
.Values.webhook.extraVolumeMounts) and inside that block include the persistence
mount when enabled and render extraVolumeMounts via toYaml with the correct
nindent so no empty list or orphaned header is produced.

Expand All @@ -136,8 +138,10 @@ spec:
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
{{- if .Values.webhook.persistence.enabled }}
- name: "data"
{{ include "n8n.pvc" . }}
{{ include "n8n.webhook.pvc" . }}
{{- end }}
{{- if .Values.webhook.extraVolumes }}
{{- toYaml .Values.webhook.extraVolumes | nindent 8 }}
{{- end }}
Comment on lines 140 to 147
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard the volumes section to avoid an empty key

volumes: is always emitted; if both persistence and extraVolumes are off, YAML is invalid. Emit only when required.

-      volumes:
-        {{- if .Values.webhook.persistence.enabled }}
-        - name: "data"
-          {{ include "n8n.webhook.pvc" . }}
-        {{- end }}
-        {{- if .Values.webhook.extraVolumes }}
-          {{- toYaml .Values.webhook.extraVolumes | nindent 8 }}
-        {{- end }}
+      {{- if or .Values.webhook.persistence.enabled .Values.webhook.extraVolumes }}
+      volumes:
+        {{- if .Values.webhook.persistence.enabled }}
+        - name: data
+{{ include "n8n.webhook.pvc" . | nindent 10 }}
+        {{- end }}
+        {{- if .Values.webhook.extraVolumes }}
+{{ toYaml .Values.webhook.extraVolumes | nindent 8 }}
+        {{- end }}
+      {{- end }}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In charts/n8n/templates/deployment.webhook.yaml around lines 140 to 147, the
template always emits the "volumes:" key which becomes an empty mapping when
both .Values.webhook.persistence.enabled and .Values.webhook.extraVolumes are
false; wrap the entire "volumes:" block in a conditional that checks whether
either .Values.webhook.persistence.enabled or .Values.webhook.extraVolumes is
true and only render "volumes:" and its children when at least one is present,
preserving the existing children logic and indentation.

Expand Down
Loading