Agentic AI Agents AI n8n

n8n, Postgres and Kubernetes: A Step-by-Step Tutorial


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+)
  • kubectl configured 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 password
  • N8N_BASIC_AUTH_PASSWORD: Your login password
  • N8N_HOST: Your domain name
  • N8N_ENCRYPTION_KEY: Generate with openssl rand -hex 16
  • GENERIC_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.com with your actual domain
  • Update secretName to 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:

  1. Update your ingress to use path /:
paths:
- path: /
  pathType: Prefix
  1. 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:

  1. Create your first workflow
    • Click “Add workflow”
    • Add nodes from the right panel
    • Connect them together
    • Click “Execute Workflow”
  2. Set up monitoring
    • Enable Prometheus scraping (already configured)
    • Create Grafana dashboard for metrics
  3. Configure backups
    • Backup PostgreSQL data regularly
    • Export workflow definitions
  4. Scale for production
    • Enable queue mode with Redis
    • Deploy separate worker pods
    • Increase replicas
  5. 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

Resources


Leave a Reply

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