N8N

🚀Installation

Environment
Install By

Preliminary

1. Kubernetes is installed; if not, check 🔗link


2. Helm is installed; if not, check 🔗link


3. ArgoCD is installed; if not, check 🔗link


  1. Database postgresql has been installed, if not check 🔗link


1.prepare `n8n-middleware-credentials.yaml`

Details
kubectl get namespaces n8n > /dev/null 2>&1 || kubectl create namespace n8n
N8N_PASSWORD=$(kubectl -n database get secret postgresql-credentials -o jsonpath='{.data.password}' | base64 -d)
kubectl -n n8n create secret generic n8n-middleware-credential \
--from-literal=postgres-password="${N8N_PASSWORD}"

2.prepare `deploy-n8n.yaml`

Details
kubectl -n argocd apply -f - <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: n8n
spec:
  project: default
  source:
    repoURL: https://community-charts.github.io/helm-charts
    targetRevision: 1.16.36
    helm:
      releaseName: n8n
      values: |
        global:
          security:
            allowInsecureImages: true
        image:
          repository: n8nio/n8n
        log:
          level: info
        encryptionKey: "ay-dev-n8n"
        timezone: Asia/Shanghai
        db:
          type: postgresdb
        externalPostgresql:
          host: postgresql-hl.database.svc.cluster.local
          port: 5432
          username: "n8n"
          database: "n8n"
          existingSecret: "n8n-middleware-credential"
        main:
          count: 1
          extraEnvVars:
            "N8N_BLOCK_ENV_ACCESS_IN_NODE": "false"
            "N8N_FILE_SYSTEM_ALLOWED_PATHS": "/home/node/.n8n-files"
            "EXECUTIONS_TIMEOUT": "300"
            "EXECUTIONS_TIMEOUT_MAX": "600"
            "DB_POSTGRESDB_POOL_SIZE": "10"
            "CACHE_ENABLED": "true"
            "N8N_CONCURRENCY_PRODUCTION_LIMIT": "5"
            "NODE_TLS_REJECT_UNAUTHORIZED": "0"
            "N8N_SECURE_COOKIE": "false"
            "WEBHOOK_URL": "https://webhook.n8n.dev.72602.online"
            "QUEUE_BULL_REDIS_TIMEOUT_THRESHOLD": "60000"
            "N8N_COMMUNITY_PACKAGES_ENABLED": "true"
            "N8N_GIT_NODE_DISABLE_BARE_REPOS": "true"
            "N8N_LICENSE_AUTO_RENEW_ENABLED": "true"
            "N8N_LICENSE_RENEW_ON_INIT": "true"
          persistence:
            enabled: true
            accessMode: ReadWriteOnce
            storageClass: "local-path"
            size: 50Gi
          volumes:
            - name: downloads-volume
              hostPath:
                path: /home/aaron/Downloads
                type: DirectoryOrCreate
          volumeMounts:
            - name: downloads-volume
              mountPath: /home/node/.n8n-files
          resources:
            requests:
              cpu: 1000m
              memory: 1024Mi
            limits:
              cpu: 2000m
              memory: 2048Mi
        worker:
          mode: queue
          count: 2
          waitMainNodeReady:
            enabled: false
          extraEnvVars:
            "N8N_FILE_SYSTEM_ALLOWED_PATHS": "/home/node/.n8n-files"
            "EXECUTIONS_TIMEOUT": "300"
            "EXECUTIONS_TIMEOUT_MAX": "600"
            "DB_POSTGRESDB_POOL_SIZE": "5"
            "QUEUE_BULL_REDIS_TIMEOUT_THRESHOLD": "60000"
            "N8N_COMMUNITY_PACKAGES_ENABLED": "true"
            "N8N_GIT_NODE_DISABLE_BARE_REPOS": "true"
            "N8N_LICENSE_AUTO_RENEW_ENABLED": "true"
            "N8N_LICENSE_RENEW_ON_INIT": "true"
          persistence:
            enabled: true
            accessMode: ReadWriteOnce
            storageClass: "local-path"
            size: 50Gi
          volumes:
            - name: downloads-volume
              hostPath:
                path: /home/aaron/Downloads
                type: DirectoryOrCreate
          volumeMounts:
            - name: downloads-volume
              mountPath: /home/node/.n8n-files
          resources:
            requests:
              cpu: 500m
              memory: 1024Mi
            limits:
              cpu: 1000m
              memory: 2048Mi
        nodes:
          builtin:
            enabled: true
            modules:
              - crypto
              - fs
          external:
            allowAll: true
            packages:
              - n8n-nodes-globals
        npmRegistry:
          enabled: true
          url: http://mirrors.cloud.tencent.com/npm/
        redis:
          enabled: true
          image:
            registry: m.daocloud.io/docker.io
            repository: bitnamilegacy/redis
          master:
            resourcesPreset: "small"
            persistence:
              enabled: true
              accessMode: ReadWriteOnce
              storageClass: "local-path"
              size: 10Gi
        ingress:
          enabled: true
          className: nginx
          annotations:
            kubernetes.io/ingress.class: nginx
            cert-manager.io/cluster-issuer: self-signed-ca-issuer
            nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"
            nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
            nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
            nginx.ingress.kubernetes.io/proxy-body-size: "50m"
            nginx.ingress.kubernetes.io/upstream-keepalive-connections: "50"
            nginx.ingress.kubernetes.io/upstream-keepalive-timeout: "60"
            nginx.ingress.kubernetes.io/enable-cors: "true"
            nginx.ingress.kubernetes.io/cors-allow-origin: "https://webhook.n8n.dev.72602.online:32443"
            nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, OPTIONS, PUT, DELETE"
            nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization"
            nginx.ingress.kubernetes.io/cors-allow-credentials: "true"
          hosts:
            - host: n8n.dev.72602.online
              paths:
                - path: /
                  pathType: Prefix
            - host: webhook.n8n.dev.72602.online
              paths:
                - path: /
                  pathType: Prefix
          tls:
          - hosts:
            - n8n.dev.72602.online
            - webhook.n8n.dev.72602.online
            secretName: n8n.dev.72602.online-tls
        webhook:
          mode: queue
          url: "https://webhook.n8n.dev.72602.online"
          autoscaling:
            enabled: false
          waitMainNodeReady:
            enabled: true
          resources:
            requests:
              cpu: 200m
              memory: 256Mi
            limits:
              cpu: 512m
              memory: 512Mi
    chart: n8n
  destination:
    server: https://kubernetes.default.svc
    namespace: n8n
  syncPolicy:
    syncOptions:
      - CreateNamespace=true
      - ApplyOutOfSyncOnly=false

