Skip to content
FS Fábio Silva
← All guides
Advanced Reference · 32 min read

The complete n8n self-hosting guide

A practical, production-minded walkthrough for self-hosting n8n — from npx and a single Docker container to Docker Compose behind a reverse proxy and full Kubernetes deployments across AWS, Azure, GCP, and OpenShift. Faithful to the official docs, with sources linked throughout.

Updated


What this document is. A practical installation guide for n8n in self-hosted mode that doubles as an article on why it’s worth hosting n8n on your own infrastructure. All technical instructions are faithful to the official n8n documentation; the links point to the original sources and to the resources referenced in each section.


Table of contents

  1. Why self-host n8n: the advantages
  2. Before you start: what you need to know
  3. Install with npm (local machine)
  4. Install with Docker (recommended)
  5. Use Docker Compose (Linux server)
  6. Deploy to a cloud provider
  7. Which option to choose
  8. Sources

1. Why self-host n8n: the advantages

n8n can be used in two ways: through the managed n8n Cloud service, or hosted by yourself (self-hosting). Hosting n8n on your own infrastructure brings concrete advantages that surface throughout the official documentation:

  • An isolated, predictable environment. n8n recommends Docker for most self-hosting needs precisely because it provides a clean, isolated environment, avoids operating system and tooling incompatibilities, and makes database and environment management simpler.

  • Control over your data. When you host n8n, you control where your credentials, past executions, and workflows are stored. n8n uses the .n8n directory (and/or a database) to keep sensitive data such as the encryption key, instance logs, and source control feature assets — all under your management.

  • Scalability on your terms. For larger deployments, n8n supports PostgreSQL as a more robust database backend than SQLite, and the documentation presents Kubernetes as the best method for scaling n8n as demand changes.

  • Deployment flexibility. You can run n8n locally to try it out (npx, npm), in a single Docker container, with Docker Compose behind a reverse proxy (Caddy or Traefik), or in managed Kubernetes clusters on any of the major cloud providers — choosing whatever best balances cost, effort, and robustness.

  • Potentially lower cost. The OpenShift Local documentation illustrates this well: running a cluster locally is free, versus roughly $135–400/month for a managed equivalent in the cloud. For many cases, a simple shared server (a basic DigitalOcean Droplet or a Hetzner CPX11 type) is enough.

Important — the flip side. Self-hosting n8n requires technical knowledge: setting up and configuring servers and containers, managing application resources and scaling, securing servers and applications, and configuring n8n itself. Mistakes can lead to data loss, security issues, and downtime. n8n recommends self-hosting for expert users; if you aren’t experienced at managing servers, n8n recommends n8n Cloud.


2. Before you start: what you need to know

Stable and Beta versions. n8n releases a new minor version most weeks. The stable version is for production use; the beta version is the most recent release and may be unstable. To report issues, use the forum.

Documentation index. For the complete documentation index, see llms.txt. Any documentation page is available in Markdown by appending .md to the URL.

Useful cross-cutting resources:


3. Install with npm (local machine)

Source: Install with npm

npm is a quick way to get started with n8n on your local machine. You must have Node.js installed. n8n requires a Node.js version between 20.19 and 24.x, inclusive.

Try n8n with npx

You can try n8n without installing it using npx. From the terminal, run:

npx n8n

This command downloads everything that’s needed to start n8n. You can then access n8n and start building workflows by opening http://localhost:5678.

Install globally with npm

To install n8n globally, use npm:

npm install n8n -g

To install or update to a specific version of n8n, use the @ syntax to specify the version. For example:

npm install -g n8n@0.126.1

To install the next version:

npm install -g n8n@next

After installation, start n8n by running:

n8n
# or
n8n start

Next steps: try out n8n using the Quickstarts.

Updating

To update your n8n instance to the latest version, run:

npm update -g n8n

To install the next version:

npm install -g n8n@next

Docker required (for the tunnel). The tunnel uses cloudflared, which runs as a Docker container. Make sure Docker is installed on your machine, even when running n8n via npm.

For npm installations, use the services only approach. Start cloudflared as a standalone service, then run n8n locally:

# Terminal 1: Start the cloudflared tunnel service
pnpm --filter n8n-containers services --services cloudflared

# Terminal 2: Start n8n locally
pnpm dev

The services command starts cloudflared, fetches the public tunnel URL, and writes a .env file to packages/cli/bin/.env with WEBHOOK_URL and N8N_PROXY_HOPS=1. n8n picks up this .env automatically on startup.

Clean up when done:

pnpm --filter n8n-containers services:clean

For the full stack approach (n8n and cloudflared both in containers), refer to the Docker tunnel setup.

Reverting an upgrade

Install the older version you want to go back to. If the upgrade involved a database migration:

  1. Check the feature documentation and release notes to see if there are any manual changes you need to make.
  2. Run n8n db:revert on your current version to roll back the database. To revert more than one migration, repeat the process.

Windows troubleshooting

If you experience issues running n8n on Windows, make sure your Node.js environment is correctly set up. Follow Microsoft’s guide to Install NodeJS on Windows.


Source: Install with Docker

n8n recommends using Docker for most self-hosting needs: it provides a clean, isolated environment, avoids OS and tooling incompatibilities, and makes database and environment management simpler. You can also use n8n in Docker with Docker Compose; you can find configurations for various architectures in the n8n-hosting repository.

There’s also a video guide: https://www.youtube.com/embed/6ET3G7GiqZA?si=mwCKbtyLqNCRc2pa

Prerequisites

Before proceeding, install Docker:

  • Docker Desktop is available for Mac, Windows, and Linux. It includes the Docker Engine and Docker Compose.
  • Docker Engine and Docker Compose are also available as separate packages for Linux — use these for Linux machines without a graphical environment or when you don’t want the Docker Desktop UI.

Starting n8n

From your terminal, run the following commands, replacing the <YOUR_TIMEZONE> placeholders with your timezone:

docker volume create n8n_data

docker run -it --rm \
 --name n8n \
 -p 5678:5678 \
 -e GENERIC_TIMEZONE="<YOUR_TIMEZONE>" \
 -e TZ="<YOUR_TIMEZONE>" \
 -e N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true \
 -e N8N_RUNNERS_ENABLED=true \
 -v n8n_data:/home/node/.n8n \
 docker.n8n.io/n8nio/n8n

