Design Document: N8n Platform
Overview
This document describes the design for a comprehensive n8n platform running in Kubernetes through a custom operator. The platform supports multi-tenancy, declarative workflow management, and follows Kubernetes best practices for namespace isolation, security, and operational simplicity.
Architecture
Namespace Structure
The platform uses a three-namespace architecture:
┌─────────────────────────────────────────────────────────────┐
│ n8n-operator-system │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ N8n Operator │ │
│ │ - Manages N8n CRs │ │
│ │ - Manages N8nWorkflow CRs │ │
│ │ - Provisions databases │ │
│ │ - Provisions admin users │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
│ manages
▼
┌─────────────────────────────────────────────────────────────┐
│ n8n-data │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ PostgreSQL Server (shared) │ │
│ │ ├── Database: n8n_default │ │
│ │ ├── Database: n8n_tenant1 │ │
│ │ └── Database: n8n_tenant2 │ │
│ │ │ │
│ │ Service: postgresql.n8n-data.svc.cluster.local │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
│ connects to
▼
┌─────────────────────────────────────────────────────────────┐
│ n8n (or n8n-tenant-1, n8n-tenant-2, etc.) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ N8n Instance │ │
│ │ - Deployment │ │
│ │ - Service │ │
│ │ - PersistentVolumeClaim (for files) │ │
│ │ - Secrets (admin credentials, API keys) │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Rationale:
- Operator isolation: Operator lifecycle independent of workloads
- Data centralization: Single PostgreSQL server for all instances
- Instance isolation: Each n8n instance in its own namespace
- Resource efficiency: Shared database server reduces overhead
- Multi-tenancy: Easy to add new instances without deploying new databases
Database Connectivity
FQDN Construction
The operator automatically constructs fully qualified domain names (FQDNs) for database connections:
// If hostname doesn't contain a dot, construct FQDN
if !containsDot(host) {
host = fmt.Sprintf("%s.%s.svc.cluster.local", host, databaseNamespace)
}Examples:
- Input:
postgresql→ Output:postgresql.n8n-data.svc.cluster.local - Input:
postgres.example.com→ Output:postgres.example.com(unchanged)
Connection Flow
N8n Instance (n8n namespace)
│
│ 1. Read N8n CR spec
│ - database.postgres.host: "postgresql"
│ - database.postgres.database: "n8n_default"
│
▼
Operator (n8n-operator-system)
│
│ 2. Construct FQDN
│ - postgresql → postgresql.n8n-data.svc.cluster.local
│
│ 3. Build connection string
│ - host=postgresql.n8n-data.svc.cluster.local
│ - port=5432
│ - user=n8n_default_user
│ - password=<from secret>
│ - dbname=n8n_default
│
▼
PostgreSQL (n8n-data namespace)
│
│ 4. Authenticate and connect
│
▼
Database: n8n_default
Multi-Tenant Database Management
Database Provisioning
When a new N8n instance is created, the operator:
- Generates unique database name:
n8n_<instance_name> - Creates database:
CREATE DATABASE n8n_<instance_name> - Creates user:
CREATE USER n8n_<instance_name>_user WITH PASSWORD '<generated>' - Grants permissions:
GRANT ALL PRIVILEGES ON DATABASE n8n_<instance_name> TO n8n_<instance_name>_user - Stores credentials: Creates Kubernetes secret in instance namespace
Database Isolation
Each instance has:
- Separate database: No shared tables or data
- Separate credentials: Unique username/password per instance
- PostgreSQL-level isolation: User can only access their own database
-- Example for instance "default"
CREATE DATABASE n8n_default;
CREATE USER n8n_default_user WITH PASSWORD 'secure-random-password';
GRANT ALL PRIVILEGES ON DATABASE n8n_default TO n8n_default_user;
REVOKE CONNECT ON DATABASE n8n_default FROM PUBLIC;
GRANT CONNECT ON DATABASE n8n_default TO n8n_default_user;Component Design
1. Operator Architecture
Controllers
N8nReconciler
- Manages N8n instance lifecycle
- Provisions databases
- Creates deployments, services, PVCs
- Manages admin user provisioning
- Handles instance deletion and cleanup
N8nWorkflowReconciler
- Manages N8nWorkflow CRD lifecycle
- Syncs workflows to n8n API
- Manages workflow credentials
- Handles workflow activation/deactivation
Database Manager
type DatabaseManager struct {
client client.Client
pgHost string
pgPort int
adminUser string
adminPass string
}
func (dm *DatabaseManager) ProvisionDatabase(ctx context.Context, instance *n8nv1alpha1.N8n) error {
// 1. Connect to PostgreSQL as admin
// 2. Check if database exists
// 3. Create database if needed
// 4. Create user with generated password
// 5. Grant permissions
// 6. Store credentials in secret
}
func (dm *DatabaseManager) CleanupDatabase(ctx context.Context, instance *n8nv1alpha1.N8n) error {
// 1. Connect to PostgreSQL as admin
// 2. Terminate active connections
// 3. Drop database
// 4. Drop user
// 5. Delete credentials secret
}2. Multi-Architecture Build System
Build Pipeline
# GitHub Actions workflow
name: Build and Push Operator
on:
push:
branches: [main]
paths:
- 'internal/**'
- 'api/**'
- 'cmd/**'
- 'Dockerfile'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build and push
uses: docker/build-push-action@v4
with:
platforms: linux/amd64,linux/arm64
push: true
tags: |
ghcr.io/craigedmunds/n8n-operator:${{ github.sha }}
ghcr.io/craigedmunds/n8n-operator:latestVersion Management
- Development builds:
0.51.4-dev-20260115-184508 - Release builds:
0.51.4(semantic versioning) - Version file:
n8n-operator/.version
3. Workflow Management
N8nWorkflow CRD
apiVersion: n8n.slys.dev/v1alpha1
kind: N8nWorkflow
metadata:
name: example-workflow
namespace: n8n
spec:
# Reference to N8n instance
n8nRef:
name: n8n
namespace: n8n
# Workflow definition
workflow:
name: "Example Workflow"
active: true
nodes:
- name: "Start"
type: "n8n-nodes-base.start"
position: [250, 300]
- name: "HTTP Request"
type: "n8n-nodes-base.httpRequest"
position: [450, 300]
parameters:
url: "https://api.example.com/data"
authentication: "genericCredentialType"
genericAuthType: "httpBasicAuth"
credentials:
httpBasicAuth:
secretRef:
name: api-credentials
key: basic-auth
connections:
Start:
main:
- - node: "HTTP Request"
type: "main"
index: 0
status:
workflowId: "123"
synced: true
lastSync: "2026-01-15T18:45:00Z"Workflow Controller Flow
1. Watch N8nWorkflow resources
│
▼
2. Validate workflow definition
│
▼
3. Resolve credential references
│
▼
4. Inject credentials into n8n
│
▼
5. Create/update workflow via n8n API
│
▼
6. Update status with workflow ID
│
▼
7. Monitor workflow health
4. Credential Management
Credential Injection Flow
Kubernetes Secret (n8n namespace)
│
│ 1. N8nWorkflow references secret
│
▼
Workflow Controller
│
│ 2. Read secret data
│
│ 3. Transform to n8n credential format
│
▼
N8n API (create credential)
│
│ 4. Store credential in n8n database
│
▼
Workflow uses credential
Credential Types
HTTP Basic Auth:
apiVersion: v1
kind: Secret
metadata:
name: api-credentials
namespace: n8n
type: Opaque
stringData:
username: "api-user"
password: "api-password"API Key:
apiVersion: v1
kind: Secret
metadata:
name: api-key
namespace: n8n
type: Opaque
stringData:
apiKey: "sk-1234567890abcdef"5. Admin User Provisioning
Provisioning Flow
N8n Instance Created
│
▼
Wait for deployment ready
│
▼
Connect to database (FQDN)
│
▼
Generate secure password
│
▼
Hash password (bcrypt)
│
▼
Insert admin user into database
│
▼
Store credentials in secret
│
▼
Verify authentication
│
▼
Mark instance ready
Admin User Schema
INSERT INTO "user" (
id,
email,
password,
"firstName",
"lastName",
"globalRole",
"createdAt",
"updatedAt"
) VALUES (
'operator-admin-user',
'operator@n8n.local',
'<bcrypt-hash>',
'Operator',
'Admin',
'owner',
NOW(),
NOW()
);6. Upstream Maintenance
Security Analysis
Workflow File Analysis:
- Detect unpinned third-party actions
- Detect
pull_request_targetwith unsafe checkout - Detect credential exposure patterns
- Detect suspicious script execution
Dependency Analysis:
- Verify dependency sources
- Check for known vulnerabilities
- Require manual review for major updates
Sync Process
1. Detect upstream changes
│
▼
2. Analyze security risks
│
▼
3. Present summary to maintainer
│
▼
4. Manual approval
│
▼
5. Apply changes to fork
│
▼
6. Run test suite
│
▼
7. Verify build succeeds
RBAC Configuration
Operator Permissions
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: n8n-operator-role
rules:
# N8n CRD management
- apiGroups: ["n8n.slys.dev"]
resources: ["n8ns", "n8nworkflows"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# N8n status updates
- apiGroups: ["n8n.slys.dev"]
resources: ["n8ns/status", "n8nworkflows/status"]
verbs: ["get", "update", "patch"]
# Deployment management
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# Service management
- apiGroups: [""]
resources: ["services"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# Secret management (cross-namespace)
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# PVC management
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]Database Access
The operator needs a PostgreSQL admin user to provision databases:
apiVersion: v1
kind: Secret
metadata:
name: postgresql-admin
namespace: n8n-operator-system
type: Opaque
stringData:
username: "postgres"
password: "<admin-password>"
host: "postgresql.n8n-data.svc.cluster.local"
port: "5432"Deployment Configuration
PostgreSQL Deployment (n8n-data namespace)
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgresql
namespace: n8n-data
spec:
replicas: 1
selector:
matchLabels:
app: postgresql
template:
metadata:
labels:
app: postgresql
spec:
containers:
- name: postgresql
image: postgres:15
env:
- name: POSTGRES_USER
value: "postgres"
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgresql-admin
key: password
- name: PGDATA
value: "/var/lib/postgresql/data/pgdata"
ports:
- containerPort: 5432
volumeMounts:
- name: postgresql-data
mountPath: /var/lib/postgresql/data
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "2000m"
volumes:
- name: postgresql-data
persistentVolumeClaim:
claimName: postgresql-pvc
---
apiVersion: v1
kind: Service
metadata:
name: postgresql
namespace: n8n-data
spec:
selector:
app: postgresql
ports:
- port: 5432
targetPort: 5432
type: ClusterIPN8n Instance CR
apiVersion: n8n.slys.dev/v1alpha1
kind: N8n
metadata:
name: n8n
namespace: n8n
spec:
version: "2.2.2"
database:
postgres:
# Short name - operator will construct FQDN
host: postgresql
# Operator will create this database
database: n8n_default
# Operator will create this user
user: n8n_default_user
# Operator will generate and store password
passwordSecretRef:
name: n8n-db-credentials
key: password
port: 5432
ssl: false
persistentStorage:
enable: true
storageClassName: local-path
size: 10Gi
ingress:
enable: false
httpRoute:
enable: falseTesting Strategy
Unit Tests
- Database manager functions
- FQDN construction logic
- Credential transformation
- Admin user provisioning
Integration Tests
- Operator deployment
- N8n instance creation
- Database provisioning
- Workflow sync with mock n8n API
End-to-End Tests
- Deploy operator
- Deploy PostgreSQL
- Create N8n instance
- Verify database created
- Verify admin user provisioned
- Create N8nWorkflow
- Verify workflow appears in n8n
- Delete N8nWorkflow
- Verify workflow removed
- Delete N8n instance
- Verify database cleaned up
Operational Tooling
Cleanup Tool
# List all workflows
n8n-cleanup list --instance n8n --namespace n8n
# Delete workflows by name pattern
n8n-cleanup delete --instance n8n --namespace n8n --name-pattern "test-*" --dry-run
# Delete all inactive workflows
n8n-cleanup delete --instance n8n --namespace n8n --inactive --dry-run
# Execute deletion
n8n-cleanup delete --instance n8n --namespace n8n --inactiveReset Tool
# Complete instance reset (requires confirmation)
n8n-reset --instance n8n --namespace n8n
# This will:
# 1. Delete all workflows
# 2. Delete all credentials
# 3. Drop and recreate database
# 4. Restart n8n deploymentMigration Path
From Current Setup to New Architecture
- Deploy n8n-data namespace and PostgreSQL
- Migrate existing data (if needed)
- Update N8n CR to reference new database location
- Redeploy operator with new FQDN logic
- Verify connectivity
- Remove old per-instance databases
Backward Compatibility
The operator supports both architectures during migration:
- New instances: Use shared database in n8n-data
- Existing instances: Continue using per-instance databases
- Migration flag:
spec.database.postgres.shared: true/false
Security Considerations
Network Policies
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: postgresql-access
namespace: n8n-data
spec:
podSelector:
matchLabels:
app: postgresql
policyTypes:
- Ingress
ingress:
# Allow from operator
- from:
- namespaceSelector:
matchLabels:
name: n8n-operator-system
# Allow from n8n instances
- from:
- namespaceSelector:
matchLabels:
n8n-instance: "true"Secret Management
- Admin credentials stored in operator namespace
- Instance credentials stored in instance namespace
- Credentials rotated on instance recreation
- ESO integration for centralized secret management
Performance Considerations
Database Connection Pooling
Each n8n instance maintains its own connection pool:
- Max connections: 10 per instance
- Idle timeout: 5 minutes
- Connection lifetime: 30 minutes
PostgreSQL configuration:
max_connections: 100 (supports ~10 instances)shared_buffers: 512MBeffective_cache_size: 1.5GB
Resource Limits
Operator:
- CPU: 100m request, 500m limit
- Memory: 128Mi request, 512Mi limit
PostgreSQL:
- CPU: 500m request, 2000m limit
- Memory: 512Mi request, 2Gi limit
N8n Instance:
- CPU: 250m request, 1000m limit
- Memory: 512Mi request, 2Gi limit
Monitoring and Observability
Metrics
- Operator reconciliation duration
- Database provisioning success/failure rate
- Workflow sync success/failure rate
- Active n8n instances count
- Database connection count per instance
Logging
- Structured logging (JSON format)
- Log levels: DEBUG, INFO, WARN, ERROR
- Correlation IDs for request tracing
- Sensitive data redaction (passwords, tokens)
Future Enhancements
- Database High Availability: PostgreSQL replication
- Backup and Restore: Automated database backups
- Metrics Dashboard: Grafana dashboards for monitoring
- Workflow Templates: Reusable workflow templates
- Multi-Cluster Support: Federated n8n instances
- External Database Support: Cloud-managed PostgreSQL (RDS, Cloud SQL)