Deploy n8n on Kubernetes: Manifests and Setup

2026.05.10
Technology
842 Words
Deploy n8n on Kubernetes: Manifests and Setup

n8n Deployment: Kubernetes Manifest Guide

Part 2 of 3. Part 1: Docker Compose Setup | Part 3: Configuration Deep Dive

Kubernetes Deployment

When I move n8n into a production cluster, I use Kubernetes with Helm-like manifests managed through GitOps. The following manifests assume you have a running cluster with an ingress controller and cert-manager for TLS.

Step 1: Namespace and Secrets

namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: n8n
secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: n8n-secrets
namespace: n8n
type: Opaque
stringData:
postgres-password: "<GENERATE_32_CHAR_PASSWORD>"
encryption-key: "<GENERATE_32_CHAR_KEY>"
basic-auth-user: "admin"
basic-auth-password: "<GENERATE_24_CHAR_PASSWORD>"

Apply with kubectl apply -f namespace.yaml -f secret.yaml.

Step 2: PostgreSQL StatefulSet

postgres.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: n8n
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16-alpine
env:
- name: POSTGRES_USER
value: "n8n"
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: n8n-secrets
key: postgres-password
- name: POSTGRES_DB
value: "n8n"
ports:
- containerPort: 5432
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "1000m"
volumeClaimTemplates:
- metadata:
name: postgres-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: n8n
spec:
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432

Step 3: Redis Deployment

redis.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: n8n
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
command:
- redis-server
- --appendonly
- "yes"
- --maxmemory
- "256mb"
- --maxmemory-policy
- "allkeys-lru"
ports:
- containerPort: 6379
volumeMounts:
- name: redis-data
mountPath: /data
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: redis-data
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: n8n
spec:
selector:
app: redis
ports:
- port: 6379
targetPort: 6379

Step 4: n8n Main Deployment

n8n-main.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: n8n-main
namespace: n8n
spec:
replicas: 1
selector:
matchLabels:
app: n8n-main
template:
metadata:
labels:
app: n8n-main
spec:
containers:
- name: n8n
image: n8nio/n8n:1.50
ports:
- containerPort: 5678
env:
- name: DB_TYPE
value: "postgresdb"
- name: DB_POSTGRESDB_HOST
value: "postgres"
- name: DB_POSTGRESDB_PORT
value: "5432"
- name: DB_POSTGRESDB_DATABASE
value: "n8n"
- name: DB_POSTGRESDB_USER
value: "n8n"
- name: DB_POSTGRESDB_PASSWORD
valueFrom:
secretKeyRef:
name: n8n-secrets
key: postgres-password
- name: EXECUTIONS_MODE
value: "queue"
- name: QUEUE_BULL_REDIS_HOST
value: "redis"
- name: QUEUE_BULL_REDIS_PORT
value: "6379"
- name: N8N_ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: n8n-secrets
key: encryption-key
- name: WEBHOOK_URL
value: "https://n8n.yourdomain.com/"
- name: GENERIC_TIMEZONE
value: "UTC"
- name: N8N_BASIC_AUTH_ACTIVE
value: "true"
- name: N8N_BASIC_AUTH_USER
valueFrom:
secretKeyRef:
name: n8n-secrets
key: basic-auth-user
- name: N8N_BASIC_AUTH_PASSWORD
valueFrom:
secretKeyRef:
name: n8n-secrets
key: basic-auth-password
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "2000m"
---
apiVersion: v1
kind: Service
metadata:
name: n8n-main
namespace: n8n
spec:
selector:
app: n8n-main
ports:
- port: 5678
targetPort: 5678

Step 5: n8n Workers

n8n-worker.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: n8n-worker
namespace: n8n
spec:
replicas: 3
selector:
matchLabels:
app: n8n-worker
template:
metadata:
labels:
app: n8n-worker
spec:
containers:
- name: n8n
image: n8nio/n8n:1.50
command: ["n8n", "worker"]
env:
- name: DB_TYPE
value: "postgresdb"
- name: DB_POSTGRESDB_HOST
value: "postgres"
- name: DB_POSTGRESDB_PASSWORD
valueFrom:
secretKeyRef:
name: n8n-secrets
key: postgres-password
- name: QUEUE_BULL_REDIS_HOST
value: "redis"
- name: QUEUE_BULL_REDIS_PORT
value: "6379"
- name: N8N_ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: n8n-secrets
key: encryption-key
- name: GENERIC_TIMEZONE
value: "UTC"
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "1000m"

Step 6: Ingress with TLS

ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: n8n
namespace: n8n
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- n8n.yourdomain.com
secretName: n8n-tls
rules:
- host: n8n.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: n8n-main
port:
number: 5678

Apply all manifests:

Terminal window
kubectl apply -f namespace.yaml -f secret.yaml -f postgres.yaml -f redis.yaml -f n8n-main.yaml -f n8n-worker.yaml -f ingress.yaml

Verify the rollout:

Terminal window
kubectl get pods -n n8n
kubectl logs -n n8n deployment/n8n-main | grep "Editor is now accessible"