This command creates a volume to store persistent data, downloads the required n8n image, and starts the container with the following settings:

  • Maps and exposes port 5678 on the host.
  • Sets the container’s timezone:
  • Enforces secure file permissions for the n8n configuration file.
  • Enables task runners, the recommended way of executing tasks in n8n.
  • Mounts the n8n_data volume to the /home/node/.n8n directory to persist your data across container restarts.

Once running, access n8n by opening http://localhost:5678.

Using with PostgreSQL

By default, n8n uses SQLite to save credentials, past executions, and workflows. n8n also supports PostgreSQL, configurable using environment variables.

Persisting the .n8n directory is still recommended. With PostgreSQL, n8n doesn’t need .n8n for the SQLite file, but the directory still contains important data such as encryption keys, instance logs, and source control feature assets. While you can work around some of these requirements (for example, by setting the N8N_ENCRYPTION_KEY environment variable), it’s best to keep mapping a persistent volume for the directory to avoid potential issues.

To use n8n with PostgreSQL, run the following commands, replacing the placeholders (within angle brackets <>) with your actual values:

docker volume create n8n_data

docker run -it --rm \
 --name n8n \
 -p 5678:5678 \
 -e GENERIC_TIMEZONE="<YOUR_TIMEZONE>" \
 -e TZ="<YOUR_TIMEZONE>" \
 -e N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true \
 -e N8N_RUNNERS_ENABLED=true \
 -e DB_TYPE=postgresdb \
 -e DB_POSTGRESDB_DATABASE=<POSTGRES_DATABASE> \
 -e DB_POSTGRESDB_HOST=<POSTGRES_HOST> \
 -e DB_POSTGRESDB_PORT=<POSTGRES_PORT> \
 -e DB_POSTGRESDB_USER=<POSTGRES_USER> \
 -e DB_POSTGRESDB_SCHEMA=<POSTGRES_SCHEMA> \
 -e DB_POSTGRESDB_PASSWORD=<POSTGRES_PASSWORD> \
 -v n8n_data:/home/node/.n8n \
 docker.n8n.io/n8nio/n8n

You can find a complete docker-compose file for PostgreSQL in the n8n hosting repository.

Updating

In Docker Desktop, go to the Images tab and select Pull from the context menu to download the latest image. From the command line:

# Pull latest (stable) version
docker pull docker.n8n.io/n8nio/n8n

# Pull specific version
docker pull docker.n8n.io/n8nio/n8n:1.81.0

# Pull next (unstable) version
docker pull docker.n8n.io/n8nio/n8n:next

After pulling the updated image, stop the container and start it again. From the command line (replace <container_id> with the ID found in the first command):

# Find your container ID
docker ps -a

# Stop the container
docker stop <container_id>

# Remove the container
docker rm <container_id>

# Start the container
docker run --name=<container_name> [options] -d docker.n8n.io/n8nio/n8n

Updating via Docker Compose:

# Navigate to the directory containing your docker compose file
cd </path/to/your/compose/file/directory>

# Pull latest version
docker compose pull

# Stop and remove the older version
docker compose down

# Start the container
docker compose up -d

Tunnel = development only. The tunnel feature is a convenience tool for local development, and its underlying implementation may change between versions. It isn’t safe to use in production.

To use webhooks for trigger nodes of external services (like GitHub), n8n has to be reachable from the web. n8n provides a tunnel service via cloudflared. There are two ways to use it:

  • Full stack (n8n + cloudflared in containers; the tunnel URL prints on startup): pnpm stack --tunnel
  • Services only (run n8n locally with pnpm dev/pnpm start and cloudflared as a standalone service) — see the commands in the npm section.

Next steps: see the README for the Docker image; learn more about configuring and scaling n8n.


5. Use Docker Compose (Linux server)

Source: Use Docker Compose

These instructions cover how to run n8n on a Linux server using Docker Compose. If you already have Docker and Docker Compose installed, you can skip to step 3. You can find configurations for various architectures in the n8n-hosting repository.

1. Install Docker and Docker Compose

The installation method depends on your Linux distribution:

Verify they’re available:

docker --version
docker compose version

2. Optional: Non-root user access

To grant access to run Docker without sudo for the current user (assuming they have sudo access):

sudo usermod -aG docker ${USER}
# Register the `docker` group membership with the current session without changing your primary group
exec sg docker newgrp

For a different user (replace <USER_TO_RUN_DOCKER>):

sudo usermod -aG docker <USER_TO_RUN_DOCKER>

Verify docker group membership with:

groups

3. DNS setup

To host n8n online or on a network, create a dedicated subdomain pointed at your server. Add an A record:

Record typeNameDestination
An8n (or your desired subdomain)<your_server_IP_address>

4. Create an .env file

Create a project directory and navigate into it:

mkdir n8n-compose
cd n8n-compose

Inside n8n-compose, create an .env file and adjust it to your information:

# DOMAIN_NAME and SUBDOMAIN together determine where n8n will be reachable from
# The top level domain to serve from
DOMAIN_NAME=example.com

# The subdomain to serve from
SUBDOMAIN=n8n

# The above example serves n8n at: https://n8n.example.com

# Optional timezone used by Cron and other scheduling nodes
# New York is the default value if not set
GENERIC_TIMEZONE=Europe/Berlin

# The email address to use for the TLS/SSL certificate creation
SSL_EMAIL=user@example.com

5. Create the local files directory

Inside your project directory, create a local-files directory for sharing files between the n8n instance and the host (for example, using the Read/Write Files from Disk node):

mkdir local-files

The Compose file below can create this directory automatically, but doing it manually ensures the right ownership and permissions.

6. Create the Docker Compose file

Create a compose.yaml file and paste the following:

services:
  traefik:
    image: "traefik"
    restart: always
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.mytlschallenge.acme.tlschallenge=true"
      - "--certificatesresolvers.mytlschallenge.acme.email=${SSL_EMAIL}"
      - "--certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - traefik_data:/letsencrypt
      - /var/run/docker.sock:/var/run/docker.sock:ro

  n8n:
    image: docker.n8n.io/n8nio/n8n
    restart: always
    ports:
      - "127.0.0.1:5678:5678"
    labels:
      - traefik.enable=true
      - traefik.http.routers.n8n.rule=Host(`${SUBDOMAIN}.${DOMAIN_NAME}`)
      - traefik.http.routers.n8n.tls=true
      - traefik.http.routers.n8n.entrypoints=web,websecure
      - traefik.http.routers.n8n.tls.certresolver=mytlschallenge
      - traefik.http.middlewares.n8n.headers.SSLRedirect=true
      - traefik.http.middlewares.n8n.headers.STSSeconds=315360000
      - traefik.http.middlewares.n8n.headers.browserXSSFilter=true
      - traefik.http.middlewares.n8n.headers.contentTypeNosniff=true
      - traefik.http.middlewares.n8n.headers.forceSTSHeader=true
      - traefik.http.middlewares.n8n.headers.SSLHost=${DOMAIN_NAME}
      - traefik.http.middlewares.n8n.headers.STSIncludeSubdomains=true
      - traefik.http.middlewares.n8n.headers.STSPreload=true
      - traefik.http.routers.n8n.middlewares=n8n@docker
    environment:
      - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
      - N8N_HOST=${SUBDOMAIN}.${DOMAIN_NAME}
      - N8N_PORT=5678
      - N8N_PROTOCOL=https  
      - NODE_ENV=production
      - WEBHOOK_URL=https://${SUBDOMAIN}.${DOMAIN_NAME}/
      - GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
      - TZ=${GENERIC_TIMEZONE}
    volumes:
      - n8n_data:/home/node/.n8n
      - ./local-files:/files

volumes:
  n8n_data:
  traefik_data:

This file configures two containers: n8n and traefik (an application proxy that manages TLS/SSL certificates and routing). It creates and mounts two Docker Volumes and mounts the local-files directory:

NameTypeContainer mountDescription
n8n_dataVolume/home/node/.n8nWhere n8n saves its SQLite database file and encryption key.
traefik_dataVolume/letsencryptWhere traefik saves TLS/SSL certificate data.
./local-filesBind/filesA local directory shared between n8n and the host. In n8n, use the /files path.

7. Start Docker Compose

sudo docker compose up -d

To stop the containers:

sudo docker compose stop

8. Done

