> ## Documentation Index
> Fetch the complete documentation index at: https://controlplanecorporation-majid-docs-content-expansion.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# n8n

> Deploy n8n on Control Plane using the Template Catalog. Workflow automation backed by a highly available PostgreSQL cluster. Covers the two prerequisite secrets — the owner login as a bcrypt hash and the credential-encryption key — plus database modes, webhooks on the canonical endpoint, and database backups.

## Overview

n8n is a workflow automation platform (fair-code, distributed under the [Sustainable Use License](https://docs.n8n.io/sustainable-use-license/)). This template deploys an n8n instance backed by a highly available PostgreSQL cluster by default. The editor, REST API, and webhook endpoints are served on one public HTTPS endpoint, and the instance owner account is pre-provisioned at install — there is never an unauthenticated setup page.

Both the owner login and the credential-encryption key come from secrets you create **before** installing. Neither passes through Helm values, which matters here because the n8n login form sits on a public endpoint by default.

<Warning>
  **Upgrading an install created with `1.0.0` or `1.0.1` is a breaking change, and it can change the password you log in with.** `owner.email` and `owner.password` no longer exist; the owner now comes from a dictionary secret holding `email` and a **bcrypt hash** of the password. Because n8n re-applies the owner from that secret on **every start**, hashing a *new* password during the upgrade silently replaces your current login. Hash the password you are already using. See [Upgrading From 1.0.x](#upgrading-from-1-0-x).
</Warning>

### Architecture

* **n8n** — A single-replica stateful workload serving the editor, REST API, and webhooks on port `5678`. Public URLs are derived from the canonical endpoint at start, so webhook URLs work out of the box.
* **PostgreSQL (HA, default)** — The [postgres-highly-available](/template-catalog/templates/postgres-highly-available) template as a subchart: 3× Patroni PostgreSQL, 3× etcd, and a HAProxy leader-routing endpoint n8n connects through.
* **PostgreSQL (dev/lightweight, optional)** — The single-instance [postgres](/template-catalog/templates/postgres) template instead, for lighter non-HA deployments.
* **Owner managed from a secret** — The owner account is applied from your prerequisite secret at every start, so n8n never opens an unauthenticated setup page and the account cannot be edited from inside the app.

### What Gets Created

* **Stateful n8n Workload** — Single replica serving the editor, API, and webhooks on port `5678`.
* **Database Workloads** — HA mode: a stateful Patroni PostgreSQL workload, a stateful etcd workload, and a standard HAProxy leader-routing workload. Single mode: one stateful PostgreSQL workload.
* **Volume Sets** — 10 GiB persistent storage for n8n instance config and binary execution data (`/home/node/.n8n`), plus the database subchart's own volume sets.
* **Secrets** — A start-script secret that derives public URLs at runtime, plus the database credentials created by the subchart. The owner login and the encryption key are **not** created here — they live in the two secrets you create.
* **Identity & Policy** — A least-privilege policy granting the n8n identity `reveal` on exactly the secrets it uses, including your two pre-created secrets.
* **Cron Backup Workload** *(optional)* — When database backups are enabled.

<Note>
  This template does not create a GVC. You must deploy it into an existing GVC.
</Note>

## Prerequisites

**Two secrets must exist before you install.** Secrets are org-level, so no GVC flag is involved.

<Steps>
  <Step title="Create the encryption key secret">
    An [opaque secret](/guides/create-secret/opaque) with encoding `plain` whose payload is a long random key. n8n encrypts every credential it stores with it:

    ```bash theme={null}
    printf '%s' "$(openssl rand -hex 24)" | \
      cpln secret create-opaque --name my-n8n-encryption-key --encoding plain -f -
    ```

    Set `encryptionKey.secretName` to the name you used, and store a copy of the key somewhere safe outside Control Plane.
  </Step>

  <Step title="Hash the owner password">
    n8n accepts only a **bcrypt hash** for the owner password, never plaintext, so hash it first. `htpasswd -B` emits the `$2y$` form, which n8n accepts:

    ```bash theme={null}
    OWNER_HASH=$(htpasswd -bnBC 10 "" 'your-owner-password' | tr -d ':\n')
    ```
  </Step>

  <Step title="Create the owner secret">
    A [dictionary secret](/guides/create-secret/dictionary) holding exactly the keys `email` and `passwordHash`:

    ```bash theme={null}
    cpln secret create-dictionary --name my-n8n-owner \
      --entry email=admin@example.com \
      --entry passwordHash="$OWNER_HASH"
    ```

    Set `owner.secretName` to the name you used.

    | Key            | What it is                                                                                                                                              |
    | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `email`        | The instance owner's login email.                                                                                                                       |
    | `passwordHash` | A bcrypt hash of the owner password. Its `$` characters survive the round trip into the container intact — verified byte for byte on a live deployment. |
  </Step>

  <Step title="Read a secret back later">
    `-o yaml` is required; without it the command prints the secret's metadata table rather than its contents:

    ```bash theme={null}
    cpln secret reveal my-n8n-owner -o yaml
    ```
  </Step>
</Steps>

<Warning>
  **The owner is re-applied from the secret on every start** (`N8N_INSTANCE_OWNER_MANAGED_BY_ENV`). Two consequences, both measured on a live instance:

  * **Editing the secret and restarting the workload is how you rotate the password.** After a rotation, the old password returned `401` and the new one `200`.
  * **The account cannot be changed from inside n8n.** A password change through the API is refused with `403 This account is managed via environment variables and cannot be modified through the API`.

  So whatever is in the secret *is* the login, at every restart. Put the password you actually want there.
</Warning>

<Warning>
  Losing the encryption key makes every credential n8n has stored permanently undecryptable, and the key must never change after first boot — n8n fails to start on a key mismatch. Back it up before installing.
</Warning>

<Warning>
  **A missing prerequisite secret wedges the install rather than failing it.** `cpln helm install` still exits 0 and reports success, the resources are created, and the workload then never starts. Because the container never ran, `cpln logs` returns **zero lines**, which reads as a broken platform rather than a missing prerequisite.

  The only diagnostic is `status.versions[].message`, which names the missing secret:

  ```bash theme={null}
  cpln workload get-deployments RELEASE_NAME-n8n --gvc GVC_NAME -o yaml
  ```

  ```text theme={null}
  The secret my-n8n-owner no longer exists. Workload updates are
  paused until the secret is added or the reference to the secret removed.
  ```

  It is **`get-deployments`** — plain `cpln workload get` has no `versions` key at all. Creating the missing secret clears the wedge on its own, but slowly: recovery across the catalog has been measured between **5.5 and 10.5 minutes**, so poll rather than time-boxing it. A forced redeployment shortcuts it to roughly 90 seconds.
</Warning>

For optional database backups, you also need a bucket and access setup for one of the supported providers — see [Backing Up](#backing-up).

Once both secrets exist, install the template using your preferred method:

<CardGroup cols={2}>
  <Card title="UI" href="/template-catalog/install-manage/ui" icon="laptop">
    Browse, install, and manage templates visually
  </Card>

  <Card title="CLI" href="/template-catalog/install-manage/cli" icon="terminal">
    Manage templates from your terminal
  </Card>

  <Card title="Terraform" href="/template-catalog/install-manage/terraform" icon={<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><g fill-rule="evenodd"><path d="M77.941 44.5v36.836L46.324 62.918V26.082zm0 0" fill="#5c4ee5"/><path d="M81.41 81.336l31.633-18.418V26.082L81.41 44.5zm0 0" fill="#4040b2"/><path d="M11.242 42.36L42.86 60.776V23.941L11.242 5.523zm0 0M77.941 85.375L46.324 66.957v36.82l31.617 18.418zm0 0" fill="#5c4ee5"/></g></svg>}>
    Declare templates in your Terraform configurations
  </Card>

  <Card
    title="Pulumi"
    href="/template-catalog/install-manage/pulumi"
    icon={<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" id="Pulumi-Icon--Streamline-Svg-Logos" height="24" width="24">
    <desc>
        Pulumi Icon Streamline Icon: https://streamlinehq.com
    </desc>
    <path fill="#f26e7e" d="M4.683025 13.3318c0.869125 -0.5018 0.870575 -2.1264 0.003225 -3.62865s-2.27504 -2.313275 -3.1441725 -1.811475C0.672945 8.3935 0.6715 10.0181 1.53885 11.52035c0.86735 1.502275 2.27505 2.313275 3.144175 1.81145Zm0.0052 3.2167c0.86735 1.502275 0.865925 3.126875 -0.003225 3.628675 -0.86915 0.5018 -2.2768275 -0.309225 -3.144175 -1.81145 -0.8673525 -1.50225 -0.8659075 -3.126875 0.003225 -3.628675 0.8691325 -0.5018 2.276825 0.309225 3.144175 1.81145Zm5.922875 3.4243c0.86735 1.50225 0.8659 3.126775 -0.003225 3.62875 -0.869125 0.501775 -2.27685 -0.309325 -3.1442 -1.81155 -0.867325 -1.50225 -0.865875 -3.12685 0.00325 -3.628675 0.869125 -0.5018 2.276825 0.309225 3.144175 1.811475Zm-0.001925 -6.845275c0.86735 1.50225 0.8659 3.12685 -0.003225 3.628675 -0.869125 0.5018 -2.276825 -0.309225 -3.144175 -1.811475 -0.86735 -1.50225 -0.8659 -3.12685 0.003225 -3.62865 0.869125 -0.501825 2.276825 0.3092 3.144175 1.81145Z" stroke-width="0.25"></path>
    <path fill="#8a3391" d="M22.45775 11.524125c0.86725 -1.502225 0.865925 -3.12685 -0.003225 -3.62865 -0.869125 -0.501825 -2.276825 0.3092 -3.144175 1.811475 -0.86735 1.50225 -0.8659 3.126825 0.003225 3.62865 0.869125 0.501825 2.276825 -0.3092 3.144175 -1.811475Zm0.000175 3.2151c0.869075 0.5018 0.870625 2.1264 0.003225 3.62865 -0.86735 1.50225 -2.27505 2.313275 -3.144175 1.81145 -0.869125 -0.5018 -0.870575 -2.126425 -0.003225 -3.62865 0.86735 -1.50225 2.27505 -2.313275 3.144175 -1.81145ZM16.536225 18.157875c0.86915 0.501825 0.8706 2.126425 0.00325 3.628675 -0.86735 1.502125 -2.275075 2.313225 -3.1442 1.81145 -0.869125 -0.50175 -0.870575 -2.126425 -0.003225 -3.62865 0.867375 -1.502275 2.27505 -2.3133 3.144175 -1.811475Zm-0.003325 -6.843775c0.869125 0.5018 0.870575 2.126425 0.003225 3.628675s-2.27505 2.313275 -3.1442 1.811475c-0.869125 -0.501825 -0.870575 -2.126425 -0.003225 -3.628675 0.86735 -1.502275 2.27505 -2.313275 3.1442 -1.811475Z" stroke-width="0.25"></path>
    <path fill="#f7bf2a" d="M15.138225 2.06721c0 1.003615 -1.40625 1.817215 -3.14095 1.817215 -1.7347 0 -3.14095 -0.8136 -3.14095 -1.817215C8.856325 1.06359 10.262575 0.25 11.997275 0.25c1.7347 0 3.14095 0.81359 3.14095 1.81721ZM9.2166 5.482375c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172s1.40625 -1.817225 3.14095 -1.817225c1.7347 0 3.14095 0.8136 3.14095 1.817225Zm8.71005 1.8172c1.7347 0 3.14095 -0.813575 3.14095 -1.8172s-1.40625 -1.817225 -3.14095 -1.817225c-1.7347 0 -3.14095 0.8136 -3.14095 1.817225s1.40625 1.8172 3.14095 1.8172Zm-2.788425 1.605625c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172 0 -1.0036 1.40625 -1.8172 3.14095 -1.8172 1.7347 0 3.14095 0.8136 3.14095 1.8172Z" stroke-width="0.25"></path>
    </svg>}
  >
    Declare templates in your Pulumi programs
  </Card>
</CardGroup>

## Upgrading From 1.0.x

Version `1.1.0` moved the instance owner out of Helm values. `1.0.0` and `1.0.1` shipped the owner's email and password as values, used exactly as written, guarding a login form that is public by default.

|                                      | `1.0.0` / `1.0.1`                              | `1.1.0`                                                                             |
| ------------------------------------ | ---------------------------------------------- | ----------------------------------------------------------------------------------- |
| Owner login                          | `owner.email` and `owner.password` values      | `owner.secretName` — a dictionary secret you create with `email` and `passwordHash` |
| Password format                      | Plaintext                                      | **Bcrypt hash** — n8n rejects plaintext                                             |
| Encryption key                       | `encryptionKey.secretName` prerequisite secret | Unchanged                                                                           |
| `owner.firstName` / `owner.lastName` | Values                                         | Unchanged; still values                                                             |

<Warning>
  **This is the one upgrade in this batch that can change your password.** Everywhere else, an existing credential keeps working untouched. Here it does not: n8n re-applies the owner from the secret at every start, so the hash you put in the secret becomes the login the moment the workload restarts. **Hash the password you are using today**, not a new one — unless changing it is what you intend.
</Warning>

<Warning>
  **A `helm upgrade` that still carries either removed key is rejected before anything is applied.** A real `cpln helm upgrade` carrying the old keys failed at render, created no Helm revision, and left the running release healthy and untouched:

  ```text theme={null}
  n8n: owner.password was removed in 1.1.0. Put a BCRYPT HASH of it in the prerequisite dictionary
  secret named by owner.secretName (key: passwordHash) — hash the password you are using today to
  keep the same login.
  ```

  Leaving `owner.secretName` empty is refused the same way.
</Warning>

To upgrade an existing install:

<Steps>
  <Step title="Create the owner secret">
    Follow [Prerequisites](#prerequisites), hashing **the password you log in with today** so the login does not change.
  </Step>

  <Step title="Drop the removed keys from your values">
    Remove `owner.email` and `owner.password`, and set `owner.secretName` instead. Leave `encryptionKey.secretName`, `owner.firstName`, and `owner.lastName` exactly as they are.
  </Step>

  <Step title="Upgrade">
    The single replica restarts and the editor and webhooks are briefly unavailable — see [Important Notes](#important-notes). Workflows, credentials, and execution data on the volume set are untouched.
  </Step>
</Steps>

## Choosing a Database Mode

Exactly one of the two database modes must be enabled — the chart enforces this at render and fails the install with a clear message otherwise.

|                   | `postgresHA` (default)                                          | `postgres`                             |
| ----------------- | --------------------------------------------------------------- | -------------------------------------- |
| What runs         | 3× Patroni PostgreSQL, 3× etcd, HAProxy leader endpoint         | One single-replica PostgreSQL workload |
| Database failover | Automatic (Patroni leader election)                             | None                                   |
| Footprint         | 8 replicas across 3 workloads (3× Patroni, 3× etcd, 2× HAProxy) | 1 workload                             |
| Best for          | Production                                                      | Development and lightweight installs   |

## Configuration

The default `values.yaml` for this template:

```yaml theme={null}
image: n8nio/n8n:2.29.8

resources:
  minCpu: 250m
  minMemory: 512Mi
  maxCpu: 1000m
  maxMemory: 1Gi

encryptionKey:
  secretName: my-n8n-encryption-key # name of your pre-created opaque secret (see Prerequisites)

owner: # instance owner, re-applied from the secret on every start
  secretName: my-n8n-owner # name of your pre-created dictionary secret (see Prerequisites)
  firstName: Instance
  lastName: Owner

timezone: UTC # IANA timezone for Schedule triggers and $now

volumeset:
  capacity: 10 # GiB (minimum 10) — instance config and binary execution data

publicAccess:
  enabled: true # editor + webhooks on the canonical *.cpln.app HTTPS endpoint

internalAccess: # internal firewall scope (in-GVC webhook callers)
  type: same-gvc # options: none, same-gvc, same-org, workload-list
  workloads: [] # used with workload-list, e.g. //gvc/GVC_NAME/workload/WORKLOAD_NAME

postgresHA: # default: highly available PostgreSQL
  enabled: true
  postgres:
    username: n8n
    password: change-me-n8n-db-password # change before installing
    database: n8n
  replicas: 3
  volumeset:
    capacity: 10 # initial capacity in GiB per replica (minimum is 10)
  backup:
    enabled: false # optional — see Backing Up
    mode: logical # logical or wal-g
    resources:
      cpu: 100m
      memory: 128Mi
    logical:
      image: ghcr.io/controlplane-com/backup-images/postgres-backup:17.1.0
      schedule: "0 2 * * *"
    walg:
      intervalSeconds: 21600
    provider: aws # options: aws, gcp, minio
    aws:
      bucket: n8n-pg-backup-bucket
      region: us-east-1
      cloudAccountName: my-s3-cloud-account
      policyName: n8n-pg-backup-policy
      prefix: postgres/backups
    gcp:
      bucket: n8n-pg-backup-bucket
      cloudAccountName: my-gcs-cloud-account
      prefix: postgres/backups
    minio:
      endpoint: http://my-minio-workload:9000
      bucket: n8n-pg-backup-bucket
      accessKey: my-minio-username
      secretKey: my-minio-password
      prefix: postgres/backups

postgres: # dev/lightweight: single-instance PostgreSQL (disable postgresHA first)
  enabled: false
  credentials: # the chart writes these into the secret named below — nothing for you to create
    username: n8n
    password: change-me-n8n-db-password # change before installing
    database: n8n
  config:
    credentialsSecretName: my-n8n-db-credentials # secret names are org-wide — give each release its own
  volumeset:
    capacity: 10 # initial capacity in GiB (minimum is 10)
  backup:
    enabled: false # optional — see Backing Up
    image: ghcr.io/controlplane-com/backup-images/postgres-backup:18.1.0
    schedule: "0 2 * * *"
    resources:
      cpu: 100m
      memory: 128Mi
    provider: aws # options: aws, gcp, minio
    aws:
      bucket: n8n-pg-backup-bucket
      region: us-east-1
      cloudAccountName: my-s3-cloud-account
      policyName: n8n-pg-backup-policy
      prefix: postgres/backups
    gcp:
      bucket: n8n-pg-backup-bucket
      cloudAccountName: my-gcs-cloud-account
      prefix: postgres/backups
    minio:
      endpoint: http://my-minio-workload:9000
      bucket: n8n-pg-backup-bucket
      credentialsSecretName: my-n8n-minio-credentials # dictionary secret holding accessKey + secretKey
      prefix: postgres/backups
```

### n8n Instance

* `image` — The n8n container image.
* `resources` — CPU and memory for the n8n container. `minCpu` / `minMemory` are the reservation; `maxCpu` / `maxMemory` are the limit.
* `encryptionKey.secretName` — Name of your pre-created opaque secret holding the credential-encryption key. See [Prerequisites](#prerequisites).
* `owner.secretName` — Name of your pre-created dictionary secret holding `email` and `passwordHash`. The owner is re-applied from it on every start, so editing the secret and restarting the workload is how you rotate the login — and the account cannot be edited from inside n8n. See [Prerequisites](#prerequisites).
* `owner.firstName` / `owner.lastName` — Display name for the owner account. These remain ordinary values; only the email and password moved into the secret.
* `timezone` — IANA timezone applied to Schedule triggers and `$now` expressions (e.g. `America/Chicago`).
* `volumeset.capacity` — Volume size in GiB (minimum 10) for instance config and binary execution data.

### Access

* `publicAccess.enabled` — Serve the editor, API, and webhooks on the canonical `*.cpln.app` HTTPS endpoint. Set to `false` for an internal-only instance (external requests are blocked at the edge; in-GVC callers still reach it per `internalAccess`).
* `internalAccess.type` — Internal firewall scope of the n8n workload:

| Type            | Description                                                            |
| --------------- | ---------------------------------------------------------------------- |
| `none`          | No internal access.                                                    |
| `same-gvc`      | Allow access from all workloads in the same GVC (default).             |
| `same-org`      | Allow access from all workloads in the same organization.              |
| `workload-list` | Allow access only from workloads listed in `internalAccess.workloads`. |

### Database

Enable exactly one of `postgresHA` (production, default) or `postgres` (dev/lightweight) — see [Choosing a Database Mode](#choosing-a-database-mode). In both modes, **change the database password before installing** (`postgres.credentials.password`). n8n is wired to the active database automatically — the HAProxy leader endpoint in HA mode, or the single instance directly in dev mode.

If you run more than one release of this template in the same organization, give each its own `postgres.config.credentialsSecretName`. Secret names are organization-wide, so a second release left on the default name is **refused at install** and creates nothing — the first release is unaffected.

<Warning>
  **Template version `1.0.0` did not compact the etcd cluster inside the bundled highly available database**, so etcd's backend grows with time alone and goes read-only once it reaches its 2 GiB quota — after roughly 110 days — taking PostgreSQL failover with it. Only installs running the HA database are affected (`postgresHA.enabled`, the default here); see [etcd History Compaction](/template-catalog/templates/postgres-highly-available#etcd-history-compaction) for the mechanism and the symptoms. Upgrade to `1.0.1` or later to turn compaction on: that stops further growth but cannot shrink a backend that has already grown, and a cluster that has already raised a `NOSPACE` alarm needs operator recovery rather than an upgrade.
</Warning>

## Connecting

| What                               | Value                                                                                                                  |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| Editor / API (public)              | `https://<canonical>.cpln.app` — `status.canonicalEndpoint` of `{release}-n8n`                                         |
| Production webhooks                | `https://<canonical>.cpln.app/webhook/<path>`                                                                          |
| Test webhooks                      | `https://<canonical>.cpln.app/webhook-test/<path>`                                                                     |
| Internal (same GVC)                | `http://{release}-n8n.{gvc}.cpln.local:5678`                                                                           |
| Login                              | The `email` and the password behind `passwordHash` in your `owner.secretName` secret                                   |
| PostgreSQL (internal, HA mode)     | `{release}-postgres-ha-proxy.{gvc}.cpln.local:5432`, credentials in the `{release}-postgres-config` secret             |
| PostgreSQL (internal, single mode) | `{release}-postgres.{gvc}.cpln.local:5432`, credentials in the secret named by `postgres.config.credentialsSecretName` |

### Webhooks

Webhook URLs are derived from the canonical endpoint at startup, so the URLs shown in the editor are the ones external callers use — no extra configuration needed.

<Warning>
  Synchronous webhook responses must finish within 30 seconds — the platform edge times out longer responses with a `504`, although the workflow itself still runs to completion. For long-running workflows, set the Webhook node to respond immediately (or add a Respond to Webhook node early) so the caller gets its response right away while the workflow keeps running.
</Warning>

## Backing Up

Database backups are optional and disabled by default. Enable them with `postgresHA.backup.enabled` or `postgres.backup.enabled` (matching your database mode), and complete the storage setup for your provider **before** installing. The values below are shown under `backup.*` — set them within the enabled database block.

<Tabs>
  <Tab title="AWS S3">
    <Steps>
      <Step title="Create a bucket">
        Create an S3 bucket. Set `backup.aws.bucket` and `backup.aws.region` to match.
      </Step>

      <Step title="Set up a Cloud Account">
        If you do not have one, [create a Cloud Account](https://docs.controlplane.com/guides/create-cloud-account) for your AWS account. Set `backup.aws.cloudAccountName` to its name.
      </Step>

      <Step title="Create a bucket-scoped IAM policy">
        Create an AWS IAM policy with the JSON below (replace `YOUR_BUCKET`), then set `backup.aws.policyName` to the policy's name:

        ```json theme={null}
        {
          "Version": "2012-10-17",
          "Statement": [{
            "Effect": "Allow",
            "Action": [
              "s3:ListBucket",
              "s3:GetBucketLocation",
              "s3:GetObject",
              "s3:PutObject",
              "s3:DeleteObject",
              "s3:AbortMultipartUpload"
            ],
            "Resource": [
              "arn:aws:s3:::YOUR_BUCKET",
              "arn:aws:s3:::YOUR_BUCKET/*"
            ]
          }]
        }
        ```
      </Step>
    </Steps>
  </Tab>

  <Tab title="Google Cloud Storage">
    <Steps>
      <Step title="Create a bucket">
        Create a GCS bucket. Set `backup.gcp.bucket` to its name.
      </Step>

      <Step title="Set up a Cloud Account">
        If you do not have one, [create a Cloud Account](https://docs.controlplane.com/guides/create-cloud-account) for your GCP project. Set `backup.gcp.cloudAccountName` to its name — the backup identity is granted access to the bucket keylessly (no stored credentials).
      </Step>
    </Steps>
  </Tab>

  <Tab title="S3-compatible (MinIO, R2, Wasabi)">
    <Steps>
      <Step title="Create a bucket">
        Create your bucket on the server. Set `backup.minio.bucket` to its name.
      </Step>

      <Step title="Set the endpoint">
        Set `backup.minio.endpoint` to the S3 API address including port. For the `minio` marketplace template deployed in the same GVC, this is `http://WORKLOAD_NAME:9000`.
      </Step>

      <Step title="Set credentials">
        The two backing stores take these differently. In HA mode (`postgresHA`), set `backup.minio.accessKey` and `backup.minio.secretKey` to credentials with access to the bucket. In single-instance mode (`postgres`), those two values were removed: create a [dictionary secret](/guides/create-secret/dictionary) holding the keys `accessKey` and `secretKey`, and set `backup.minio.credentialsSecretName` to its name — see [MinIO backup prerequisites](/template-catalog/templates/postgres#minio) for the exact command.
      </Step>
    </Steps>
  </Tab>
</Tabs>

In HA mode, `backup.mode` selects `logical` (scheduled `pg_dump` via a cron workload) or `wal-g` (continuous WAL archiving). The single-instance mode takes scheduled logical dumps.

## Important Notes

* **Back up the encryption-key secret** — losing it permanently bricks every credential n8n has stored; never change it after first boot (n8n fails to start on a key mismatch).
* **Create both prerequisite secrets before installing.** A missing one wedges the deployment with no log output at all; [Prerequisites](#prerequisites) gives the one command that diagnoses it.
* **The owner secret is authoritative at every restart** — it is not a one-time bootstrap. Changing it changes the login; see [Upgrading From 1.0.x](#upgrading-from-1-0-x).
* **Change the bundled database password** (`postgres.credentials.password`) before installing — it is used exactly as written. It stays a value deliberately: it is internal plumbing between n8n and its own database that nobody types.
* **The n8n main instance is single-replica by upstream design** — the default HA PostgreSQL backend removes the database as a failure point.
* **Upgrades restart the single replica** — expect roughly a minute of editor/webhook downtime per Helm upgrade. The first upgrade after an install also re-applies the bundled database, which can add a couple of minutes.
* **Access changes take time to propagate** — after toggling `publicAccess` or `internalAccess`, re-test over roughly 30 seconds to 5 minutes before concluding the knob is broken.
* **Synchronous webhook responses must finish within 30 seconds** — see [Webhooks](#webhooks).
* **Uninstall deletes the database and n8n volume sets** — all workflows, credentials, and execution data. Enable backups if the data matters.
* **n8n is fair-code under the Sustainable Use License** — free to self-host, but not OSI open source.

## External References

<CardGroup cols={2}>
  <Card title="n8n Documentation" icon="book" href="https://docs.n8n.io/">
    Official n8n documentation
  </Card>

  <Card title="Environment Variables" icon="gear" href="https://docs.n8n.io/deploy/host-n8n/configure-n8n/basic-configuration/use-environment-variables/deployment/">
    n8n deployment environment variables reference
  </Card>

  <Card title="Webhook Endpoints" icon="globe" href="https://docs.n8n.io/deploy/host-n8n/configure-n8n/basic-configuration/use-environment-variables/endpoints/">
    Webhook and endpoint configuration reference
  </Card>

  <Card title="User Management" icon="users" href="https://docs.n8n.io/deploy/host-n8n/configure-n8n/user-management/">
    Owner account and user management guide
  </Card>

  <Card title="Sustainable Use License" icon="scale-balanced" href="https://docs.n8n.io/sustainable-use-license/">
    The fair-code license n8n is distributed under
  </Card>

  <Card title="n8n Template" icon="github" href="https://github.com/controlplane-com/templates/tree/main/n8n">
    View the source files, default values, and chart definition
  </Card>
</CardGroup>
