- 2026-02-12
- 7 minuti
Installa n8n su Kubernetes con Helm: guida passo passo

Dopo aver cercato in lungo e in largo una guida che spiegasse in modo chiaro e dettagliato come installare n8n su Kubernetes, ho deciso di scriverla per condividere con voi i passaggi necessari per l’installazione di n8n su Kubernetes utilizzando Helm.
Per installare n8n su Kubernetes, è possibile utilizzare Helm, un gestore di pacchetti per Kubernetes. Di seguito sono riportati i passaggi per l’installazione di n8n su Kubernetes utilizzando Helm.
Aggiungi il repository Helm
Uno dei repository più aggiornato per n8n è il repository “community-charts”. Per aggiungere questo repository e aggiornare la cache dei chart, esegui i seguenti comandi:
helm repo add community-charts https://community-charts.github.io/helm-charts
helm repo update
Crea un namespace (opzionale)
Questa è facile: ti basterà eseguire questo comando per creare un namespace dedicato a n8n:
kubectl create namespace n8n-test
Configurazione Secret
N8N usa una chiave di crittografia per proteggere i dati sensibili, come le credenziali e i segreti. È importante generare una chiave di crittografia sicura e creare un secret Kubernetes per n8n.
Esegui questo comando per generare una key casuale usando PowerShell:
$N8N_ENCRYPTION_KEY = -join ((48..57) + (65..90) + (97..122) | Get-Random -Count 32 | ForEach-Object {[char]$_})
Write-Host "Generated Encryption Key: $N8N_ENCRYPTION_KEY"
Oppure usa questo comando in bash:
N8N_ENCRYPTION_KEY=$(tr -dc 'A-Za-z0-9' </dev/urandom | head -c 32)
echo "Generated Encryption Key: $N8N_ENCRYPTION_KEY"
A questo punto, puoi usare il comando seguente per creare un secret Kubernetes che contiene la chiave di crittografia e il segreto JWT per n8n. Assicurati di sostituire $N8N_ENCRYPTION_KEY con una chiave di crittografia appena generata.
kubectl create secret generic n8n-secrets `
--namespace=n8n-automation `
--from-literal=N8N_ENCRYPTION_KEY=$N8N_ENCRYPTION_KEY `
--from-literal=N8N_USER_MANAGEMENT_JWT_SECRET=$N8N_ENCRYPTION_KEY `
--dry-run=client -o yaml | kubectl apply -f -
Installazione
Siamo pronti per installare n8n. Per l’ambiente creato in precedenza, puoi eseguire il seguente comando:
helm install my-test-n8n community-charts/n8n -n n8n-test
Usando questo comando, andrai a utilizzare i valori di default del chart. Per personalizzare la configurazione, puoi creare un file values.yaml e passarlo al comando di installazione con l’opzione -f values.yaml.
Tieni conto che il file values.yaml contiene le configurazioni specifiche, come la configurazione del database, le variabili d’ambiente e le risorse, e permette di personalizzare l’installazione in base alle esigenze del tuo ambiente.
Vediamo quali sono le configurazioni più importanti da utilizzare nel file values.yaml.
Per semplicità, troverai all’interno del file dei commenti che spiegano il significato di ogni configurazione e forniscono consigli su come personalizzarla in base alle esigenze del tuo ambiente (test o produzione): in ogni caso, soprattutto per la configurazione del database, è importante personalizzare i valori per garantire prestazioni ottimali e sicurezza: nella documentazione ufficiale che trovi qui, vengono fornite le indicazioni per la configurazione di un database esterno o interno al cluster, ma in questo esempio, per semplificare l’installazione, utilizzeremo il chart di PostgreSQL incluso come dipendenza del chart di n8n, che è già preconfigurato per funzionare con n8n.
# -----------------------------------------------------------------------------
# IMAGE CONFIGURATION
# -----------------------------------------------------------------------------
image:
repository: n8nio/n8n
tag: "2.13.0" # MODIFICARE CON LA VERSIONE DESIDERATA
pullPolicy: IfNotPresent
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
# -----------------------------------------------------------------------------
# SERVICE ACCOUNT
# -----------------------------------------------------------------------------
serviceAccount:
create: true
annotations: {}
name: ""
# -----------------------------------------------------------------------------
# POD CONFIGURATION
# -----------------------------------------------------------------------------
serviceMonitor:
enabled: false
podAnnotations:
prometheus.io/scrape: "false" # Cambia a "true" se vuoi abilitare lo scraping da parte di Prometheus
prometheus.io/port: "5678"
prometheus.io/path: "/metrics"
podLabels: {}
podSecurityContext:
runAsNonRoot: true # Assicurati che l'utente non sia root, per girare con privilegi minimi (necessario, ad esempio, per OpenShift)
runAsUser: 1000 # L'utente con cui n8n girerà all'interno del container (1000 è un valore comune per applicazioni non root)
fsGroup: 1000 # Il gruppo con cui n8n avrà accesso ai volumi (deve corrispondere all'utente)
seccompProfile:
type: RuntimeDefault
securityContext:
allowPrivilegeEscalation: false # Non permettere l'escalation dei privilegi
readOnlyRootFilesystem: false # N8N ha bisogno di scrivere su disco per i dati temporanei, quindi non possiamo rendere il filesystem root read-only
capabilities:
drop:
- ALL
# -----------------------------------------------------------------------------
# N8N APPLICATION CONFIGURATION
# -----------------------------------------------------------------------------
timezone: "Europe/Rome" # Imposta il timezone per n8n (importante per la gestione delle date e degli orari nei flussi di lavoro)
webhook:
url: "https://n8n.<YOUR_DOMAIN>"
db:
type: postgresdb # Usa PostgreSQL come database (consigliato per produzione), oppure MySQL per ambienti di test o sviluppo
logging:
enabled: true # Abilita i log del database per facilitare il debug
options: error
maxQueryExecutionTime: 1000 # Logga solo query che impiegano più di 1000ms (1 secondo) per essere eseguite, per identificare eventuali colli di bottiglia
postgresql:
enabled: true
architecture: replication # Configura PostgreSQL in modalità replica per alta disponibilità, consigliato per ambienti di produzione
auth:
password: "<POSTGRES_PASSWORD>" # Se non specificato, il chart genererà una password casuale
replicationPassword: "<POSTGRES_REPLICATION_PASSWORD>" # Idem
database: "<DB_NAME>"
username: "<DB_USERNAME>"
primary:
resources:
requests:
memory: 1Gi
cpu: 500m
limits:
memory: 4Gi
cpu: 2000m
persistence:
enabled: true
storageClass: "mystorageclass" # Sostituisci con lo storage class del tuo cluster
size: 10Gi
extendedConfiguration: |
max_connections = 100 # Numero massimo di connessioni al database: in ambienti di produzione, è consigliabile aumentare questo valore per supportare un maggior numero di connessioni simultanee, mentre in ambienti di test o sviluppo, puoi mantenerlo a un valore più basso per risparmiare risorse
shared_buffers = 1GB # Memoria condivisa per il database: in ambienti di produzione, è consigliabile allocare più memoria per migliorare le prestazioni, mentre in ambienti di test o sviluppo, puoi ridurre questo valore per risparmiare risorse
wal_level = replica # Livello di logging per la replica: necessario per abilitare la replica, consigliato per ambienti di produzione
max_wal_senders = 10 # Numero massimo di processi di invio WAL: indovina? In ambienti di produzione, è consigliabile aumentare questo valore per supportare più repliche, mentre in ambienti di test o sviluppo, puoi mantenerlo a un valore più basso
max_replication_slots = 10 # Numero massimo di slot di replica: in ambienti di produzione, è consigliabile aumentare questo valore per supportare più repliche, mentre in ambienti di test o sviluppo, puoi mantenerlo a un valore più basso
synchronous_commit = on
readReplicas:
replicaCount: 2 # Numero di repliche di sola lettura (consigliato almeno 2 per bilanciare il carico di lettura)
resources:
requests:
memory: 1Gi
cpu: 500m
limits:
memory: 4Gi
cpu: 2000m
extendedConfiguration: |
max_connections = 100
shared_buffers = 1GB
hot_standby = on
synchronous_commit = on
persistence:
enabled: true
storageClass: "mystorageclass" # Sostituisci con lo storage class del tuo cluster
size: 5Gi
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/component: read
topologyKey: kubernetes.io/hostname
backup: # Configurazione del backup automatico di PostgreSQL: consigliato per ambienti di produzione, non necessario per ambienti di test
enabled: true
cronjob:
schedule: "0 2 * * *"
concurrencyPolicy: Forbid
storage:
storageClass: "acloud"
size: 10Gi
# -----------------------------------------------------------------------------
# ENVIRONMENT VARIABLES
# -----------------------------------------------------------------------------
main:
extraEnvVars: # Se stai usando il chart ufficiale di n8n, queste variabili sono già preconfigurate; se invece stai usando un chart custom, assicurati di configurare tutte le variabili necessarie per la connessione al database e per il funzionamento di n8n
DB_POSTGRESDB_HOST: "<RELEASE_NAME>-postgresql-primary"
N8N_HOST: "n8n.<YOUR_DOMAIN>"
N8N_PROTOCOL: "https" # Solo se stai usando un Ingress con TLS; altrimenti, usa "http"
N8N_EDITOR_BASE_URL: "n8n.<YOUR_DOMAIN>/"
N8N_SECURE_COOKIE: "true"
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: "true"
EXECUTIONS_MODE: "regular"
# -----------------------------------------------------------------------------
# RESOURCES
# -----------------------------------------------------------------------------
resources: # Configura le risorse per il container n8n: in ambienti di produzione, è consigliabile allocare più risorse per garantire prestazioni ottimali, mentre in ambienti di test o sviluppo, puoi ridurre le risorse per risparmiare costi
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 250m
memory: 512Mi
# -----------------------------------------------------------------------------
# PROBES
# -----------------------------------------------------------------------------
livenessProbe:
httpGet:
path: "/healthz"
port: "http"
scheme: HTTP
initialDelaySeconds: 60
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: "/healthz" # Questo, nella versione del chart ufficiale, presenta un endpoint diverso da quello di liveness che però non funziona: in questo caso, lo forziamo a usare lo stesso endpoint di liveness, che invece è funzionante
port: "http"
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
# -----------------------------------------------------------------------------
# SERVICE CONFIGURATION
# -----------------------------------------------------------------------------
service:
type: ClusterIP
port: 5678
Facciamo un piccolo recap: questa installazione andrà a creare un deployment di n8n con un database PostgreSQL in modalità replica, configurato con risorse adeguate e con una configurazione di backup automatico. Inoltre, il servizio sarà esposto come ClusterIP, quindi sarà necessario utilizzare il port forwarding o un Ingress per accedere all’interfaccia utente di n8n.
Tieni presente che in ambienti di test o sviluppo, puoi semplificare la configurazione riducendo le risorse allocate, disabilitando la replica del database e il backup automatico, e utilizzando un database più leggero come SQLite o MySQL, a seconda delle tue esigenze. Trovi tutto nella documentazione riportata in precedenza!
E ora?
Configurazione dell’accesso all’interfaccia utente
Dopo l’installazione, è possibile accedere all’interfaccia utente di n8n per configurare i flussi di lavoro. Per accedere all’interfaccia utente, è necessario esporre il servizio n8n utilizzando un LoadBalancer o un Ingress.
Per l’ambiente di test, è possibile esporre il servizio n8n utilizzando un LoadBalancer:
kubectl expose deployment my-test-n8n --type=LoadBalancer --name=my-test-n8n-service -n n8n-test
o accedere tramite port forwarding:
kubectl port-forward deployment/my-test-n8n 5678:5678 -n n8n-test
A questo punto, puoi accedere all’interfaccia utente di n8n aprendo un browser e navigando all’indirizzo http://localhost:5678 (se stai usando il port forwarding) o all’indirizzo del LoadBalancer (se stai usando un LoadBalancer).
Più facile di così! Se vuoi, puoi anche configurare un Ingress per esporre n8n con un nome di dominio personalizzato e con TLS, ma questa è un’altra storia che affronteremo magari in un prossimo articolo!