EOF

3.sync by argocd

Details
argocd app sync argocd/n8n
Using AY Helm Mirror

for more information, you can check 🔗https://github.com/AaronYang0628/helm-chart-mirror

helm repo add ay-helm-mirror https://aaronyang0628.github.io/helm-chart-mirror/charts
  helm repo update
  helm install ay-helm-mirror/chart-name --generate-name --version a.b.c
Using AY ACR Image Mirror
Using DaoCloud Mirror
Install By

Preliminary

1. Kubernetes is installed; if not, check 🔗link


2. Helm is installed; if not, check 🔗link


3. ArgoCD is installed; if not, check 🔗link


  1. Database postgresql has been installed, if not check 🔗link


1.verify retained credentials and storage

Details
kubectl get namespace n8n
kubectl -n n8n get secret n8n-middleware-credential n8n-encryption-key-existing
kubectl -n n8n get pvc
Important

The live credentials and PVCs are retained state. Do not delete, recreate, or replace them when updating the Argo CD Application.

2.prepare `deploy-n8n.yaml`

Details
kubectl -n argocd apply -f - <<'EOF'
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: n8n
  namespace: argocd
spec:
  project: default
  ignoreDifferences:
    - group: ""
      kind: Secret
      name: n8n-redis
      namespace: n8n
      jsonPointers:
        - /data/redis-password
    - group: apps
      kind: StatefulSet
      name: n8n-redis-master
      namespace: n8n
      jqPathExpressions:
        - .spec.template.metadata.annotations."checksum/secret"
  source:
    repoURL: https://community-charts.github.io/helm-charts
    targetRevision: 1.16.36
    chart: n8n
    helm:
      releaseName: n8n
      values: |
        global:
          security:
            allowInsecureImages: true
        image:
          repository: m.daocloud.io/docker.io/n8nio/n8n
        log:
          level: info
        existingEncryptionKeySecret: n8n-encryption-key-existing
        timezone: Asia/Shanghai
        db:
          type: postgresdb
        externalPostgresql:
          host: postgresql-hl.database.svc.cluster.local
          port: 5432
          username: "n8n"
          database: "n8n"
          existingSecret: "n8n-middleware-credential"
        main:
          count: 1
          editorBaseUrl: "https://n8n.72602.space"
          extraEnvVars:
            HTTP_PROXY: "http://192.168.0.25:17890"
            HTTPS_PROXY: "http://192.168.0.25:17890"
            NO_PROXY: "registry.npmjs.org,npmjs.org,npmmirror.com,registry.npmmirror.com"
            no_proxy: "registry.npmjs.org,npmjs.org,npmmirror.com,registry.npmmirror.com"
            NPM_CONFIG_REGISTRY: "https://registry.npmmirror.com"
            N8N_BLOCK_ENV_ACCESS_IN_NODE: "false"
            N8N_FILE_SYSTEM_ALLOWED_PATHS: "/data"
            EXECUTIONS_TIMEOUT: "300"
            EXECUTIONS_TIMEOUT_MAX: "600"
            DB_POSTGRESDB_POOL_SIZE: "10"
            CACHE_ENABLED: "true"
            N8N_CONCURRENCY_PRODUCTION_LIMIT: "5"
            NODE_TLS_REJECT_UNAUTHORIZED: "0"
            N8N_SECURE_COOKIE: "false"
            QUEUE_BULL_REDIS_TIMEOUT_THRESHOLD: "60000"
            N8N_COMMUNITY_PACKAGES_ENABLED: "true"
            N8N_GIT_NODE_DISABLE_BARE_REPOS: "true"
            N8N_LICENSE_AUTO_RENEW_ENABLED: "true"
            N8N_LICENSE_RENEW_ON_INIT: "true"
          persistence:
            enabled: true
            accessMode: ReadWriteOnce
            storageClass: "local-path"
            size: 5Gi
          volumes:
            - name: downloads-volume
              hostPath:
                path: /run/media/aaron/DATA
                type: DirectoryOrCreate
          volumeMounts:
            - name: downloads-volume
              mountPath: /data
          resources:
            requests:
              cpu: 1000m
              memory: 1024Mi
            limits:
              cpu: 2000m
              memory: 2048Mi
        worker:
          mode: queue
          count: 2
          waitMainNodeReady:
            enabled: false
          extraEnvVars:
            HTTP_PROXY: "http://192.168.0.25:17890"
            HTTPS_PROXY: "http://192.168.0.25:17890"
            NO_PROXY: "registry.npmjs.org,npmjs.org,npmmirror.com,registry.npmmirror.com"
            no_proxy: "registry.npmjs.org,npmjs.org,npmmirror.com,registry.npmmirror.com"
            NPM_CONFIG_REGISTRY: "https://registry.npmmirror.com"
            N8N_FILE_SYSTEM_ALLOWED_PATHS: "/data"
            EXECUTIONS_TIMEOUT: "300"
            EXECUTIONS_TIMEOUT_MAX: "600"
            DB_POSTGRESDB_POOL_SIZE: "5"
            QUEUE_BULL_REDIS_TIMEOUT_THRESHOLD: "60000"
            N8N_COMMUNITY_PACKAGES_ENABLED: "true"
            N8N_GIT_NODE_DISABLE_BARE_REPOS: "true"
            N8N_LICENSE_AUTO_RENEW_ENABLED: "true"
            N8N_LICENSE_RENEW_ON_INIT: "true"
          persistence:
            enabled: true
            accessMode: ReadWriteOnce
            storageClass: "local-path"
            size: 50Gi
          volumes:
            - name: downloads-volume
              hostPath:
                path: /run/media/aaron/DATA
                type: DirectoryOrCreate
          volumeMounts:
            - name: downloads-volume
              mountPath: /data
          resources:
            requests:
              cpu: 500m
              memory: 1024Mi
            limits:
              cpu: 1000m
              memory: 2048Mi
        nodes:
          builtin:
            enabled: true
            modules:
              - crypto
              - fs
          external:
            allowAll: true
            packages:
              - n8n-nodes-globals
              - n8n-nodes-wechat-formatter
        npmRegistry:
          enabled: true
          url: https://registry.npmmirror.com
        redis:
          enabled: true
          image:
            registry: m.daocloud.io/docker.io
            repository: bitnamilegacy/redis
          master:
            resourcesPreset: "small"
            persistence:
              enabled: true
              accessMode: ReadWriteOnce
              storageClass: "local-path"
              size: 50Gi
        ingress:
          enabled: true
          className: nginx
          annotations:
            kubernetes.io/ingress.class: nginx
            cert-manager.io/cluster-issuer: lets-encrypt
            nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"
            nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
            nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
            nginx.ingress.kubernetes.io/proxy-body-size: "50m"
            nginx.ingress.kubernetes.io/upstream-keepalive-connections: "50"
            nginx.ingress.kubernetes.io/upstream-keepalive-timeout: "60"
          hosts:
            - host: n8n.72602.space
              paths:
                - path: /
                  pathType: Prefix
            - host: webhook.n8n.72602.space
              paths:
                - path: /
                  pathType: Prefix
          tls:
            - hosts:
                - n8n.72602.space
                - webhook.n8n.72602.space
              secretName: n8n.72602.space-tls
        webhook:
          mode: queue
          url: "https://webhook.n8n.72602.space"
          autoscaling:
            enabled: false
          waitMainNodeReady:
            enabled: true
          resources:
            requests:
              cpu: 200m
              memory: 256Mi
            limits:
              cpu: 512m
              memory: 512Mi
  destination:
    server: https://kubernetes.default.svc
    namespace: n8n
  syncPolicy:
    syncOptions:
      - CreateNamespace=true
      - ApplyOutOfSyncOnly=false
      - RespectIgnoreDifferences=true
