Skip to main content

N8N Common Shared Configuration Module

The N8N Common module defines the n8n workflow automation platform (without AI components) for the RAD Modules ecosystem. It creates GCP resources (two Secret Manager secrets) and produces a config output consumed by platform-specific wrapper modules (N8N CloudRun and N8N GKE).

1. Overview

Purpose: To centralize all n8n-specific configuration — PostgreSQL backend, Redis queue support, GCS data storage, SMTP credentials, and an encryption key secret — in a single module shared by Cloud Run and GKE deployments. This is the standard n8n module; for deployments that also require Qdrant and Ollama AI sidecars, use N8N AI Common instead.

Architecture:

Layer 3: Application Wrappers
├── N8N_CloudRun ──┐
└── N8N_GKE ──┤── instantiate N8N_Common

N8N_Common (this module)
Creates: 2 Secret Manager secrets (SMTP password, encryption key)
Produces: config, storage_buckets, secret_ids, secret_values,
resource_prefix, path

Layer 2: Platform Modules
├── App_CloudRun (serverless deployment)
└── App_GKE (Kubernetes deployment)

Layer 1: App_Common (networking, database, storage, secrets, IAM)

Key differences from N8N AI Common:

  • No AI sidecar services — config.additional_services is not present.
  • Redis is disabled by default (enable_redis = false).
  • Health probes target /healthz (not /), and the startup probe uses a shorter 10s initial delay with a higher 30-failure threshold.
  • Uses resource_prefix (not wrapper_prefix) for naming, and exposes it as an output.
  • Secrets are output together as a secret_ids map rather than as individual outputs.
  • The SMTP secret is seeded with a dummy placeholder value; the real SMTP password is expected to be provided by the caller or set post-deployment.
  • DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED is deliberately not set — see §4 for the reason.
  • Default resources: 1000m CPU / 2Gi memory (lighter than the AI variant).

2. GCP Resources Created

Secret IDContentPurpose
<resource_prefix>-smtp-password16-char random alphanumeric (dummy)Placeholder for n8n outbound SMTP password — replaced by the caller with the real credential
<resource_prefix>-encryption-key32-char random (with special chars)n8n N8N_ENCRYPTION_KEY — encrypts stored workflow credentials in the database

A 30-second time_sleep is applied after both secret versions are written before the secret_ids output is resolved.

Note on SMTP secret: The SMTP password secret is seeded with a generated dummy value at provisioning time. Wrapper modules are expected to either override this value post-deployment or wire in the real SMTP credential via a separate mechanism. The secret ID is still included in secret_ids so the container receives the secret reference — callers must update the secret version with the real password before n8n sends email.


3. Outputs

config

The application configuration object passed to the platform module via application_config.

FieldValue / Description
app_namefrom application_name (default: "n8n")
display_namefrom display_name (default: "n8n Workflow Automation")
container_image"n8nio/n8n" (public base image)
image_source"custom"
enable_image_mirroringtrue (configurable variable — default: true)
container_build_configdockerfile_path = "Dockerfile", context_path = ".", no build args
container_port5678
database_type"POSTGRES_15"
db_namefrom db_name variable (default: "n8n")
db_userfrom db_user variable (default: "n8n")
enable_cloudsql_volumeWhether to mount Cloud SQL Auth Proxy sidecar (default: true)
cloudsql_volume_mount_path"/cloudsql"
gcs_volumesPassed through from var.gcs_volumes (empty by default)
container_resourcesCPU/memory limits; no requests set
min_instance_count0 (scale-to-zero)
max_instance_count3
environment_variablesMerged map — see §4
enable_postgres_extensionsfalse
postgres_extensions[]
initialization_jobsDefault db-init job or custom override — see §6
startup_probeHTTP GET /healthz, 10s initial delay, 5s timeout, 10s period, 30 failure threshold
liveness_probeHTTP GET /healthz, 15s initial delay, 5s timeout, 30s period, 3 failure threshold

storage_buckets

One GCS bucket for n8n workflow data:

FieldValue
name<resource_prefix>-storage (explicit, not just a suffix)
name_suffix"n8n-data"
locationDeployment region
storage_class"STANDARD"
versioning_enabledfalse
public_access_prevention"inherited"

secret_ids

A map of n8n secret environment variable names to their Secret Manager secret IDs, with depends_on on the 30-second propagation wait:

{
N8N_SMTP_PASS = "<resource_prefix>-smtp-password"
N8N_ENCRYPTION_KEY = "<resource_prefix>-encryption-key"
}

secret_values

A sensitive map of raw generated values for GKE deployments that bypass Secret Manager read-after-write:

{
N8N_SMTP_PASS = "<16-char dummy password>"
N8N_ENCRYPTION_KEY = "<32-char encryption key>"
}

resource_prefix

Exposes var.resource_prefix directly. Wrapper modules use this to reference the prefix when constructing other resource names (e.g., IAM bindings, bucket references) without having to pass it through separately.

path

Absolute path to the module directory, used by wrapper modules to locate scripts/.


4. Environment Variables

