apiVersion: apps/v1 kind: Deployment metadata: name: n8n namespace: ${NAMESPACE} spec: # One replica. n8n's queue mode splits the editor from workers and needs Redis # to do it; without that, a second replica is two schedulers racing over the # same cron triggers. See the README before scaling this. replicas: 1 strategy: # The volume is ReadWriteOnce, so a rolling update deadlocks: the new pod # waits for a volume the old pod has not released. type: Recreate selector: matchLabels: app: n8n template: metadata: labels: app: n8n spec: securityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 1000 initContainers: - name: wait-for-postgres image: ${POSTGRES_IMAGE} command: - sh - -c - | until pg_isready -h n8n-postgres -U n8n -d n8n; do echo "waiting for postgres..."; sleep 3 done containers: - name: n8n image: ${N8N_IMAGE} ports: - name: http containerPort: 5678 env: - name: DB_TYPE value: postgresdb - name: DB_POSTGRESDB_HOST value: n8n-postgres - name: DB_POSTGRESDB_PORT value: "5432" - name: DB_POSTGRESDB_DATABASE value: n8n - name: DB_POSTGRESDB_USER value: n8n - name: DB_POSTGRESDB_PASSWORD valueFrom: secretKeyRef: name: n8n-secrets key: DB_PASSWORD # Encrypts stored credentials. Held in Vault, never regenerated — # a new key does not re-encrypt, it orphans everything already # stored. - name: N8N_ENCRYPTION_KEY valueFrom: secretKeyRef: name: n8n-secrets key: N8N_ENCRYPTION_KEY # A private CA to trust, so a workflow can call an internal # endpoint over TLS without anyone disabling verification to make it # work. n8n is Node, hence this variable rather than a trust store. # Provided by the deployment; an empty ConfigMap disables it in # practice, since the file will not exist and Node falls back to its # own bundle. - name: NODE_EXTRA_CA_CERTS value: /etc/extra-ca/ca.crt envFrom: - configMapRef: name: n8n-config # Whatever else this deployment's workflows need in their # environment. Provided by the deployment, not by this repo: an # empty Secret of that name is a valid answer. # # Two slots rather than one, because a deployment's environment # usually comes from more than one owner — credentials the platform # holds, and credentials a workflow's own estate holds. # # `optional: true` covers a Secret that does not exist; the `:=` # defaults cover the *variable* not being set. Both are needed, and # only the first was here: Flux substitutes in strict mode, so an # unused slot failed the whole build rather than being skipped, and # every later change to this repo silently stopped reaching the # cluster. - secretRef: name: ${EXTRA_ENV_SECRET:=n8n-extra-env} optional: true - secretRef: name: ${EXTRA_ENV_SECRET_2:=n8n-extra-env-2} optional: true volumeMounts: - name: data mountPath: /home/node/.n8n - name: extra-ca mountPath: /etc/extra-ca readOnly: true readinessProbe: httpGet: path: /healthz port: http initialDelaySeconds: 15 periodSeconds: 10 livenessProbe: httpGet: path: /healthz port: http initialDelaySeconds: 60 periodSeconds: 20 resources: requests: cpu: 100m memory: 512Mi limits: cpu: "2" memory: 2Gi securityContext: allowPrivilegeEscalation: false capabilities: drop: ["ALL"] volumes: - name: data persistentVolumeClaim: claimName: n8n-data - name: extra-ca configMap: name: ${EXTRA_CA_CONFIGMAP} optional: true