You can now reach n8n using the subdomain + domain combination defined in your .env (in the example, https://n8n.example.com). n8n is only accessible over secure HTTPS, not plain HTTP. If you have trouble reaching your instance, check your server’s firewall settings and your DNS configuration.


6. Deploy to a cloud provider

The sections below cover deploying n8n to cloud providers. The approaches fall into two broad groups: single server with Docker Compose + Caddy (DigitalOcean, Hetzner) and Kubernetes for scaling (AWS, Azure, GKE), plus serverless (Cloud Run), PaaS (Heroku), and local (OpenShift CRC) options.

6.1. DigitalOcean

Source: Deploy to Digital Ocean

This guide uses Caddy (a reverse proxy that automatically creates and manages SSL/TLS certificates) and Docker Compose.

Create a Droplet

  1. Log in to DigitalOcean.
  2. Select the project to host the Droplet, or create a new project.
  3. In your project, select Droplets from the Manage menu.
  4. Create a new Droplet using the Docker image available on the Marketplace tab.

For most usage levels, a basic shared CPU plan is enough. DigitalOcean lets you choose between SSH key and password-based authentication; SSH keys are considered more secure.

Log in to your Droplet and create a new user

The rest of the guide requires logging in to the Droplet via SSH (see How to Connect to Droplets with SSH). You should create a new user to avoid working as root:

  1. Log in as root.
  2. Create a new user: adduser <username>
  3. Follow the CLI prompts to finish creating the user.
  4. Grant administrative privileges: usermod -aG sudo <username> (you can now use sudo).
  5. Set up SSH for the new user: Add Public Key Authentication.
  6. Log out of the Droplet.
  7. Log back in via SSH as the new user.

Clone the configuration repository

Clone the files from the n8n-docker-caddy repository into the logged-in user’s home folder:

git clone https://github.com/n8n-io/n8n-docker-caddy.git
cd n8n-docker-caddy

The two folders are caddy_config (Caddy configuration) and local_files (files you upload or add in n8n).

Create Docker volumes (to persist the Caddy cache and n8n data):

sudo docker volume create caddy_data
sudo docker volume create n8n_data

Set up DNS: create an “A” record for the n8n subdomain pointing to the Droplet’s IP. See An Introduction to DNS Terminology, Components, and Concepts.

Open ports (80 for non-secure traffic, 443 for secure):

sudo ufw allow 80
sudo ufw allow 443

Configure n8n: edit the .env file (which contains inline comments to help):

nano .env

See details in Environment variables. The docker-compose.yml file uses the .env variables, so you normally don’t need to change it (nano docker-compose.yml to inspect it).

Configure Caddy: edit the Caddyfile in the caddy_config folder and replace the placeholder domain with yours:

nano caddy_config/Caddyfile
n8n.<domain>.<suffix> {
    reverse_proxy n8n:5678 {
      flush_interval -1
    }
}

Start:

sudo docker compose up -d

Test by opening the subdomain + domain URL in your browser and entering the user name and password defined earlier. To stop: sudo docker compose stop.

Updating (Docker Compose): cd to the directory, then docker compose pull, docker compose down, docker compose up -d.

6.2. Hetzner

Source: Deploy to Hetzner

As with DigitalOcean, this uses Caddy + Docker Compose.

Create a server

  1. Log in to the Hetzner Cloud Console.
  2. Select the project, or create a new one with + NEW PROJECT.
  3. Select + CREATE SERVER on the project.

Since this guide uses Docker, under the Image section select “Docker CE” from the APPS tab. For most usage levels, the CPX11 type is enough. Hetzner lets you choose between SSH and password-based authentication; SSH is more secure (the guide assumes SSH).

Log in to your server via SSH (see Access with SSH/rsync/BorgBackup). You can find the public IP in the server listing of your project.

Install Docker Compose (the Hetzner Docker image doesn’t include it):

apt update && apt -y upgrade
apt install docker-compose-plugin

Clone the configuration repository from n8n-docker-caddy into the root user folder:

git clone https://github.com/n8n-io/n8n-docker-caddy.git
cd n8n-docker-caddy

Folders: caddy_config and local_files.

Create Docker volumes:

docker volume create caddy_data
sudo docker volume create n8n_data

Set up DNS: an “A” record for the subdomain pointing to the server’s IP (see intro to DNS).

Open ports:

sudo ufw allow 80
sudo ufw allow 443

Configure n8n (nano .env; see Environment variables) and configure Caddy (nano caddy_config/Caddyfile):

n8n.<domain>.<suffix> {
    reverse_proxy n8n:5678 {
      flush_interval -1
    }
}

Start: docker compose up -d. Test in your browser. To stop: sudo docker compose stop.

6.3. Heroku

Source: Deploy to Heroku

This guide uses Docker Compose, Heroku’s PostgreSQL service for storage, and a Deploy to Heroku button for a one-click deployment (with minor configuration).

Create the Heroku project from the template — the Deploy to Heroku button:

Deploy

This opens the Create New App page. Set a name and choose the region.

Configure environment variables: Heroku pre-fills the options from the env section of app.json. You can change any value, but you must change:

  • N8N_ENCRYPTION_KEY — used to encrypt user account details before saving them to the database.
  • WEBHOOK_URL — should match the application name you create, so webhooks have the correct URL.

Deploy: select Deploy app. After the build, Heroku provides links to Manage App or View. To connect your domain, see the Heroku networking/DNS documentation.

Customize the template: fork the repository and deploy from the fork. By default the Dockerfile pulls the latest image (change the tag on the top line to pin a version). Heroku doesn’t allow EXPOSE in Docker apps — it provides a dynamic PORT variable, and entrypoint.sh handles this; you can then access n8n on port 80 (Docker limitations with Heroku). The heroku.yml defines the addons (PostgreSQL) and the build via the Docker buildpack.

6.4. AWS (EKS)

Source: Deploy to AWS

This guide self-hosts n8n with PostgreSQL as the backend, using Kubernetes to manage the resources and reverse proxy. AWS offers several options (EC2, EKS); this guide uses EKS. Using Kubernetes adds complexity but is the best method for scaling as demand changes.

Prerequisites: a mix of the AWS UI and the eksctl CLI tool. Although not mentioned in the eksctl documentation, you also need to install the AWS CLI and configure its authentication.

Create a cluster:

eksctl create cluster --name n8n --region <your-aws-region>

This can take a while. Once created, eksctl automatically sets the kubectl context to the cluster.

Clone the configuration repository (n8n-hosting):

git clone https://github.com/n8n-io/n8n-hosting.git 
cd n8n-hosting/kubernetes

Configure Postgres. For larger-scale deployments, Postgres is more robust than SQLite.

  • Persistent storage volume: the default AWS storage class, gp3, is suitable and is defined in the postgres-claim0-persistentvolumeclaim.yaml manifest:

    spec:
      storageClassName: gp3
      accessModes:
        - ReadWriteOnce
  • Postgres environment variables: the postgres-secret.yaml file contains placeholders to replace with your values. Postgres uses a root user (POSTGRES_USER) for administration, but it’s best practice to create a non-root user (POSTGRES_NON_ROOT_USER) for n8n — this improves security and prevents accidental changes. The postgres-deployment.yaml uses these values.

Configure n8n.

  • File storage volume: while not essential, persistent volumes help maintain uploaded files and persist manual encryption keys between restarts. Created by n8n-claim0-persistentvolumeclaim.yaml, mounted in n8n-deployment.yaml:

    volumes:
      - name: n8n-claim0
        persistentVolumeClaim:
          claimName: n8n-claim0
  • Pod resources: Kubernetes lets you specify minimum resources and limits. The example in n8n-deployment.yaml:

    resources:
      requests:
        memory: "250Mi"
      limits:
        memory: "500Mi"

    A minimum of 250mb and maximum of 500mb per container, leaving CPU to Kubernetes. As a guide, values for the n8n cloud offerings: Start 320mb RAM / 10 millicore CPU burstable; Pro (10k) 640mb / 20 millicore; Pro (50k) 1280mb / 80 millicore.

  • Optional — environment variables: create an n8n-secret.yaml (see Environment variables).

Deployments and Services. The n8n-deployment.yaml and postgres-deployment.yaml manifests define the applications (environment variables, image, resource limits, volumes/volumeMounts, scaling and restart policies — the examples define one instance of each pod; adjust as needed). The postgres-service.yaml and n8n-service.yaml manifests expose the services via the Kubernetes load balancer, on ports 5432 and 5678 by default.

Send to the cluster:

kubectl apply -f .

Namespace error. You may see an error about not finding the “n8n” namespace because the resource isn’t ready yet. Run the command again, or apply the namespace first: kubectl apply -f namespace.yaml.

Set up DNS. Create a DNS record for the subdomain pointing to a static address of the instance. To find the address: in the AWS console, Amazon Elastic Kubernetes Service > Clusters > (cluster name) > Resources tab > Service and networking > Services > select the n8n service and copy the Load balancer URLs value. Use that value suffixed with the n8n port (5678).

Use HTTP. This guide uses HTTP connections. If you click the Load balancer URLs value, EKS takes you to an “HTTPS” URL that results in an error. To fix this, when you open the n8n subdomain, use HTTP.

Delete resources: kubectl delete -f .

6.5. Azure (AKS)

Source: Deploy to Azure

Self-hosting with PostgreSQL and Kubernetes. Prerequisite: the Azure command line tool.

Azure offers Azure Container Instances, Linux VMs, and Azure Kubernetes Service (AKS). This guide uses AKS — more complex, but the best for scaling.

Open AKS: from the Azure portal select Kubernetes services.

Create a cluster: Create > Create a Kubernetes cluster, select your options, then Create.

Set the Kubectl context: open the cluster details page and the Connect button; copy and run the snippets in a terminal to point your local Kubernetes at the new cluster.

Clone the repository (n8n-hosting):

git clone https://github.com/n8n-io/n8n-hosting.git
cd n8n-hosting/kubernetes

Configure Postgres. The default storage class is suitable, defined in postgres-claim0-persistentvolumeclaim.yaml (for specialized requirements, see Azure storage options). The postgres-secret.yaml contains placeholders to replace; the postgres-deployment.yaml uses those values.

Configure n8n. The file volume (n8n-claim0-persistentvolumeclaim.yaml) is required for using nodes that interact with files (like the binary data node) and for persisting manual encryption keys:

volumes:
  - name: n8n-claim0
    persistentVolumeClaim:
      claimName: n8n-claim0

Pod resources (same as the AWS example; see the reference values above) and, optionally, an n8n-secret.yaml (Environment variables).

Deployments and Services as described above; services exposed via load balancer on ports 5432 and 5678.

Send to the cluster:

kubectl apply -f .

(On a namespace error, repeat or apply kubectl apply -f namespace.yaml.)

Set up DNS: create a DNS record for the subdomain pointing to the IP of the n8n service, found under Services & ingresses of the cluster, External IP column, adding port 5678. For a static IP with AKS, see this tutorial.

Delete resources: kubectl delete -f .

6.6. Google Cloud Run

Source: Deploy to Google Cloud Run

Cloud Run is a serverless container runtime. There are two modes: easy mode (fast, in-memory data — demo only) and durable mode (production, with a database and secret manager). For GKE, see the next section. You can also enable OAuth access to Google Workspace services (Gmail, Drive, etc.) as n8n tools.

Before you begin: if you don’t yet have a Google Cloud project, create one first and enable billing (even if the service runs for free, you must have billing enabled to deploy).

Easy mode

The fastest way. Warning: data is in-memory — anytime the service scales to zero or is redeployed, the n8n data is lost. Use durable mode for production.

Open the Cloud Shell Terminal (in the console, type “G” then “S”, or click the terminal icon). If needed, log in and enable the API:

gcloud auth login
gcloud services enable run.googleapis.com

Required: custom health check endpoint. Cloud Run reserves /healthz for its own health checks. Since n8n uses this path by default, it conflicts. Set the N8N_ENDPOINT_HEALTH environment variable to a custom path (included in the commands below).

Deploy n8n (you can choose another region instead of us-west1):

gcloud run deploy n8n \
    --image=n8nio/n8n \
    --region=us-west1 \
    --allow-unauthenticated \
    --port=5678 \
    --no-cpu-throttling \
    --memory=2Gi \
    --set-env-vars="N8N_ENDPOINT_HEALTH=health"

When finished, open the Service URL in another tab. Optionally, to keep the service running as long as possible (manual scale to 1, avoiding autoscaling to 0):

gcloud run deploy n8n \
    --image=n8nio/n8n \
    --region=us-west1 \
    --allow-unauthenticated \
    --port=5678 \
    --no-cpu-throttling \
    --memory=2Gi \
    --scaling=1 \
    --set-env-vars="N8N_ENDPOINT_HEALTH=health"

This doesn’t fully prevent data loss (for example, on redeploys). For truly persistent data, use durable mode.

Durable mode

A more durable, production-grade deployment with a database for persistence and secret manager for sensitive data. If you prefer Terraform, see this example.

Enable APIs and set variables:

## You may need to login first
gcloud auth login

gcloud services enable run.googleapis.com
gcloud services enable sqladmin.googleapis.com
gcloud services enable secretmanager.googleapis.com
export PROJECT_ID=your-project
export REGION=region-where-you-want-this-deployed

Postgres database (takes a few minutes; change the root-password):

gcloud sql instances create n8n-db \
    --database-version=POSTGRES_13 \
    --tier=db-f1-micro \
    --region=$REGION \
    --root-password="change-this-password" \
    --storage-size=10GB \
    --availability-type=ZONAL \
    --no-backup \
    --storage-type=HDD
gcloud sql databases create n8n --instance=n8n-db
gcloud sql users create n8n-user \
    --instance=n8n-db \
    --password="change-this-password"

You can save the n8n-user password to a file for the next step (delete it afterward).

Store sensitive data in Secret Manager (recommended):

gcloud secrets create n8n-db-password \
    --data-file=/your/password/file \
    --replication-policy="automatic"

Create an encryption key (this example generates a random one):

openssl rand -base64 -out my-encryption-key 42
gcloud secrets create n8n-encryption-key \
    --data-file=my-encryption-key \
    --replication-policy="automatic"

Then delete the my-encryption-key and password files — the values are stored in Secret Manager.

Service account for Cloud Run (restricted to only what it needs):

gcloud iam service-accounts create n8n-service-account \
    --display-name="n8n Service Account"

gcloud secrets add-iam-policy-binding n8n-db-password \
    --member="serviceAccount:n8n-service-account@$PROJECT_ID.iam.gserviceaccount.com" \
    --role="roles/secretmanager.secretAccessor"

gcloud secrets add-iam-policy-binding n8n-encryption-key \
    --member="serviceAccount:n8n-service-account@$PROJECT_ID.iam.gserviceaccount.com" \
    --role="roles/secretmanager.secretAccessor"

gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:n8n-service-account@$PROJECT_ID.iam.gserviceaccount.com" \
    --role="roles/cloudsql.client"

Deploy the Cloud Run service:

gcloud run deploy n8n \
    --image=n8nio/n8n:latest \
    --command="/bin/sh" \
    --args="-c,sleep 5;n8n start" \
    --region=$REGION \
    --allow-unauthenticated \
    --port=5678 \
    --memory=2Gi \
    --no-cpu-throttling \
    --set-env-vars="N8N_PORT=5678,N8N_PROTOCOL=https,N8N_ENDPOINT_HEALTH=health,DB_TYPE=postgresdb,DB_POSTGRESDB_DATABASE=n8n,DB_POSTGRESDB_USER=n8n-user,DB_POSTGRESDB_HOST=/cloudsql/$PROJECT_ID:$REGION:n8n-db,DB_POSTGRESDB_PORT=5432,DB_POSTGRESDB_SCHEMA=public,GENERIC_TIMEZONE=UTC,QUEUE_HEALTH_CHECK_ACTIVE=true" \
    --set-secrets="DB_POSTGRESDB_PASSWORD=n8n-db-password:latest,N8N_ENCRYPTION_KEY=n8n-encryption-key:latest" \
    --add-cloudsql-instances=$PROJECT_ID:$REGION:n8n-db \
    --service-account=n8n-service-account@$PROJECT_ID.iam.gserviceaccount.com

When finished, open the Service URL — you should see the n8n login screen.

Troubleshooting: a “Cannot GET /” screen usually means n8n is still starting up; refresh the page.

(Optional) Enable Google Workspace services as n8n tools. Setting up OAuth is recommended. First, enable the APIs you want (Sheets/Docs have their own API, the Drive one isn’t enough):

gcloud services enable gmail.googleapis.com
gcloud services enable drive.googleapis.com
gcloud services enable sheets.googleapis.com
gcloud services enable docs.googleapis.com
gcloud services enable calendar-json.googleapis.com

Re-deploy with the OAuth callback URLs as variables:

export SERVICE_URL="your-n8n-service-URL"
## e.g. https://n8n-12345678.us-west1.run.app

gcloud run services update n8n \
    --region=$REGION \
    --update-env-vars="N8N_HOST=$(echo $SERVICE_URL | sed 's/https:\/\///'),WEBHOOK_URL=$SERVICE_URL,N8N_EDITOR_BASE_URL=$SERVICE_URL"

Finally, set up OAuth at https://console.cloud.google.com/auth: click “Get Started” (if shown); fill in “App Information”; for “Audience” choose “Internal” (only your Workspace) or “External”; enter “Contact Information”; if “External”, add test users; under “Clients” > “Create client” choose “Web application”, put the n8n service URL in “Authorized JavaScript origins” and /rest/oauth2-credential/callback in “Authorized redirect URIs” (download the client JSON, which contains the secret); under “Data Access” add the scopes (e.g. for Sheets: https://googleapis.com/auth/drive.file and https://googleapis.com/auth/spreadsheets). You can then test by adding a Tool in n8n with the credentials from the JSON.

6.7. Google Kubernetes Engine (GKE)

Source: Deploy to Google Kubernetes

Google Cloud offers Cloud Run, Compute Engine (VMs), and Kubernetes Engine (GKE). This guide uses GKE. For Cloud Run, see the previous section. Most steps use the Google Cloud UI, but you can use the gcloud tool.

Prerequisites:

Create a project: in the Google Cloud Console, project dropdown > NEW PROJECT, then select it (always confirm you’re in the correct project).

Enable the Kubernetes Engine API: search “Kubernetes” in the top search bar, choose “Kubernetes Engine”, and select ENABLE.

Create a cluster: from the GKE page, Clusters > CREATE. Select the Standard option — n8n does not work with an “Autopilot” cluster. You can keep the defaults except for what you need to change (e.g. location).

Set the Kubectl context: open the cluster details and CONNECT; paste and run the snippet in the gcloud CLI.

Clone the repository (n8n-hosting):

git clone https://github.com/n8n-io/n8n-hosting.git
cd n8n-hosting/kubernetes

Configure Postgres. Running Postgres on GCP requires a specific Kubernetes Storage Class (see this guide); the storage.yaml manifest creates it. You can change the regions under allowedTopologies > matchedLabelExpressions > values (default us-central):

allowedTopologies:
  - matchLabelExpressions:
      - key: failure-domain.beta.kubernetes.io/zone
        values:
          - us-central1-b
          - us-central1-c

The postgres-secret.yaml contains placeholders to replace; the postgres-deployment.yaml uses those values.

Configure n8n. File volume (required for nodes that interact with files and for persisting manual encryption keys) via n8n-claim0-persistentvolumeclaim.yaml:

volumes:
  - name: n8n-claim0
    persistentVolumeClaim:
      claimName: n8n-claim0

Pod resources (present in n8n-deployment.yaml and postgres-deployment.yaml; same reference values noted in the AWS section) and, optionally, an n8n-secret.yaml (Environment variables).

Deployments and Services as in the other Kubernetes sections; services exposed via load balancer on ports 5432 and 5678.

Send to the cluster:

kubectl apply -f .

(Namespace error: repeat or apply kubectl apply -f namespace.yaml.)

Set up DNS: a DNS record for the subdomain pointing to the IP of the n8n service, found under Services & Ingress of the cluster, Endpoints column. For reserved IPs with GKE, see this tutorial.

Delete resources: kubectl delete -f .

6.8. OpenShift Local (CRC)

Source: Deploy to OpenShift Local (CRC)

This guide deploys n8n on OpenShift Local (CRC), Red Hat’s tool for running a local OpenShift cluster. It mirrors the AWS/EKS deployment but runs entirely on your machine — ideal for testing n8n in an OpenShift environment without cloud costs. You need a machine with significant resources.

OpenShift vs standard Kubernetes concepts (summary): kubectloc; Namespace → Project; Ingress/LoadBalancer → Route (built-in); EBS StorageClass (gp3) → CRC’s built-in storage provisioner; RDS PostgreSQL → in-cluster PostgreSQL via Helm (Bitnami); ElastiCache Redis → in-cluster Redis; AWS S3 → in-cluster MinIO; OIDC/IAM and AWS Load Balancer Controller → not needed; ~$135–400/month → free (runs on your machine).

Machine prerequisites: 4+ physical CPU cores with virtualization support; 32+ GB free RAM (CRC reserves 9 GB for the VM); 100 GB free disk; OS Ubuntu (22.04 LTS or newer).

Prepare Ubuntu

Open a terminal (Ctrl+Alt+T). Update the system:

sudo apt update && sudo apt upgrade -y

Check CPU virtualization support (output 0 = disabled, enable VT-x/AMD-V in the BIOS; 1 or more = good):

egrep -c '(vmx|svm)' /proc/cpuinfo

Install KVM and libvirt, and virtiofsd (required to share the filesystem with the VM):

sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils
sudo apt install -y virtiofsd
sudo systemctl start libvirtd
sudo systemctl enable libvirtd
sudo systemctl status libvirtd

Add your user to the required groups (you must log out and log back in, or reboot, otherwise CRC fails with “permission denied”):

sudo usermod -aG libvirt $USER
sudo usermod -aG kvm $USER
sudo reboot

After rebooting, verify with groups (should list libvirt and kvm). Install NetworkManager (required to manage DNS for the internal domains *.apps-crc.testing, api.crc.testing):

sudo apt install -y network-manager
sudo systemctl start NetworkManager
sudo systemctl enable NetworkManager
nmcli general status

Install tools

Red Hat account and pull secret: CRC needs a free Red Hat account to pull images. Create a free account; at console.redhat.com/openshift/create/local click Download OpenShift Local, select Linux, and download the .tar.xz to ~/Downloads. On the same page, Copy pull secret and save it to a file.

Install CRC:

cd ~/Downloads
tar xf crc-linux-amd64.tar.xz
sudo mv crc-*-linux-amd64/crc /usr/local/bin/
crc version

Install Helm (installs n8n and supporting services into the cluster):

curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version

Set environment variables (only last for the current terminal session; re-run when opening a new terminal):

export NAMESPACE=n8n-$(date +%Y%m%d)
echo "Namespace:$NAMESPACE"

Start OpenShift Local

Run setup (only once; configures KVM networking, checks requirements, and downloads the bundle ~2.5 GB):

crc setup

CRC defaults to 9 GB of RAM; n8n needs more. Set 14 GB before starting (persists across crc stop/crc start):

crc config set memory 14336

Recommended: save the pull secret to a file and start with it:

nano ~/pull-secret.txt        # paste the pull secret, Ctrl+O, Ctrl+X
chmod 600 ~/pull-secret.txt
crc start --pull-secret-file ~/pull-secret.txt

This takes 10–15 minutes. When done, the web console URL and credentials are shown (kubeadmin with a generated password, developer/developer). Save the kubeadmin password (you can retrieve it later with crc console --credentials).

Verify DNS and API (on Ubuntu, CRC configures the resolver automatically):

sudo ss -tlnp | grep 6443

Configure your shell (makes oc available in the terminal):

eval $(crc oc-env)
echo 'eval $(crc oc-env)' >> ~/.bashrc
source ~/.bashrc
oc version

Log in to the cluster (replace the kubeadmin password):

oc login -u kubeadmin -p <your-kubeadmin-password> https://api.crc.testing:6443
oc whoami

Standalone deployment

Standalone mode: n8n as a single pod with SQLite, no external database or Redis. Ideal for exploring n8n locally.

oc new-project $NAMESPACE

Grant the security permission (SCC). OpenShift enforces Security Context Constraints; the chart runs as UID 1000, so you must allow it (use the explicit form — the -z shorthand can silently fail):

oc adm policy add-scc-to-user anyuid \
  system:serviceaccount:$NAMESPACE:n8n
oc get rolebindings -n $NAMESPACE

Create the required secret and back up the encryption key immediately (losing it makes all stored credentials permanently unreadable):

oc create secret generic n8n-secrets \
  --namespace $NAMESPACE \
  --from-literal=N8N_ENCRYPTION_KEY="$(openssl rand -hex 32)" \
  --from-literal=N8N_HOST="localhost" \
  --from-literal=N8N_PORT="5678" \
  --from-literal=N8N_PROTOCOL="http"

oc get secret n8n-secrets -n $NAMESPACE \
  -o jsonpath='{.data.N8N_ENCRYPTION_KEY}' | base64 --decode

Create the values file n8n-standalone-values.yaml (nano n8n-standalone-values.yaml):

queueMode:
  enabled: false

database:
  type: sqlite
  useExternal: false

redis:
  enabled: false

persistence:
  enabled: true
  size: 5Gi

secretRefs:
  existingSecret: "n8n-secrets"

service:
  type: ClusterIP
  port: 5678

securityContext:
  enabled: true

resources:
  main:
    requests:
      cpu: 100m
      memory: 256Mi
    limits:
      cpu: "1"
      memory: 1Gi

config:
  timezone: UTC

Deploy. The Helm chart hard codes seccompProfile: RuntimeDefault, which OpenShift 4.14+ rejects even with anyuid. The fix is to pull the chart locally, remove those two lines, and install from the patched copy:

helm pull oci://ghcr.io/n8n-io/n8n-helm-chart/n8n --version 1.0.3 --untar
sed -i '/seccompProfile:/d; /type: RuntimeDefault/d' ~/n8n/templates/deployment-main.yaml
grep -n "seccomp\|RuntimeDefault" ~/n8n/templates/deployment-main.yaml   # should return nothing

helm install n8n ~/n8n/ \
  --namespace $NAMESPACE \
  --values n8n-standalone-values.yaml \
  --wait \
  --timeout 10m

Access via port forward (simpler than Routes for local access):

oc port-forward service/n8n-main --namespace $NAMESPACE 5678:5678

Keep it running and open http://localhost:5678 (n8n prompts you to create the owner account). Ctrl+C to stop the tunnel. Check status with oc get pods -n $NAMESPACE.

Multi-instance queue mode

Runs multiple n8n pods with a shared database, message queue, and object storage. Requires an n8n Enterprise license. Instead of AWS managed services, it uses in-cluster equivalents: RDS PostgreSQL → PostgreSQL (Bitnami), ElastiCache Redis → Redis (Bitnami), S3 → MinIO (Bitnami).

Install in-cluster services:

oc new-project $NAMESPACE
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update

PostgreSQL (replace the password; the adaptSecurityContext=auto flag lets OpenShift assign the correct UID):

helm install postgresql bitnami/postgresql \
  --namespace $NAMESPACE \
  --set auth.username=n8n \
  --set auth.password='YourStrongPassword123' \
  --set auth.database=n8n_enterprise \
  --set global.compatibility.openshift.adaptSecurityContext=auto \
  --wait

Endpoint: postgresql.YOUR_NAMESPACE.svc.cluster.local (replace YOUR_NAMESPACE with the actual value). Redis:

helm install redis bitnami/redis \
  --namespace $NAMESPACE \
  --set auth.enabled=false \
  --set architecture=standalone \
  --set global.compatibility.openshift.adaptSecurityContext=auto \
  --wait

Endpoint: redis-master.$NAMESPACE.svc.cluster.local. MinIO (S3-compatible):

helm install minio bitnami/minio \
  --namespace $NAMESPACE \
  --set auth.rootUser=minioadmin \
  --set auth.rootPassword='MinioStrongPassword123' \
  --set global.compatibility.openshift.adaptSecurityContext=auto \
  --wait

Endpoint: http://minio:9000. Create the bucket by opening the MinIO console (oc port-forward svc/minio 9001:9001 -n $NAMESPACE, then http://localhost:9001, log in with minioadmin/password): Buckets > Create Bucket named n8n-data. Ctrl+C to stop the port-forward.

Deploy n8n. Grant SCC to the n8n-enterprise service account:

oc adm policy add-scc-to-user anyuid \
  system:serviceaccount:$NAMESPACE:n8n-enterprise

Create the secrets (back up the encryption key; the passwords must match those set above):

oc create secret generic n8n-enterprise-secrets \
  --namespace $NAMESPACE \
  --from-literal=N8N_ENCRYPTION_KEY="$(openssl rand -hex 32)" \
  --from-literal=N8N_HOST="localhost" \
  --from-literal=N8N_PORT="5678" \
  --from-literal=N8N_PROTOCOL="http"

oc create secret generic n8n-enterprise-db-secret \
  --namespace $NAMESPACE \
  --from-literal=password='YourStrongPassword123'

oc create secret generic n8n-minio-secret \
  --namespace $NAMESPACE \
  --from-literal=root-password='MinioStrongPassword123'

Create n8n-multimain-ocp-values.yaml (replace the 3 values marked # <-- REPLACE: the license key and the two YOUR_NAMESPACE):

license:
  enabled: true
  activationKey: "your-enterprise-license-key-here"  # <-- REPLACE

multiMain:
  enabled: true
  replicas: 2

queueMode:
  enabled: true
  workerReplicaCount: 2
  workerConcurrency: 5

webhookProcessor:
  enabled: true
  replicaCount: 1
  disableProductionWebhooksOnMainProcess: true

database:
  type: postgresdb
  useExternal: true
  host: "postgresql.YOUR_NAMESPACE.svc.cluster.local"   # <-- REPLACE YOUR_NAMESPACE
  port: 5432
  database: n8n_enterprise
  schema: "public"
  user: n8n
  passwordSecret:
    name: "n8n-enterprise-db-secret"
    key: "password"

redis:
  enabled: true
  useExternal: true
  host: "redis-master.YOUR_NAMESPACE.svc.cluster.local"  # <-- REPLACE YOUR_NAMESPACE
  port: 6379
  tls: false

s3:
  enabled: true
  bucket:
    name: "n8n-data"
    region: "us-east-1"
  host: "http://minio:9000"
  auth:
    autoDetect: false
    accessKeyId: "minioadmin"
    secretAccessKeySecret:
      name: "n8n-minio-secret"
      key: "root-password"
  storage:
    mode: "s3"
    availableModes: "filesystem,s3"
  forcePathStyle: true

serviceAccount:
  create: true
  name: n8n

Replace the YOUR_NAMESPACE placeholders automatically and verify:

echo $NAMESPACE
sed -i "s/YOUR_NAMESPACE/$NAMESPACE/g" n8n-multimain-ocp-values.yaml
grep "svc.cluster.local" n8n-multimain-ocp-values.yaml

Pull and patch the chart (if you haven’t already) and install:

helm pull oci://ghcr.io/n8n-io/n8n-helm-chart/n8n --version 1.0.3 --untar
sed -i '/seccompProfile:/d; /type: RuntimeDefault/d' ~/n8n/templates/deployment-main.yaml
grep -n "seccomp\|RuntimeDefault" ~/n8n/templates/deployment-main.yaml

helm install n8n ~/n8n/ \
  --namespace $NAMESPACE \
  --values n8n-multimain-ocp-values.yaml \
  --wait \
  --timeout 15m

Create a Route for external access (equivalent to Ingress/LoadBalancer, no extra controller) and get the URL:

oc expose svc/n8n-main -n $NAMESPACE
export ROUTE=$(oc get route n8n-main -n $NAMESPACE -o jsonpath='{.spec.host}')
echo "n8n URL: http://$ROUTE"

Update the host secret with the Route hostname and restart the pods:

ENCRYPTION_KEY=$(oc get secret n8n-enterprise-secrets -n $NAMESPACE \
  -o jsonpath='{.data.N8N_ENCRYPTION_KEY}' | base64 --decode)

oc create secret generic n8n-enterprise-secrets \
  --namespace $NAMESPACE \
  --from-literal=N8N_ENCRYPTION_KEY="$ENCRYPTION_KEY" \
  --from-literal=N8N_HOST="$ROUTE" \
  --from-literal=N8N_PORT="5678" \
  --from-literal=N8N_PROTOCOL="http" \
  --dry-run=client -o yaml | oc apply -f -

oc rollout restart deployment -n $NAMESPACE
oc rollout status deployment/n8n-main -n $NAMESPACE

Verify all pods are Running with oc get pods -n $NAMESPACE and open the URL in your browser.

Updating, stopping/resuming, and quick reference

Updating (pull and re-patch the new chart version, then helm upgrade):

rm -rf ~/n8n/
helm pull oci://ghcr.io/n8n-io/n8n-helm-chart/n8n --version <new-version> --untar
sed -i '/seccompProfile:/d; /type: RuntimeDefault/d' ~/n8n/templates/deployment-main.yaml

# Standalone
helm upgrade n8n ~/n8n/ --namespace $NAMESPACE --values n8n-standalone-values.yaml
# Multi-instance
helm upgrade n8n ~/n8n/ --namespace $NAMESPACE --values n8n-multimain-ocp-values.yaml

Stop/resume CRC (no need to delete between sessions):

crc stop
crc start

After restarting, re-export the variables and log back in:

eval $(crc oc-env)
export NAMESPACE=n8n-YYYYMMDD   # use your original date
oc login -u kubeadmin -p <password> https://api.crc.testing:6443

The original documentation also includes an extensive Troubleshooting section (SCC/seccomp errors, pods stuck in Pending due to memory, DNS for *.apps-crc.testing, EACCES writing to /home/node/.n8n/, viewing logs with oc logs ...) and a table of items to save (kubeadmin password, encryption key, values files, MinIO and PostgreSQL passwords). See the source for the full detail.


7. Which option to choose

ScenarioRecommended option
Try it quickly on your local machinenpx / npm
Simple, isolated self-hostingDocker
Single production server with automatic HTTPSDocker Compose, DigitalOcean, or Hetzner
Managed one-click deployHeroku
Serverless / quick demo on Google CloudCloud Run — easy mode
Scalable production with KubernetesAWS/EKS, Azure/AKS, or GKE
Test OpenShift without cloud costsOpenShift Local (CRC)

In any case, keep the cross-cutting constants in mind: persist the .n8n directory (the encryption key!), prefer PostgreSQL at scale, and use Kubernetes when you need to scale as demand changes. And, once more: self-hosting is recommended for expert users — if you’d rather not manage infrastructure, there’s n8n Cloud.


8. Sources

All the instructions above derive from the official n8n documentation: