This post walks through deploying n8n on a local k3s cluster with production-grade practices: a CloudNativePG-managed PostgreSQL cluster, continuous backups to MinIO via Barman, GitOps with Flux, secrets from Vault via External Secrets Operator, and a hardened pod security posture.
Stack
k3s — lightweight Kubernetes for bare-metal/homelab
CloudNativePG (CNPG) — Postgres operator with built-in HA and backup
Barman Cloud Plugin — WAL archiving and point-in-time recovery to S3/MinIO
⚠️ Gotcha: The credentials used in the CNPG initdb bootstrap and the n8n app secret must reference the same Vault path. A mismatch causes password authentication failed on startup.
3. Continuous Backups to MinIO
ObjectStore
apiVersion:barmancloud.cnpg.io/v1kind:ObjectStoremetadata:name:n8n-objectstorespec:configuration:destinationPath:s3://backups/n8n-db/ # ⚠️ use a cluster-specific subfolderendpointURL:http://minio.example.net:9000s3Credentials:accessKeyId:key:ACCESS_KEY_IDname:minio-s3secretAccessKey:key:ACCESS_SECRET_KEYname:minio-s3wal:compression:gzipretentionPolicy:3d
⚠️ Critical: Always include a cluster-specific subfolder in destinationPath (e.g. s3://backups/n8n-db/). Using the bucket root causes barman-cloud-check-wal-archive to fail with Expected empty archive once any backup data exists, blocking all WAL archiving.
apiVersion:v1kind:PersistentVolumeClaimmetadata:name:n8n-dataspec:accessModes:- ReadWriteOnce # ⚠️ local-path only supports RWO, not RWXresources:requests:storage:1Gi
⚠️ Gotcha: k3s’s default local-path StorageClass uses WaitForFirstConsumer binding. If the PVC is created with ReadWriteMany, it stays Pending indefinitely. Use ReadWriteOnce for single-replica workloads.
readOnlyRootFilesystem: true — writable paths mounted as emptyDir
All Linux capabilities dropped
seccompProfile: RuntimeDefault
Pod Security Standards (PSS) restricted compliant
5. Ingress via Cloudflare Tunnel
Rather than exposing NodePorts or LoadBalancer IPs, use a Cloudflare Tunnel for zero-trust ingress. The cloudflared deployment runs as 2 replicas for redundancy and connects your n8n service to a Cloudflare-managed hostname — no inbound firewall rules required.
6. GitOps with Flux
All manifests live in Git. Flux watches the repo and reconciles state continuously. Secrets are never in Git — only ExternalSecret resources that reference Vault paths.
For immutable fields (like PVC accessModes), Flux cannot update them in-place. The workflow is:
vscode@8f5604632155 /workspaces/pi5cluster main 2m 10s
❯ k get pods
NAME READY STATUS RESTARTS AGE
cloudflared-589c8f977c-fs88r 1/1 Running 0 20m
cloudflared-589c8f977c-kt7s4 1/1 Running 0 20m
n8n-db-1 2/2 Running 0 27m
n8n-db-2 2/2 Running 0 27m
n8n-df6b4587d-dkvcx 1/1 Running 0 20m
vscode@8f5604632155 /workspaces/pi5cluster main
❯
vscode@8f5604632155 /workspaces/pi5cluster main
❯ k cnpg status n8n-db
Cluster Summary
Name n8n/n8n-db
System ID: 7611505246418595865
PostgreSQL Image: ghcr.io/cloudnative-pg/postgresql:18.0-system-trixie
Primary instance: n8n-db-1
Primary promotion time: 2026-02-27 11:28:01 +0000 UTC (27m47s)
Status: Cluster in healthy state
Instances: 2
Ready instances: 2
Size: 131M
Current Write LSN: 0/7000000 (Timeline: 1 - WAL File: 000000010000000000000007)
Continuous Backup status (Barman Cloud Plugin)
ObjectStore / Server name: n8n-objectstore/n8n-db
First Point of Recoverability: 2026-02-27 11:50:11 UTC
Last Successful Backup: 2026-02-27 11:50:11 UTC
Last Failed Backup: -
Working WAL archiving: OK
WALs waiting to be archived: 0
Last Archived WAL: 000000010000000000000006 @ 2026-02-27T11:50:12.067046Z
Last Failed WAL: 000000010000000000000001 @ 2026-02-27T11:48:57.432193Z
Streaming Replication status
Replication Slots Enabled
Name Sent LSN Write LSN Flush LSN Replay LSN Write Lag Flush Lag Replay Lag State Sync State Sync Priority Replication Slot
---- -------- --------- --------- ---------- --------- --------- ---------- ----- ---------- ------------- ----------------
n8n-db-2 0/7000000 0/7000000 0/7000000 0/7000000 00:00:00 00:00:00 00:00:00 streaming async 0 active
Instances status
Name Current LSN Replication role Status QoS Manager Version Node
---- ----------- ---------------- ------ --- --------------- ----
n8n-db-1 0/7000000 Primary OK BestEffort 1.27.1 nuc242
n8n-db-2 0/7000000 Standby (async) OK BestEffort 1.27.1 nuc243
Plugins status
Name Version Status Reported Operator Capabilities
---- ------- ------ ------------------------------
barman-cloud.cloudnative-pg.io 0.9.0 N/A Reconciler Hooks, Lifecycle Service
Lessons Learned
Problem
Root Cause
Fix
Pod stuck Pending
PVC used ReadWriteMany with local-path
Change to ReadWriteOnce
n8n CrashLoopBackOff
DB password mismatch between Vault paths
Align both secrets to the same Vault key
WAL archiving Expected empty archive
destinationPath pointed to bucket root
Add cluster-specific subfolder to path
Result
A fully operational n8n instance with:
PostgreSQL HA (primary + standby, streaming replication)
Continuous WAL archiving to MinIO (point-in-time recovery)