Agentic AI

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

December 30, 2025 Kubezilla Team 7 min read

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 comment