Webhook Security

Webhooks are the most exposed surface of any automation platform. Here is how I lock them down.

Use a Dedicated Webhook Subdomain

I always separate the webhook ingress from the editor UI. If your main instance is at n8n.yourdomain.com, run webhooks on hooks.yourdomain.com. This lets you apply different rate limits, WAF rules, and authentication policies.

Enable Webhook Authentication

n8n supports basic auth and header-based auth on webhook workflows. For production webhooks, I add a custom header check:

// n8n Webhook node → Headers
{
"X-Webhook-Secret": "{{$env.WEBHOOK_SECRET}}"
}

Store WEBHOOK_SECRET in the n8n credentials vault, not in the workflow JSON.

IP Allowlisting

If your webhooks only receive events from known sources (GitHub, Stripe, etc.), restrict ingress by source IP:

# nginx ingress annotation
nginx.ingress.kubernetes.io/whitelist-source-range: "192.30.252.0/22, 185.199.108.0/22"

Credential Management

n8n encrypts credentials at rest using the N8N_ENCRYPTION_KEY. However, there are additional layers you should add.

LayerImplementationRisk Mitigated
Encryption at restN8N_ENCRYPTION_KEYDatabase theft
Encryption in transitTLS on ingressMan-in-the-middle
Secret injectionKubernetes Secrets / Docker SecretsCredential leakage in env
Access controln8n RBAC + basic authUnauthorized workflow edits
Audit loggingPostgreSQL query logsCredential access tracing

I rotate the encryption key quarterly. To do this, export all workflows, destroy the volume, start n8n with a new key, and re-import. It is tedious but necessary for high-security environments.

AI Agent Node Setup

n8n’s AI Agent node lets you build agentic workflows that can call tools, reason over context, and interact with LLMs. I use this to build self-healing infrastructure alerts and automated ticket triage.

Connecting to a Self-Hosted LLM

If you followed my Ollama deployment guide, you already have a local LLM running. Wire it into n8n like this:

  1. Add an HTTP Request node or use the Ollama Chat Model node (community node).
  2. Set the base URL to your Ollama instance: http://ollama.ai-serving.svc.cluster.local:11434.
  3. Select the model: llama3.1:8b or mistral:7b.
  4. For the AI Agent node, set the System Message to define the agent’s role:
You are an SRE assistant. You have access to Kubernetes logs, deployment statuses,
and alerting history. When asked about an incident, summarize the symptoms,
suggest root causes, and recommend the next diagnostic step. Do not execute
commands without explicit approval.

Basic Workflow: Alert → LLM → Slack

Here is a simple workflow I run for triaging Prometheus alerts:

  1. Webhook Trigger: Receives alert payload from Alertmanager.
  2. HTTP Request: Fetches recent logs from Loki for the affected service.
  3. AI Agent: Summarizes logs and suggests a root cause.
  4. Slack: Posts the summary to #incidents with approve/reject buttons.
  5. If Approved: A second branch runs a safe diagnostic (e.g., kubectl get events).

This keeps humans in the loop while automating the noise reduction that usually burns out on-call engineers.

FAQ

What is n8n and what makes it different from Zapier?

n8n is an open-source workflow automation platform. The key difference from Zapier: you self-host everything. Your data never touches third-party servers, credentials stay encrypted on your infrastructure, and there are no usage tiers limiting how many workflows you can run. I switched to n8n after Zapier changed their pricing model and my automation bill tripled overnight.

How do I scale n8n workers on Kubernetes?

Set the replicas field in the n8n-worker Deployment manifest. I start with 3 replicas and scale based on Redis queue depth. Monitor LLEN bull:jobs in Redis. When it stays above 100 for more than 5 minutes, I add more workers. Each worker needs at least 256Mi RAM and 250m CPU for basic workflows.

Can I use n8n with vLLM instead of Ollama?

Yes. Both Ollama and vLLM expose OpenAI-compatible APIs. Point the HTTP Request node or the OpenAI Chat Model node at your vLLM service URL and set the model name. I run Ollama for development and vLLM for production workloads.

How do I back up my n8n instance on Kubernetes?

Back up two things: the PostgreSQL database (pg_dump) and the encryption key. Without the N8N_ENCRYPTION_KEY, your credentials are unrecoverable. I run nightly pg_dumps to S3-compatible storage and store the encryption key in a secure vault.

What is queue mode and when do I need it?

Queue mode separates the web UI from workflow execution workers using Redis as a job queue. I enable queue mode as soon as I have more than 5 active workflows. Without it, a single slow workflow can block the entire execution pipeline.

Is the n8n AI Agent node production-ready?

For internal tooling and alert triage, yes. I would not trust it for autonomous remediation in production without human approval gates. Always keep a human in the loop for destructive actions like deleting resources or modifying deployments.


Parts in this series: Part 1: Docker Compose Setup ← | Part 3: Configuration Deep Dive →

# N8N # ai-automation # workflow # docker # Kubernetes # redis # postgresql # deployment # self-hosted