Skip to main content

Installing n8n

n8n deploys like any other platform component: an Installation CR in your GitHub org's .platform repo references the n8n Installable and names the destination cluster. The installation-operator reconciles it into an ArgoCD Application. Everything below is either a capability the cluster must already have (Assumptions) or a component you add before n8n (Prerequisites).


Before you begin

Assumptions — platform capabilities that must already be present

These are baseline p6m platform capabilities. n8n depends on them but does not install them. If any are missing, contact your platform team rather than adding them yourself.

CapabilityWhy n8n needs itManaged by
Crossplane + shared compositionsThe XPostgresDatabase composition fulfills n8n's PostgresDatabase claim.Control plane
Crossplane providers — provider-sql + provider-kubernetesSupply the postgresql.sql.crossplane.io and kubernetes.crossplane.io APIs the composition binds to.Control plane / org-operator
External Secrets OperatorBridges Key Vault entries (DB, Redis, license, SMTP password) into Kubernetes Secrets.Platform baseline
installation-operatorReconciles the Installation CR into an ArgoCD Application.Control plane
SSO ext-authz (oauth2-proxy + Auth0, or Keycloak)Gates the n8n editor / REST / UI at the gateway.Platform baseline
Crossplane providers are control-plane managed — do not self-install

If provider-sql or provider-kubernetes are missing, do not add a crossplane-providers Installation to your .platform repo. Installing the provider bundle per-cluster fights the org-operator over field ownership — it flips managed fields back to invalid values and blocks the providers from reconciling. The provider bundle must be installed and managed through the control plane. If the SQL/Kubernetes provider APIs are unavailable on your cluster, raise it with the platform team.

Prerequisites — install these first

These are ordinary Installables. Add them to your org's .platform repo (or confirm they're already Healthy on the cluster) before applying the n8n Installation. Cluster names follow the {solution}-{env}-{region} convention — see Installation → Discovering Cluster Names.

InstallableProvidesn8n dependency
kedaEvent-driven autoscaling.Worker autoscaling on Redis queue depth.
istio-gateway-apiThe public-oidc (SSO-gated) and public-open (unauthenticated) Gateways.n8n's HTTPRoutes attach to these listeners.
cnpgA CloudNativePG cluster and the cnpg-cluster provider-sql ProviderConfig.The PostgresDatabase claim binds to this ProviderConfig.

KEDA and the Gateways are stock installs with no overrides:

apiVersion: p6m.dev/v1alpha1
kind: Installation
metadata:
name: keda
spec:
installableRef:
kind: Installable
name: keda
namespace: installables
destinations:
- clusterRef:
name: acme-corp-dev-eastus # your cluster
apiVersion: p6m.dev/v1alpha1
kind: Installation
metadata:
name: istio-gateway-api
spec:
installableRef:
kind: Installable
name: istio-gateway-api
namespace: installables
destinations:
- clusterRef:
name: acme-corp-dev-eastus

CNPG enables both the shared cluster and the provider-sql ProviderConfig. The cluster block is templated from cluster info (template variables); the two flags below are the parts that matter for n8n:

apiVersion: p6m.dev/v1alpha1
kind: Installation
metadata:
name: cnpg
spec:
installableRef:
kind: Installable
name: cnpg
namespace: installables
overrides:
source:
template: |
p6m-cnpg-cluster:
enabled: true # the CloudNativePG cluster
# ... cloud/backup config templated from cluster info ...
providerSql:
enabled: true # the cnpg-cluster ProviderConfig the claim binds to
destinations:
- clusterRef:
name: acme-corp-dev-eastus

Install n8n

With the prerequisites Healthy, add the n8n Installation. This minimal form gives you an SSO-gated editor on n8n.{{ dns.domainName }}, a platform-provisioned Postgres, in-cluster Redis, and KEDA worker autoscaling:

