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:

  1. Generates unique database name: n8n_<instance_name>
  2. Creates database: CREATE DATABASE n8n_<instance_name>
  3. Creates user: CREATE USER n8n_<instance_name>_user WITH PASSWORD '<generated>'
  4. Grants permissions: GRANT ALL PRIVILEGES ON DATABASE n8n_<instance_name> TO n8n_<instance_name>_user
  5. 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:latest

Version 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_target with 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: ClusterIP

N8n 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: false

Testing 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 --inactive

Reset 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 deployment

Migration Path

From Current Setup to New Architecture

  1. Deploy n8n-data namespace and PostgreSQL
  2. Migrate existing data (if needed)
  3. Update N8n CR to reference new database location
  4. Redeploy operator with new FQDN logic
  5. Verify connectivity
  6. 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: 512MB
  • effective_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

  1. Database High Availability: PostgreSQL replication
  2. Backup and Restore: Automated database backups
  3. Metrics Dashboard: Grafana dashboards for monitoring
  4. Workflow Templates: Reusable workflow templates
  5. Multi-Cluster Support: Federated n8n instances
  6. External Database Support: Cloud-managed PostgreSQL (RDS, Cloud SQL)