The module merges a fixed set of n8n configuration variables with caller-provided environment_variables (caller variables take precedence):

VariableValuePurpose
N8N_PORT"5678"n8n listening port
N8N_PROTOCOL"https"Public protocol
N8N_DIAGNOSTICS_ENABLED"true"Usage telemetry
N8N_METRICS"true"Prometheus metrics endpoint
N8N_SECURE_COOKIE"false"Disable secure cookie (Cloud Run terminates TLS)
DB_TYPE"postgresdb"n8n database backend
N8N_DEFAULT_BINARY_DATA_MODE"filesystem"Store binary data on disk (GCS Fuse volume)
WEBHOOK_URLvar.service_urlPublic webhook base URL
N8N_EDITOR_BASE_URLvar.service_urlEditor base URL
ENABLE_REDIS"true" / "false"Redis queue mode toggle
QUEUE_BULL_REDIS_HOSTvar.redis_host or "$(NFS_SERVER_IP)"Redis host; placeholder expanded at runtime when no explicit host is set
QUEUE_BULL_REDIS_PORTvar.redis_port (when enabled)Redis port
QUEUE_BULL_REDIS_PASSWORDvar.redis_auth (when set)Redis auth

DB_POSTGRESDB_* variables are intentionally absent from the Terraform-set environment. The DB_HOST, DB_NAME, DB_USER, and DB_PASSWORD platform variables are translated to DB_POSTGRESDB_* at runtime by entrypoint.sh. This separation avoids a specific Cloud SQL Auth Proxy incompatibility:

Setting DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED=false would instruct n8n's PostgreSQL driver to attempt an SSL handshake with relaxed certificate verification. However, the Cloud SQL Auth Proxy does not support client-side SSL on its local interface — it handles encryption internally — causing the error: "The server does not support SSL connections". By leaving DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED unset, SSL is disabled in the pg driver (its default), which is correct when using the Auth Proxy.


5. Database Naming

The module computes two local values from resource_prefix that are available for use within the module:

database_name_full = replace(var.resource_prefix, "-", "_")
database_user_full = replace(var.resource_prefix, "-", "_")

These convert the hyphen-separated resource prefix into an underscore-separated PostgreSQL identifier (hyphens are not valid in unquoted PostgreSQL names). However, the actual db_name and db_user used in the config output come from var.db_name and var.db_user directly — these locals are available for wrapper module reference but are not wired into the config by default.


6. Initialization Job

One db-init job runs by default (when initialization_jobs = []):

FieldValue
Imagepostgres:15-alpine
Scriptscripts/db-init.sh
Secrets requiredROOT_PASSWORD (PostgreSQL superuser), DB_PASSWORD (app user)
execute_on_applytrue
Timeout600s, 1 retry

db-init.sh behavior:

  1. Detects Cloud SQL Auth Proxy socket: if /cloudsql contains a socket file, symlinks it to /tmp/.s.PGSQL.5432 and sets DB_HOST=/tmp.
  2. Resolves the target host from DB_IP, then DB_HOST, then DB_POSTGRESDB_HOST.
  3. Polls PostgreSQL using pg_isready until available.
  4. Creates (or updates the password of) the n8n application user.
  5. Grants the application user role to postgres (to allow ownership assignment).
  6. Creates the n8n database owned by the application user if absent; otherwise reassigns ownership.
  7. Grants full privileges on the database and public schema.
  8. Signals Cloud SQL Proxy shutdown via wget POST http://127.0.0.1:9091/quitquitquit (up to 10 retries, 1s apart).

7. Scripts and Container Image

Dockerfile

Identical in structure to N8N AI Common's Dockerfile, with one difference in the ENTRYPOINT:

  • Base image: node:22-alpine3.22
  • Installs: python3, py3-pip, git, bash, curl, jq, tini, su-exec
  • Installs n8n globally at the pinned version: npm install -g n8n@2.4.7
  • Creates node group (GID 1000) and user (UID 1000)
  • Sets N8N_USER_FOLDER=/home/node/.n8n and N8N_PORT=5678
  • ENTRYPOINT: ["tini", "--", "/entrypoint.sh"] — tini is explicit here as PID 1 (in N8N AI Common tini is called implicitly via exec)

entrypoint.sh

Identical to N8N AI Common's entrypoint. Translates platform variables to n8n-native names before starting n8n:

  1. Socket detection: If DB_HOST starts with /, symlinks to /tmp/.s.PGSQL.5432 and resets DB_HOST=/tmp.
  2. DB variable mapping (only when the n8n-native variable is not already set):
Platform variablen8n variable
DB_HOSTDB_POSTGRESDB_HOST
DB_NAMEDB_POSTGRESDB_DATABASE
DB_USERDB_POSTGRESDB_USER
DB_PASSWORDDB_POSTGRESDB_PASSWORD
  1. Redis host resolution: Expands $(NFS_SERVER_IP) in QUEUE_BULL_REDIS_HOST to the runtime NFS server IP.
  2. exec n8n "$@" — starts n8n as the final process under tini.

8. Input Variables

Project & Identity