EOF

3.sync by argocd

Details
argocd app sync argocd/n8n
Using AY Helm Mirror

for more information, you can check 🔗https://github.com/AaronYang0628/helm-chart-mirror

helm repo add ay-helm-mirror https://aaronyang0628.github.io/helm-chart-mirror/charts
  helm repo update
  helm install ay-helm-mirror/chart-name --generate-name --version a.b.c
Using AY ACR Image Mirror
Using DaoCloud Mirror

🛎️FAQ

Q1: n8n cannot connect to PostgreSQL

Symptom

  • n8n Pod starts but keeps retrying DB connection.

Check

kubectl -n n8n get pods
kubectl -n n8n logs deploy/n8n -c n8n --tail=100
kubectl -n n8n get secret n8n-middleware-credential -o yaml
kubectl -n database get svc postgresql-hl

Fix

  • Confirm secret key name matches chart expectation (postgres-password).
  • Confirm DB host/port/user/database in values are correct.
  • Ensure PostgreSQL is healthy before syncing n8n.

Expected

  • n8n Pod reaches Running and UI becomes accessible.
Q2: 72602 Argo CD reports Redis Secret and checksum drift

Symptom

  • Secret/n8n-redis and StatefulSet/n8n-redis-master repeatedly report drift even though Redis is healthy.

