What You’ll Deploy
This tutorial walks you through deploying a production-ready n8n setup on Kubernetes with:
- n8n application with health checks and resource limits
- PostgreSQL database with persistent storage
- Nginx Ingress for external access with TLS
- Prometheus metrics integration
- Basic authentication
Time to complete: ~15 minutes
Prerequisites
Before starting, make sure you have:
- A running Kubernetes cluster (v1.16+)
kubectlconfigured to access your cluster- Nginx Ingress Controller installed
- A domain name pointing to your cluster
- TLS certificate (or use cert-manager for automatic certificates)
Check your cluster is ready:
kubectl cluster-info
kubectl get nodes
Architecture Overview
Here’s what we’re building:
Internet โ Nginx Ingress โ n8n Service โ n8n Pod
โ
PostgreSQL StatefulSet
The n8n pod connects to PostgreSQL for storing workflows and execution history. Everything runs in the standard namespace.
Step 1: Create Namespace
Create a dedicated namespace for n8n:
kubectl create namespace standard
Step 2: Deploy PostgreSQL Database
PostgreSQL stores all n8n data (workflows, credentials, execution history). We use a StatefulSet for persistent storage.
Create postgres.yml:
---
apiVersion: v1
kind: Service
metadata:
name: n8n-postgres
namespace: standard
labels: &labels
app: n8n
component: database
spec:
ports:
- name: postgres
port: 5432
targetPort: 5432
clusterIP: None
selector:
app: n8n
component: database
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: n8n-postgres
namespace: standard
labels: &labels
app: n8n
component: database
spec:
serviceName: "n8n-postgres"
replicas: 1
selector:
matchLabels: *labels
template:
metadata:
labels: *labels
spec:
containers:
- name: postgresql
image: postgres:15
ports:
- name: postgres
containerPort: 5432
env:
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
- name: POSTGRES_USER
value: n8n
- name: POSTGRES_DB
value: n8n
- name: POSTGRES_PASSWORD
value: YOUR_SECURE_PASSWORD_HERE # Change this!
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "standard" # Change to your storage class
resources:
requests:
storage: 10Gi
Important: Replace YOUR_SECURE_PASSWORD_HERE with a strong password.
Deploy it:
kubectl apply -f postgres.yml
Verify PostgreSQL is running:
kubectl get pods -n standard -l component=database
kubectl logs -n standard n8n-postgres-0
Step 3: Configure n8n Secrets
Secrets store sensitive configuration like database passwords and authentication credentials.
Create n8n-secrets.yml:
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: n8n-secrets
namespace: standard
labels:
app: n8n
component: secrets
stringData:
# Database configuration
DB_TYPE: "postgresdb"
DB_POSTGRESDB_USER: "n8n"
DB_POSTGRESDB_DATABASE: "n8n"
DB_POSTGRESDB_PASSWORD: "YOUR_SECURE_PASSWORD_HERE" # Match postgres.yml
DB_POSTGRESDB_HOST: "n8n-postgres"
DB_POSTGRESDB_PORT: "5432"
# Basic auth credentials
N8N_BASIC_AUTH_ACTIVE: "true"
N8N_BASIC_AUTH_USER: "admin"
N8N_BASIC_AUTH_PASSWORD: "CHANGE_THIS_PASSWORD" # Change this!
# n8n configuration
N8N_HOST: "your.domain.com" # Your domain
N8N_PROTOCOL: "https"
WEBHOOK_URL: "https://your.domain.com/"
# Security
N8N_ENCRYPTION_KEY: "GENERATE_RANDOM_32_CHAR_STRING" # openssl rand -hex 16
# Performance
GENERIC_TIMEZONE: "America/New_York" # Your timezone
NODE_ENV: "production"
N8N_METRICS: "true"
NODE_OPTIONS: "--max_old_space_size=1024"
EXECUTIONS_PROCESS: "main"
Customize these values:
DB_POSTGRESDB_PASSWORD: Must match PostgreSQL passwordN8N_BASIC_AUTH_PASSWORD: Your login passwordN8N_HOST: Your domain nameN8N_ENCRYPTION_KEY: Generate withopenssl rand -hex 16GENERIC_TIMEZONE: Your timezone
Deploy secrets:
kubectl apply -f n8n-secrets.yml
Step 4: Deploy n8n Application
Create n8n-deployment.yml:
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: n8n-deployment
namespace: standard
labels: &labels
app: n8n
component: deployment
spec:
replicas: 1
selector:
matchLabels: *labels
template:
metadata:
labels: *labels
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "5678"
spec:
containers:
- name: n8n
image: n8nio/n8n:latest
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 5678
envFrom:
- secretRef:
name: n8n-secrets
livenessProbe:
httpGet:
path: /healthz
port: 5678
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /healthz
port: 5678
initialDelaySeconds: 10
periodSeconds: 5
resources:
limits:
cpu: "1.0"
memory: "1024Mi"
requests:
cpu: "0.5"
memory: "512Mi"
Deploy n8n:
kubectl apply -f n8n-deployment.yml
Wait for the pod to be ready:
kubectl get pods -n standard -l component=deployment -w
Check logs:
kubectl logs -n standard -l component=deployment --tail=50 -f
Step 5: Create Service
The service exposes n8n internally within the cluster.
Create n8n-service.yml:
---
apiVersion: v1
kind: Service
metadata:
name: n8n-service
namespace: standard
annotations:
prometheus.io/probe: "true"
prometheus.io/probe-path: "/healthz"
labels:
app: n8n
component: service
spec:
type: ClusterIP
selector:
app: n8n
component: deployment
ports:
- protocol: TCP
name: http
port: 80
targetPort: 5678
Deploy service:
kubectl apply -f n8n-service.yml
Step 6: Configure Ingress for External Access
The Ingress routes traffic from your domain to n8n with TLS.
Create n8n-ingress.yml:
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: n8n-ingress
namespace: standard
labels: &labels
app: n8n
component: ingress
annotations:
kubernetes.io/ingress.class: "nginx"
cert-manager.io/cluster-issuer: "letsencrypt-prod" # If using cert-manager
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
spec:
tls:
- hosts:
- your.domain.com # Change this
secretName: n8n-tls # Your TLS secret name
rules:
- host: your.domain.com # Change this
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: n8n-service
port:
number: 80
Customize:
- Replace
your.domain.comwith your actual domain - Update
secretNameto match your TLS certificate secret - If using cert-manager, it will auto-generate certificates
Deploy ingress:
kubectl apply -f n8n-ingress.yml
Step 7: Verify Deployment
Check all components are running:
# Check all pods
kubectl get pods -n standard
# Check services
kubectl get svc -n standard
# Check ingress
kubectl get ingress -n standard
# View n8n logs
kubectl logs -n standard deployment/n8n-deployment -f
Expected output:
NAME READY STATUS RESTARTS AGE
n8n-deployment-xxxxxxxxxx-xxxxx 1/1 Running 0 2m
n8n-postgres-0 1/1 Running 0 5m
Step 8: Access n8n
Open your browser and navigate to:
https://your.domain.com
You should see the n8n login page. Use the credentials from your secrets:
- Username: The value in
N8N_BASIC_AUTH_USER - Password: The value in
N8N_BASIC_AUTH_PASSWORD
Troubleshooting
Issue: Blank Page on Custom Path
Problem: If you try to access n8n on a subpath like /n8n, you get a blank page.
Solution: n8n doesn’t natively support subpaths well. You need to:
- Update your ingress to use path
/:
paths:
- path: /
pathType: Prefix
- Use a subdomain instead:
n8n.your.domain.com
Or, if you must use a subpath, add this to your secrets:
N8N_PATH: "/n8n"
VUE_APP_URL_BASE_API: "https://your.domain.com/n8n/"
And update ingress with rewrite annotations:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
# Update path to:
path: /n8n(/|$)(.*)
Issue: Pod Not Starting
Check logs:
kubectl describe pod -n standard -l component=deployment
kubectl logs -n standard -l component=deployment
Common causes:
- Database connection failed (check password matches)
- PVC not binding (check storage class exists)
- Image pull error (check internet connectivity)
Issue: Can’t Access n8n
Check ingress:
kubectl describe ingress -n standard n8n-ingress
Verify:
- DNS is pointing to your cluster’s load balancer
- TLS secret exists:
kubectl get secret -n standard n8n-tls - Ingress controller is running:
kubectl get pods -n ingress-nginx
Issue: Database Connection Errors
Test database connectivity:
kubectl run -it --rm debug --image=postgres:15 --restart=Never -- \
psql -h n8n-postgres.standard.svc.cluster.local -U n8n -d n8n
Enter the password when prompted. If it connects, database is working.
Next Steps
Now that n8n is running:
- Create your first workflow
- Click “Add workflow”
- Add nodes from the right panel
- Connect them together
- Click “Execute Workflow”
- Set up monitoring
- Enable Prometheus scraping (already configured)
- Create Grafana dashboard for metrics
- Configure backups
- Backup PostgreSQL data regularly
- Export workflow definitions
- Scale for production
- Enable queue mode with Redis
- Deploy separate worker pods
- Increase replicas
- Secure your setup
- Use strong passwords
- Enable network policies
- Regular security updates
Quick Reference
View all resources:
kubectl get all -n standard -l app=n8n
Restart n8n:
kubectl rollout restart deployment/n8n-deployment -n standard
View live logs:
kubectl logs -n standard deployment/n8n-deployment -f
Connect to PostgreSQL:
kubectl exec -it -n standard n8n-postgres-0 -- psql -U n8n
Delete everything:
kubectl delete namespace standard