VariableTypeDefaultDescription
project_idstringrequiredGCP project ID
resource_prefixstringrequiredPrefix for all resource IDs and secret names
labelsmap(string){}Labels applied to secrets
tenant_deployment_idstring"demo"Tenant identifier (1–20 lowercase alphanumeric chars)
deployment_idstring""Unique deployment ID suffix
regionstring"us-central1"Region for the storage bucket

Application

VariableTypeDefaultDescription
application_namestring"n8n"Application name
application_versionstring"latest"n8n version tag
display_namestring"n8n Workflow Automation"Display name
descriptionstring"n8n is an extendable workflow automation tool"Description
db_namestring"n8n"PostgreSQL database name
db_userstring"n8n"PostgreSQL application user
cpu_limitstring"1000m"Container CPU limit
memory_limitstring"2Gi"Container memory limit
min_instance_countnumber0Minimum instances (scale-to-zero)
max_instance_countnumber3Maximum instances
environment_variablesmap(string){}Additional env vars merged over module defaults
initialization_jobslist(any)[]Custom init jobs; empty triggers default db-init
startup_probeobject/healthz, 10s delay, 30 thresholdStartup health probe
liveness_probeobject/healthz, 15s delay, 3 thresholdLiveness health probe
enable_cloudsql_volumebooltrueMount Cloud SQL Auth Proxy sidecar socket
enable_image_mirroringbooltrueMirror the container image to the project's Artifact Registry before deployment

External Integration

VariableTypeDefaultDescription
service_urlstring""Public URL set as WEBHOOK_URL and N8N_EDITOR_BASE_URL
db_hoststringnullExplicit DB host override; defaults to /cloudsql when Auth Proxy is enabled
enable_redisboolfalseEnable Redis queue-mode operation
redis_hoststringnullRedis host; if null/empty, uses $(NFS_SERVER_IP) placeholder
redis_portstring"6379"Redis port
redis_authstring""Redis auth password (sensitive)
nfs_server_ipstringnullNFS server IP for Redis host resolution (when using NFS-hosted Redis)

Storage

VariableTypeDefaultDescription
gcs_volumeslist(object)[]GCS Fuse volume mounts; no default volume unlike N8N AI Common
bucket_namestring""Legacy: GCS bucket name reference (not wired into config)
service_account_emailstring""Legacy: service account email reference

9. Platform-Specific Differences

AspectN8N CloudRunN8N GKE
service_urlPredicted Cloud Run service URL (https://<prefix>-<project_number>.<region>.run.app)Internal ClusterIP URL (http://<service>.<namespace>.svc.cluster.local), computed before deployment
enable_cloudsql_volumetrue (Auth Proxy sidecar, default)true (Auth Proxy sidecar, default)
DB_HOSTCloud SQL Auth Proxy socket path (/cloudsql)Cloud SQL Auth Proxy socket path (/cloudsql) when enable_cloudsql_volume = true
NFSEnabled by default (enable_nfs = true); provides shared persistenceEnabled by default (enable_nfs = true) via enable_nfs
RedisOptional; $(NFS_SERVER_IP) placeholder when enabledOptional; $(NFS_SERVER_IP) placeholder when enabled
GCS volumesOptional via gcs_volumesOptional via gcs_volumes
ScalingServerless, scale-to-zero (min_instance_count = 0)Kubernetes Deployment with configurable replicas

10. Implementation Pattern

# Example: how N8N_CloudRun instantiates N8N_Common

module "n8n_app" {
source = "../N8N_Common"

# Project & Deployment
project_id = var.project_id
resource_prefix = local.resource_prefix
labels = var.resource_labels
tenant_deployment_id = var.tenant_deployment_id
deployment_id = local.random_id
deployment_region = local.region

# Application Details
application_name = var.application_name
application_version = var.application_version
display_name = var.display_name
description = var.description

# Database
db_name = var.db_name
db_user = var.db_user

# Container Resources
cpu_limit = var.cpu_limit
memory_limit = var.memory_limit
min_instance_count = var.min_instance_count
max_instance_count = var.max_instance_count

# Probes
startup_probe = var.startup_probe
liveness_probe = var.liveness_probe

# Integration
service_url = local.predicted_service_url

# Redis (redis_auth is NOT passed here; App_CloudRun handles it directly)
enable_redis = var.enable_redis
redis_host = var.redis_host
redis_port = var.redis_port
nfs_server_ip = null

# Environment & Initialization
environment_variables = var.environment_variables
enable_cloudsql_volume = var.enable_cloudsql_volume
initialization_jobs = var.initialization_jobs

# Storage
gcs_volumes = var.gcs_volumes
}

# config and secrets are passed to App_CloudRun
module "app_cloudrun" {
source = "../App_CloudRun"

application_config = { n8n = merge(module.n8n_app.config, { description = var.description, container_port = var.container_port }) }
module_storage_buckets = module.n8n_app.storage_buckets
module_secret_env_vars = module.n8n_app.secret_ids
module_explicit_secret_values = module.n8n_app.secret_values
scripts_dir = abspath("${module.n8n_app.path}/scripts")
# ... other inputs
}