apiVersion: p6m.dev/v1alpha1
kind: Installation
metadata:
name: n8n
spec:
installableRef:
kind: Installable
name: n8n
namespace: installables
destinations:
- clusterRef:
name: acme-corp-dev-eastus
overrides:
source:
template: |
n8n:
persistence:
# RWX so rolling updates don't block on a single-writer volume.
# The cluster default is RWO-only managed disk — use a ReadWriteMany class.
storageClassName: azurefile-csi
keda:
enabled: true # worker autoscaling on Redis queue depth

Open a PR against your .platform repo and merge. The {github-org}-installations root application picks it up; watch the child Application reach Synced / Healthy in ArgoCD.


After installing

1. External Secrets reconcile. If you enabled any Key-Vault-backed feature (license, SMTP, bring-your-own connections), confirm the secrets synced:

kubectl -n n8n get externalsecret
# STATUS should be Ready. A SecretSyncedError almost always means the Key Vault
# entry named in your overrides doesn't exist yet — create it, then it self-heals.

2. Pods come up. Expect a main pod and worker pods (plus the CNPG database pods in cnpg-system):

kubectl -n n8n get pods

3. First sign-in. Reach the UI at https://n8n.<your-domain>/. The first user to sign in becomes the instance owner — make sure that's your admin. They invite the rest of the team from inside n8n. (Without SMTP, invite and password-reset links aren't emailed — the owner copies them manually. Enable SMTP below to have n8n send them.)


Optional features

All off by default. Enable per install by adding to the overrides.source.template.

SMTP — user-management email

Sends invite and password-reset emails. Requires externalSecrets.enabled: true. Seed the Key Vault entry first — the password is a hard dependency and n8n won't start without it:

az keyvault secret set --vault-name <your-vault> --name n8n-smtp-password --value "<api-key>"
externalSecrets:
enabled: true
storeConfig:
vaultName: platform-{{ azure.uniqueSuffix }}
enableAccessManagement: true
smtp:
enabled: true
host: smtp.sendgrid.net
port: 587
user: apikey
sender: n8n@example.com # must be a domain-authenticated sender
ssl: false
startTls: true
passwordKeyVaultEntry: n8n-smtp-password

External webhooks

By default n8n's only route is SSO-gated, so external callers (Stripe, GitHub, …) POSTing to a Webhook node get bounced to SSO. Enabling this adds a second HTTPRoute on the unauthenticated public-open Gateway, exposing only the webhook/form/mcp trigger paths on a dedicated subdomain:

gatewayApi:
webhook:
enabled: true
hostname: n8n-webhooks.{{ dns.domainName }} # must differ from the editor host
public-open has no SSO

Every Webhook node you expose on this route must set its own node-level authentication (Header, Basic, JWT, or an HMAC-verified signature). Without it, the endpoint is open to the internet.

Worker autoscaling

KEDA is enabled in the base install above. It scales workers on Redis queue depth — defaults are 2–20 workers at roughly 5 waiting jobs per worker. Tune min/max under n8n.keda.worker.


Troubleshooting

n8n CrashLoops with permission denied for schema public

A known gap in the shared Postgres composition: the app role can connect but isn't the owner of the database, so on PostgreSQL 15+ it can't CREATE in the public schema and first-boot migrations fail. Until the composition grants schema-level privileges, apply the grant out-of-band once the PostgresDatabase claim is READY, then restart the n8n pods. Ask your platform team to run the GRANT ... ON SCHEMA public against the CNPG primary — it requires cluster-superuser access.

An ExternalSecret is stuck SecretSyncedError

The Key Vault entry referenced in your overrides doesn't exist (or the name is misspelled). Create it with az keyvault secret set …; ESO retries automatically.

Never rotate the encryption key

N8N_ENCRYPTION_KEY decrypts every stored credential. The wrapper stores it in an immutable Secret and never rotates it by design — rotating it destroys every encrypted credential in the database. A genuine rotation is a from-scratch migration, not a config change.

main pod stuck Pending after a rapid uninstall + reinstall

The persistent volume claim from the previous install can still be Terminating (finalizer delay), which silently drops the new claim. Re-run the install to re-reconcile — the volume is recreated on the second pass. It backs only n8n's local cache; workflow and credential data live in Postgres and are unaffected.