Root cause

  • The Redis subchart renders a generated password when no fixed password is supplied. A new desired render changes /data/redis-password and the derived pod-template checksum/secret without indicating live credential corruption.

Fix

  • Preserve all live n8n credentials and PVCs. Do not delete, recreate, or replace them to resolve this drift.
  • Keep ignoreDifferences limited to /data/redis-password and the Redis StatefulSet’s checksum/secret, with RespectIgnoreDifferences=true.
  • Review the remaining diff, then sync the Application only when it contains the intended values change.
argocd app diff argocd/n8n --insecure --grpc-web --refresh
argocd app sync argocd/n8n --insecure --grpc-web
argocd app get argocd/n8n --insecure --grpc-web
kubectl -n n8n rollout status deployment/n8n --timeout=300s
kubectl -n n8n rollout status statefulset/n8n-redis-master --timeout=300s

Rollback

  • Remove only the two ignoreDifferences entries and RespectIgnoreDifferences=true, then reapply the Application. This restores drift reporting without changing the Secret or PVC.

Expected

  • Argo CD reports Synced and Healthy; n8n and Redis remain Ready, and the existing PVCs remain Bound.
Q3: Community nodes fail — “Unrecognized node type” after pod restart

Symptom

  • Webhook 或 workflow 报 Unrecognized node type: n8n-nodes-xxx
  • 社区包在 Pod 重启后消失

Root cause

  • Helm chart 内置 initContainer 使用 node:20-alpine,缺少 Python
  • 含 native 依赖的包(如 isolated-vm)npm install 失败,导致所有社区包未安装
  • 对 webhook pod,chart 默认不提供社区节点 volume/initContainer

Fix (permanent, survives ArgoCD sync)

核心思路:chart 内置 initContainer 空跑,自定义 initContainer 注入到 `main/worker/webhook.initContainers`。

nodes:
  external:
    packages: []   # 清空 chart 内置包列表,避免 native build 失败
main:
  volumes:
    - name: community-node-modules
      emptyDir: {}
  volumeMounts:
    - name: community-node-modules
      mountPath: /home/node/.n8n/nodes
  initContainers:
    - name: npm-install-community
      image: node:20-alpine
      command: ['/bin/sh', '-c']
      args:
        - |
          export COMMUNITY_PACKAGES="n8n-nodes-globals n8n-nodes-wechat-formatter n8n-nodes-browserless-api"
          mkdir -p /nodesdata/nodes
          echo "$COMMUNITY_PACKAGES" | sha256sum > /nodesdata/nodes/packages.hash.new
          if [ ! -f /nodesdata/nodes/packages.hash ] || ! cmp /nodesdata/nodes/packages.hash /nodesdata/nodes/packages.hash.new; then
            npm install --loglevel info --no-save --ignore-scripts $COMMUNITY_PACKAGES --prefix /nodesdata/nodes
            mv /nodesdata/nodes/packages.hash.new /nodesdata/nodes/packages.hash
          fi
      env:
        - name: HTTP_PROXY
          value: http://192.168.0.25:17890
        - name: HTTPS_PROXY
          value: http://192.168.0.25:17890
      volumeMounts:
        - name: community-node-modules
          mountPath: /nodesdata/nodes
      securityContext:
        runAsUser: 1000
        runAsGroup: 1000
        runAsNonRoot: true
# worker 和 webhook 同样添加上述 volumes/volumeMounts/initContainers
worker:
  volumes: ...
  volumeMounts: ...
  initContainers: ...
webhook:
  volumes: ...
  volumeMounts: ...
  initContainers: ...

--ignore-scripts 是关键:跳过 isolated-vm 等 native 依赖编译,node:20-alpine 不含 Python 也能装。

同上,只需修改: - `HTTP_PROXY`/`HTTPS_PROXY` 按 ZJ 集群代理地址填写 - `COMMUNITY_PACKAGES` 按需调整

Manual emergency fix (quick)

# 在每个 Pod 内手动安装
kubectl exec -n n8n deploy/n8n -- sh -c \
  "cd /home/node/.n8n/nodes && npm install --ignore-scripts n8n-nodes-globals n8n-nodes-wechat-formatter n8n-nodes-browserless-api"
kubectl exec -n n8n statefulset/n8n-worker -- sh -c \
  "cd /home/node/.n8n/nodes && npm install --ignore-scripts n8n-nodes-globals n8n-nodes-wechat-formatter n8n-nodes-browserless-api"
# 重启 n8n 加载新节点
kubectl delete pods -n n8n -l app.kubernetes.io/component=main
kubectl delete pods -n n8n -l app.kubernetes.io/component=worker

Expected

  • kubectl exec -n n8n deploy/n8n -- ls /home/node/.n8n/nodes/node_modules/ | grep n8n 有输出
  • Webhook 返回正常响应(非 Unrecognized node type