tva
← Approfondimenti

Costruire uno stack di sviluppo multi-tenant con Docker: configurazione completa per distribuzioni scalabili per i clienti

Come creare un ambiente di sviluppo multi-tenant basato su template, con 16 servizi in container, che funziona offline ma resta accessibile online tramite instradamento basato su sottodomini

Gestire ambienti di sviluppo per più clienti significa spesso scegliere tra configurazioni manuali complesse e soluzioni cloud costose. Le distribuzioni manuali richiedono tempo e sono soggette a errori. Le piattaforme cloud sono comode, ma creano dipendenza dal fornitore e costi ricorrenti che crescono con l’utilizzo.

Vediamo come costruire uno stack di sviluppo multi-tenant scalabile che offre entrambi i vantaggi: isolamento solido tra gli ambienti dei clienti e distribuzione automatizzata, mantenendo il pieno controllo dell’infrastruttura. L’approccio segue la nostra filosofia delle soluzioni gestite sulla propria infrastruttura, come nelle guide che mostrano come gestire n8n sulla propria infrastruttura per automatizzare i flussi di lavoro e distribuire Windmill con Docker per un controllo operativo completo.

Gli strumenti che utilizziamo

Iniziamo dal ruolo di ciascun componente nella nostra architettura completa a 16 container:

Docker: la base per la containerizzazione

Docker fornisce l’isolamento e la coerenza necessari agli ambienti multi-tenant. Ogni cliente ha i propri container con configurazioni identiche, il che aiuta a garantire che ciò che funziona in sviluppo funzioni anche in produzione. È come avere più server separati sullo stesso hardware, pur condividendone CPU, memoria, disco e kernel.

Il vantaggio principale è un solido isolamento tra i clienti. Dati, configurazioni e personalizzazioni di ciascun cliente risiedono in container, reti e volumi separati, così da non interferire con quelli degli altri. È importante quando si gestiscono più clienti aziendali con requisiti ed esigenze di sicurezza diversi.

Traefik: reverse proxy intelligente e bilanciatore di carico

Traefik dirige il traffico in modo intelligente, instradando automaticamente le richieste all’ambiente del cliente corretto in base al dominio. Invece di configurare manualmente regole complesse per Apache o Nginx, legge le etichette dei container Docker e configura automaticamente l’instradamento.

Pensa a Traefik come a un addetto alla reception che sa esattamente a quale ufficio (container) indirizzare ogni visitatore (richiesta), senza bisogno di istruzioni ogni volta. Nella nostra configurazione gestisce la terminazione SSL e il rilevamento automatico dei servizi e fornisce dashboard di monitoraggio dettagliate.

Cloudflare Tunnels: accesso esterno sicuro

Cloudflare Tunnels consente l’accesso sicuro allo stack di sviluppo locale senza configurazioni complesse del firewall o VPN. Ogni dominio cliente ha il proprio tunnel, che mantiene separato il percorso di accesso esterno di ciascun cliente.

Il vantaggio è che gli ambienti di sviluppo restano locali e sicuri, ma i clienti possono accedere ai propri servizi da qualsiasi luogo con l’autenticazione appropriata, come nell’accesso esterno sicuro configurato nella nostra guida all’hosting di n8n.

Lo stack completo di servizi: tutto ciò che serve ai tuoi clienti

Il nostro stack multi-tenant comprende quattro categorie principali di servizi distribuite su 16 container per cliente:

Automazione dei flussi di lavoro e logica aziendale:

  • n8n: piattaforma completa per automatizzare flussi di lavoro e processi aziendali
  • Authentik: single sign-on e gestione delle identità di livello aziendale (3 container: server, worker, cache Redis)

Database e servizi backend:

  • PostgreSQL: backend database affidabile a supporto di tutti i servizi, con pooling delle connessioni ottimizzato
  • Supabase Stack: backend-as-a-service completo con 6 container specializzati (Studio, Meta, Auth, REST API, Realtime, Kong Gateway)
  • NocoDB: interfaccia no-code per gestire i dati dei clienti nel database

IA e intelligenza:

  • Ollama: modelli linguistici IA locali con accelerazione GPU per l’automazione intelligente
  • Qdrant (opzionale): database vettoriale per flussi di lavoro IA avanzati e ricerca per similarità

Infrastruttura e monitoraggio:

  • Cloudflare Tunnel: connettività esterna sicura
  • Traefik: reverse proxy con SSL automatico e dashboard di monitoraggio

Come funzionano insieme i componenti

Ecco il flusso completo quando un cliente accede al proprio ambiente:

  1. Il cliente visita il proprio dominio personalizzato (per esempio workflows.client-a.com)
  2. Cloudflare Tunnel instrada la richiesta alla tua istanza Traefik locale
  3. Traefik legge il dominio, applica i middleware (autenticazione, SSL, limitazione della frequenza delle richieste) e inoltra la richiesta al container del cliente corretto
  4. Authentik gestisce l’autenticazione SSO su tutti i servizi, se configurato
  5. Il cliente accede al proprio ambiente isolato, con i suoi dati e le sue configurazioni
  6. Gli ambienti degli altri clienti non sono raggiungibili attraverso le route di questo cliente

Tutto rimane organizzato e separato, con una struttura di sottodomini per ogni cliente, come auth.client-a.com, database.client-a.com, backend.client-a.com, ecc.

Configurazione pratica: i passaggi concreti

Preparare le basi

Per prima cosa servono Docker Desktop installato e un sistema di gestione dei domini. Consigliamo una struttura DNS wildcard per facilitare l’attivazione dei clienti:

# Install Docker Desktop (macOS)
brew install --cask docker

# Verify installation  
docker --version
docker-compose --version

# Ensure sufficient resources for multi-container environments
# Recommended: 16GB RAM, 8+ CPU cores, 500GB+ SSD storage

Creazione del sistema di template

L’efficacia deriva dall’approccio basato su template. Anziché configurare manualmente ogni cliente, creiamo template distribuibili subito con configurazioni specifiche per ciascuno.

Crea la struttura completa delle directory:

mkdir -p development-stack/{template,deployments}
cd development-stack/template

# Create service-specific configuration directories
mkdir -p {traefik,authentik,supabase,init}

Configurazione completa del template multiservizio

Crea un template docker-compose.yml completo con tutti i 16 servizi. È un template di sviluppo: la dashboard di Traefik funziona senza autenticazione, quindi il template la pubblica, insieme a PostgreSQL, solo su 127.0.0.1, dove le altre macchine non possono raggiungerle (vedi la sezione sul rafforzamento della sicurezza in produzione più avanti):

version: '3.8'

networks:
  default:
    name: ${TENANT_NETWORK}
    driver: bridge

services:
  # External Connectivity
  cloudflare-tunnel:
    image: cloudflare/cloudflared:latest
    container_name: ${TENANT_PREFIX}-tunnel
    command: tunnel --no-autoupdate run --token ${CLOUDFLARE_TOKEN}
    networks:
      - default
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "cloudflared tunnel info ${TUNNEL_ID} || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3

  # Reverse Proxy & Load Balancer
  traefik:
    image: traefik:v3.0
    container_name: ${TENANT_PREFIX}-traefik
    command:
      - "--api.dashboard=true"
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.network=${TENANT_NETWORK}"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.email=${ADMIN_EMAIL}"
      - "--certificatesresolvers.letsencrypt.acme.storage=/acme.json"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
    ports:
      - "${TRAEFIK_PORT}:80"
      - "${TRAEFIK_SECURE_PORT}:443" 
      - "127.0.0.1:${TRAEFIK_DASHBOARD_PORT}:8080"
    networks:
      - default
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik/acme.json:/acme.json
    healthcheck:
      test: ["CMD", "traefik", "healthcheck"]
      interval: 30s
      timeout: 10s
      retries: 3

  # Database Backend
  postgres:
    image: postgres:15-alpine
    container_name: ${TENANT_PREFIX}-postgres
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_MULTIPLE_DATABASES: n8n,supabase,authentik,nocodb
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init:/docker-entrypoint-initdb.d
    networks:
      - default
    ports:
      - "127.0.0.1:${POSTGRES_PORT}:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 30s
      timeout: 10s
      retries: 5
    deploy:
      resources:
        limits:
          memory: 2G
        reservations:
          memory: 1G

  # Workflow Automation
  n8n:
    image: n8nio/n8n:latest
    container_name: ${TENANT_PREFIX}-n8n
    environment:
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_DATABASE: n8n
      DB_POSTGRESDB_USER: ${POSTGRES_USER}
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
      N8N_PROTOCOL: ${N8N_PROTOCOL}
      N8N_HOST: ${N8N_DOMAIN}
      N8N_PORT: 5678
      N8N_SECURE_COOKIE: ${N8N_SECURE_COOKIE}
      WEBHOOK_URL: https://${N8N_DOMAIN}
      N8N_EDITOR_BASE_URL: https://${N8N_DOMAIN}
      EXECUTIONS_DATA_PRUNE: "true"
      EXECUTIONS_DATA_MAX_AGE: 168
    volumes:
      - n8n_data:/home/node/.n8n
    networks:
      - default
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.n8n.rule=Host(`${N8N_DOMAIN}`)"
      - "traefik.http.routers.n8n.tls.certresolver=letsencrypt"
      - "traefik.http.services.n8n.loadbalancer.server.port=5678"
      - "traefik.http.routers.n8n.middlewares=${AUTH_MIDDLEWARE}"
    depends_on:
      postgres:
        condition: service_healthy

  # No-Code Database Interface  
  nocodb:
    image: nocodb/nocodb:latest
    container_name: ${TENANT_PREFIX}-nocodb
    environment:
      NC_DB: "pg://postgres:${POSTGRES_PASSWORD}@postgres:5432/nocodb"
      NC_PUBLIC_URL: https://${NOCODB_DOMAIN}
      NC_DISABLE_TELE: "true"
      NC_ADMIN_EMAIL: ${ADMIN_EMAIL}
      NC_ADMIN_PASSWORD: ${NOCODB_ADMIN_PASSWORD}
    volumes:
      - nocodb_data:/usr/app/data
    networks:
      - default
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.nocodb.rule=Host(`${NOCODB_DOMAIN}`)"
      - "traefik.http.routers.nocodb.tls.certresolver=letsencrypt"
      - "traefik.http.services.nocodb.loadbalancer.server.port=8080"
      - "traefik.http.routers.nocodb.middlewares=${AUTH_MIDDLEWARE}"
    depends_on:
      postgres:
        condition: service_healthy

  # Supabase Backend Stack (5 containers)
  supabase-studio:
    image: supabase/studio:latest
    container_name: ${TENANT_PREFIX}-supabase-studio
    environment:
      STUDIO_PG_META_URL: http://supabase-meta:8080
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      DEFAULT_ORGANIZATION_NAME: ${CLIENT_NAME}
      DEFAULT_PROJECT_NAME: ${CLIENT_NAME} Project
      SUPABASE_PUBLIC_URL: https://${SUPABASE_DOMAIN}
    networks:
      - default
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.supabase-studio.rule=Host(`${SUPABASE_DOMAIN}`)"
      - "traefik.http.routers.supabase-studio.tls.certresolver=letsencrypt"
      - "traefik.http.services.supabase-studio.loadbalancer.server.port=3000"
      - "traefik.http.routers.supabase-studio.middlewares=${AUTH_MIDDLEWARE}"
    healthcheck:
      disable: true
    depends_on:
      postgres:
        condition: service_healthy

  supabase-meta:
    image: supabase/postgres-meta:latest
    container_name: ${TENANT_PREFIX}-supabase-meta
    environment:
      PG_META_PORT: 8080
      PG_META_DB_HOST: postgres
      PG_META_DB_PORT: 5432
      PG_META_DB_NAME: supabase
      PG_META_DB_USER: ${POSTGRES_USER}
      PG_META_DB_PASSWORD: ${POSTGRES_PASSWORD}
    networks:
      - default
    depends_on:
      postgres:
        condition: service_healthy

  supabase-auth:
    image: supabase/gotrue:latest
    container_name: ${TENANT_PREFIX}-supabase-auth
    environment:
      GOTRUE_API_HOST: 0.0.0.0
      GOTRUE_API_PORT: 9999
      GOTRUE_DB_DRIVER: postgres
      GOTRUE_DB_DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/supabase
      GOTRUE_SITE_URL: https://${SUPABASE_DOMAIN}
      GOTRUE_JWT_SECRET: ${SUPABASE_JWT_SECRET}
      GOTRUE_JWT_EXP: 3600
      GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
    networks:
      - default
    depends_on:
      postgres:
        condition: service_healthy

  supabase-rest:
    image: postgrest/postgrest:latest
    container_name: ${TENANT_PREFIX}-supabase-rest
    environment:
      PGRST_DB_URI: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/supabase
      PGRST_DB_SCHEMAS: public,graphql_public
      PGRST_DB_ANON_ROLE: anon
      PGRST_JWT_SECRET: ${SUPABASE_JWT_SECRET}
      PGRST_DB_USE_LEGACY_GUCS: "false"
    networks:
      - default
    depends_on:
      postgres:
        condition: service_healthy

  supabase-realtime:
    image: supabase/realtime:latest
    container_name: ${TENANT_PREFIX}-supabase-realtime
    environment:
      PORT: 4000
      DB_HOST: postgres
      DB_PORT: 5432
      DB_USER: ${POSTGRES_USER}
      DB_PASSWORD: ${POSTGRES_PASSWORD}
      DB_NAME: supabase
      DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime'
      DB_ENC_KEY: supabaserealtime
      API_JWT_SECRET: ${SUPABASE_JWT_SECRET}
      FLY_ALLOC_ID: fly123
      FLY_APP_NAME: realtime
      SECRET_KEY_BASE: ${SUPABASE_JWT_SECRET}
      ERL_AFLAGS: -proto_dist inet_tcp
      ENABLE_TAILSCALE: "false"
      DNS_NODES: "''"
    networks:
      - default
    command: >
      sh -c "/app/bin/migrate && /app/bin/realtime eval 'Realtime.Release.seeds(Realtime.Repo)' && /app/bin/server"
    depends_on:
      postgres:
        condition: service_healthy

  supabase-kong:
    image: kong:3.2-alpine
    container_name: ${TENANT_PREFIX}-supabase-kong
    environment:
      KONG_DATABASE: "off"
      KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml
      KONG_DNS_ORDER: LAST,A,CNAME
      KONG_PLUGINS: request-size-limiting,cors,key-auth,rate-limiting
      KONG_NGINX_PROXY_PROXY_BUFFER_SIZE: 160k
      KONG_NGINX_PROXY_PROXY_BUFFERS: 64 160k
    volumes:
      - ./supabase/kong.yml:/var/lib/kong/kong.yml:ro
    networks:
      - default
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.kong.rule=Host(`api.${CLIENT_DOMAIN}`)"
      - "traefik.http.routers.kong.tls.certresolver=letsencrypt"
      - "traefik.http.services.kong.loadbalancer.server.port=8000"
      - "traefik.http.routers.kong.middlewares=${AUTH_MIDDLEWARE}"

  # Local AI Language Models
  ollama:
    image: ollama/ollama:latest
    container_name: ${TENANT_PREFIX}-ollama
    environment:
      OLLAMA_HOST: 0.0.0.0:11434
      OLLAMA_ORIGINS: "*"
    volumes:
      - ollama_data:/root/.ollama
    networks:
      - default
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.ollama.rule=Host(`ai.${CLIENT_DOMAIN}`)"
      - "traefik.http.routers.ollama.tls.certresolver=letsencrypt"
      - "traefik.http.services.ollama.loadbalancer.server.port=11434"
      - "traefik.http.routers.ollama.middlewares=${AUTH_MIDDLEWARE}"
    deploy:
      resources:
        limits:
          memory: 16G
        reservations:
          memory: 8G
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
      interval: 30s
      timeout: 10s
      retries: 3

  # Enterprise SSO Authentication (3 containers)
  authentik-redis:
    image: redis:alpine
    container_name: ${TENANT_PREFIX}-authentik-redis
    command: --save 60 1 --loglevel warning
    networks:
      - default
    volumes:
      - authentik_redis_data:/data
    healthcheck:
      test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
      interval: 30s
      timeout: 3s
      retries: 3

  authentik-server:
    image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}
    container_name: ${TENANT_PREFIX}-authentik-server
    command: server
    environment:
      AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
      AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
      AUTHENTIK_POSTGRESQL__HOST: postgres
      AUTHENTIK_POSTGRESQL__USER: ${POSTGRES_USER}
      AUTHENTIK_POSTGRESQL__NAME: authentik
      AUTHENTIK_POSTGRESQL__PASSWORD: ${POSTGRES_PASSWORD}
      AUTHENTIK_REDIS__HOST: authentik-redis
    volumes:
      - authentik_media:/media
      - authentik_templates:/templates
    networks:
      - default
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.authentik.rule=Host(`auth.${CLIENT_DOMAIN}`)"
      - "traefik.http.routers.authentik.tls.certresolver=letsencrypt"
      - "traefik.http.services.authentik.loadbalancer.server.port=9000"
    depends_on:
      postgres:
        condition: service_healthy
      authentik-redis:
        condition: service_healthy

  authentik-worker:
    image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG}
    container_name: ${TENANT_PREFIX}-authentik-worker
    command: worker
    environment:
      AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
      AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
      AUTHENTIK_POSTGRESQL__HOST: postgres
      AUTHENTIK_POSTGRESQL__USER: ${POSTGRES_USER}
      AUTHENTIK_POSTGRESQL__NAME: authentik
      AUTHENTIK_POSTGRESQL__PASSWORD: ${POSTGRES_PASSWORD}
      AUTHENTIK_REDIS__HOST: authentik-redis
    volumes:
      - authentik_media:/media
      - authentik_templates:/templates
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - default
    depends_on:
      postgres:
        condition: service_healthy
      authentik-redis:
        condition: service_healthy

  # Test Service for Health Monitoring
  whoami:
    image: traefik/whoami:latest
    container_name: ${TENANT_PREFIX}-whoami
    networks:
      - default
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`test.${CLIENT_DOMAIN}`)"
      - "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
      - "traefik.http.services.whoami.loadbalancer.server.port=80"

volumes:
  postgres_data:
  n8n_data:
  nocodb_data:
  ollama_data:
  authentik_redis_data:
  authentik_media:
  authentik_templates:

Template completo dell’ambiente

Crea .env.template con tutte le variabili specifiche del cliente:

# Client Configuration
CLIENT_NAME=CLIENT_NAME_PLACEHOLDER
CLIENT_DOMAIN=CLIENT_DOMAIN_PLACEHOLDER
TENANT_PREFIX=CLIENT_PREFIX_PLACEHOLDER
TENANT_NETWORK=CLIENT_NETWORK_PLACEHOLDER

# Service Domains (Subdomain-based routing)
N8N_DOMAIN=workflows.CLIENT_DOMAIN_PLACEHOLDER
NOCODB_DOMAIN=database.CLIENT_DOMAIN_PLACEHOLDER
SUPABASE_DOMAIN=backend.CLIENT_DOMAIN_PLACEHOLDER
AUTHENTIK_DOMAIN=auth.CLIENT_DOMAIN_PLACEHOLDER

# Infrastructure Ports
TRAEFIK_PORT=80
TRAEFIK_SECURE_PORT=443
TRAEFIK_DASHBOARD_PORT=8080
POSTGRES_PORT=5432

# Admin Configuration
ADMIN_EMAIL=admin@CLIENT_DOMAIN_PLACEHOLDER

# Database Configuration
POSTGRES_DB=main_db
POSTGRES_USER=postgres
POSTGRES_PASSWORD=SECURE_PASSWORD_PLACEHOLDER

# Service-Specific Passwords
NOCODB_ADMIN_PASSWORD=NOCODB_PASSWORD_PLACEHOLDER
SUPABASE_JWT_SECRET=SUPABASE_JWT_PLACEHOLDER

# Authentik SSO Configuration
AUTHENTIK_SECRET_KEY=AUTHENTIK_SECRET_PLACEHOLDER
AUTHENTIK_TAG=2024.8.3

# n8n Configuration
N8N_PROTOCOL=https
N8N_SECURE_COOKIE=true

# Cloudflare Integration
CLOUDFLARE_TOKEN=CLOUDFLARE_TOKEN_PLACEHOLDER
TUNNEL_ID=TUNNEL_ID_PLACEHOLDER

# Authentication Middleware (set to 'auth-global' for SSO, leave empty for no auth)
AUTH_MIDDLEWARE=

Script di inizializzazione del database

Configura l’inizializzazione completa dei database in init/01-create-multiple-databases.sql:

-- Create databases for all services
CREATE DATABASE n8n;
CREATE DATABASE nocodb;
CREATE DATABASE supabase;
CREATE DATABASE authentik;

-- Grant permissions
GRANT ALL PRIVILEGES ON DATABASE n8n TO postgres;
GRANT ALL PRIVILEGES ON DATABASE nocodb TO postgres;
GRANT ALL PRIVILEGES ON DATABASE supabase TO postgres;
GRANT ALL PRIVILEGES ON DATABASE authentik TO postgres;

-- Enable required extensions for Supabase
\c supabase;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";

-- Enable required extensions for Authentik
\c authentik;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";

\echo 'Multiple databases and extensions created successfully'

Configurazione del Kong Gateway di Supabase

Crea supabase/kong.yml per l’instradamento del gateway API:

_format_version: "3.0"

services:
  - name: auth-v1-open
    url: http://supabase-auth:9999/verify
    plugins:
      - name: cors
    routes:
      - name: auth-v1-open
        strip_path: true
        paths:
          - /auth/v1/verify
        methods:
          - POST
          - OPTIONS

  - name: auth-v1-open-callback
    url: http://supabase-auth:9999/callback
    plugins:
      - name: cors
    routes:
      - name: auth-v1-open-callback
        strip_path: true
        paths:
          - /auth/v1/callback
        methods:
          - GET
          - POST
          - OPTIONS

  - name: auth-v1
    _comment: "GoTrue: /auth/v1/* -> http://supabase-auth:9999/*"
    url: http://supabase-auth:9999/
    plugins:
      - name: cors
      - name: key-auth
        config:
          hide_credentials: false
    routes:
      - name: auth-v1-all
        strip_path: true
        paths:
          - /auth/v1/
        methods:
          - GET
          - POST
          - PUT
          - PATCH
          - DELETE
          - OPTIONS

  - name: rest-v1
    _comment: "PostgREST: /rest/v1/* -> http://supabase-rest:3000/*"
    url: http://supabase-rest:3000/
    plugins:
      - name: cors
      - name: key-auth
        config:
          hide_credentials: true
    routes:
      - name: rest-v1-all
        strip_path: true
        paths:
          - /rest/v1/
        methods:
          - GET
          - POST
          - PUT
          - PATCH
          - DELETE
          - OPTIONS

  - name: realtime-v1
    _comment: "Realtime: /realtime/v1/* -> ws://supabase-realtime:4000/socket/*"
    url: http://supabase-realtime:4000/socket/
    plugins:
      - name: cors
      - name: key-auth
        config:
          hide_credentials: false
    routes:
      - name: realtime-v1-all
        strip_path: true
        paths:
          - /realtime/v1/
        methods:
          - GET
          - POST
          - PUT
          - PATCH
          - DELETE
          - OPTIONS

consumers:
  - username: anon
    keyauth_credentials:
      - key: your-anon-key-here
  - username: service_role
    keyauth_credentials:
      - key: your-service-role-key-here

plugins:
  - name: cors
    config:
      origins:
        - "*"
      methods:
        - GET
        - POST
        - PUT
        - PATCH
        - DELETE
        - OPTIONS
      headers:
        - Accept
        - Accept-Version
        - Content-Length
        - Content-MD5
        - Content-Type
        - Date
        - X-Auth-Token
        - Authorization
        - X-Forwarded-For
        - X-Forwarded-Proto
        - X-Forwarded-Port
      exposed_headers:
        - X-Auth-Token
      credentials: true
      max_age: 3600

Script di distribuzione automatizzata

Usa questo script di distribuzione per creare nuovi ambienti cliente in pochi minuti. Ricava tutte le porte dell’host (HTTP, HTTPS e dashboard di Traefik, oltre a PostgreSQL) da un checksum del prefisso cliente, così più clienti possono condividere un host. Verifica che gli offset di due clienti non entrino in conflitto:

#!/bin/bash
# deploy-client.sh - Complete Multi-Tenant Deployment

CLIENT_DOMAIN=$1
CLIENT_NAME=$2
CLOUDFLARE_TOKEN=$3

if [ -z "$CLIENT_DOMAIN" ] || [ -z "$CLIENT_NAME" ] || [ -z "$CLOUDFLARE_TOKEN" ]; then
    echo "Usage: ./deploy-client.sh example.com 'Client Name' 'cloudflare-token'"
    echo ""
    echo "Example: ./deploy-client.sh client-a.com 'Client A Corporation' 'your-cloudflare-token'"
    exit 1
fi

CLIENT_PREFIX=$(echo $CLIENT_DOMAIN | sed 's/[.-]//g' | tr '[:upper:]' '[:lower:]')
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

echo "🚀 Deploying complete multi-tenant environment..."
echo "📋 Configuration:"
echo "   Domain: $CLIENT_DOMAIN"
echo "   Name: $CLIENT_NAME"
echo "   Prefix: $CLIENT_PREFIX"
echo "   Timestamp: $TIMESTAMP"
echo ""

# Create deployment directory
DEPLOY_DIR="../deployments/$CLIENT_DOMAIN"
mkdir -p "$DEPLOY_DIR"/{traefik,authentik,supabase,init,logs}

echo "📁 Created deployment directory structure"

# Copy template files
cp docker-compose.yml "$DEPLOY_DIR/"
cp -r {traefik,authentik,supabase,init}/ "$DEPLOY_DIR/" 2>/dev/null || true

echo "📋 Copied configuration templates"

# Generate secure passwords and secrets
POSTGRES_PASSWORD=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-25)
NOCODB_PASSWORD=$(openssl rand -base64 16 | tr -d "=+/" | cut -c1-16)
SUPABASE_JWT_SECRET=$(openssl rand -base64 64 | tr -d "=+/" | cut -c1-64)
AUTHENTIK_SECRET=$(openssl rand -hex 32)

echo "🔐 Generated secure credentials"

# Calculate unique ports to avoid conflicts
PORT_OFFSET=$(($(echo "$CLIENT_PREFIX" | cksum | cut -f1 -d' ') % 1000))
TRAEFIK_DASHBOARD_PORT=$((8080 + PORT_OFFSET))
POSTGRES_PORT=$((5432 + PORT_OFFSET))
TRAEFIK_PORT=$((10000 + PORT_OFFSET))
TRAEFIK_SECURE_PORT=$((11000 + PORT_OFFSET))

# Create comprehensive environment file
cat .env.template | \
    sed "s/CLIENT_NAME_PLACEHOLDER/$CLIENT_NAME/g" | \
    sed "s/CLIENT_DOMAIN_PLACEHOLDER/$CLIENT_DOMAIN/g" | \
    sed "s/CLIENT_PREFIX_PLACEHOLDER/$CLIENT_PREFIX/g" | \
    sed "s/CLIENT_NETWORK_PLACEHOLDER/${CLIENT_PREFIX}-network/g" | \
    sed "s/SECURE_PASSWORD_PLACEHOLDER/$POSTGRES_PASSWORD/g" | \
    sed "s/NOCODB_PASSWORD_PLACEHOLDER/$NOCODB_PASSWORD/g" | \
    sed "s/SUPABASE_JWT_PLACEHOLDER/$SUPABASE_JWT_SECRET/g" | \
    sed "s/AUTHENTIK_SECRET_PLACEHOLDER/$AUTHENTIK_SECRET/g" | \
    sed "s/CLOUDFLARE_TOKEN_PLACEHOLDER/$CLOUDFLARE_TOKEN/g" | \
    sed "s/^TRAEFIK_PORT=80$/TRAEFIK_PORT=$TRAEFIK_PORT/" | \
    sed "s/^TRAEFIK_SECURE_PORT=443$/TRAEFIK_SECURE_PORT=$TRAEFIK_SECURE_PORT/" | \
    sed "s/8080/$TRAEFIK_DASHBOARD_PORT/g" | \
    sed "s/5432/$POSTGRES_PORT/g" \
    > "$DEPLOY_DIR/.env"

echo "⚙️  Generated environment configuration"

# Create ACE file for Traefik SSL
touch "$DEPLOY_DIR/traefik/acme.json"
chmod 600 "$DEPLOY_DIR/traefik/acme.json"

# Initialize deployment
cd "$DEPLOY_DIR"

echo "🐋 Starting Docker containers..."
echo "   This may take several minutes for first-time image downloads"

# Start core infrastructure first
docker-compose up -d cloudflare-tunnel traefik postgres

echo "⏳ Waiting for database to be ready..."
sleep 30

# Start all remaining services
docker-compose up -d

echo ""
echo "✅ Multi-tenant environment deployed successfully!"
echo ""
echo "🌐 Access URLs:"
echo "   🔧 Traefik Dashboard: http://localhost:$TRAEFIK_DASHBOARD_PORT"
echo "   🔀 Workflows (n8n): https://workflows.$CLIENT_DOMAIN"
echo "   🗄️  Database (NocoDB): https://database.$CLIENT_DOMAIN"
echo "   🔧 Backend (Supabase): https://backend.$CLIENT_DOMAIN"
echo "   🤖 AI (Ollama): https://ai.$CLIENT_DOMAIN"
echo "   🔐 Authentication: https://auth.$CLIENT_DOMAIN"
echo "   🔌 API Gateway: https://api.$CLIENT_DOMAIN"
echo "   🧪 Test Service: https://test.$CLIENT_DOMAIN"
echo ""
echo "📊 Container Status:"
docker-compose ps
echo ""
echo "🔑 Generated Credentials (save these securely):"
echo "   📍 Client: $CLIENT_NAME"
echo "   🔐 PostgreSQL Password: $POSTGRES_PASSWORD"
echo "   🔐 NocoDB Admin Password: $NOCODB_PASSWORD"
echo "   🔐 Supabase JWT Secret: [hidden - check .env file]"
echo ""
echo "📝 Next Steps:"
echo "   1. Configure Cloudflare DNS: *.${CLIENT_DOMAIN} → tunnel"
echo "   2. Wait 2-3 minutes for all services to initialize"
echo "   3. Access services via the URLs above"
echo "   4. Configure SSO via auth.$CLIENT_DOMAIN if needed"
echo ""
echo "📚 Documentation: Visit tva.sg for setup guides and troubleshooting"
echo "💬 Support: Contact us via tva.sg/contact for assistance"

Utilizzare lo stack multi-tenant

Creare gli ambienti per nuovi clienti

Con il nostro script completo, creare un nuovo ambiente cliente diventa semplice:

# Deploy Client A with full enterprise stack
./deploy-client.sh client-a.com "Client A Corporation" "your-cloudflare-token"

# Deploy Client B with different domain
./deploy-client.sh client-b.org "Client B Industries" "your-cloudflare-token"

# Deploy Startup C
./deploy-client.sh startup-c.io "Startup C" "your-cloudflare-token"

Ogni distribuzione crea:

  • Una rete Docker dedicata con 16 container
  • Volumi di dati separati per l’archiviazione persistente
  • Container di servizi dedicati con monitoraggio dello stato
  • Una configurazione Cloudflare Tunnel individuale
  • Instradamento dei domini personalizzati con certificati SSL
  • Un’infrastruttura SSO di livello aziendale pronta per l’attivazione

Gestire più ambienti

Monitora tutti gli ambienti cliente da un punto centrale:

# Check all running environments across clients
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "(client|startup)"

# View comprehensive logs for specific client
cd deployments/client-a.com
docker-compose logs -f --tail=50 n8n

# Health check all services for a client
docker-compose ps
docker-compose exec postgres pg_isready

# Restart specific services
docker-compose restart nocodb supabase-studio

# Update all services to latest images
docker-compose pull && docker-compose up -d

L’architettura dei container nel dettaglio

La nostra architettura completa a 16 container per cliente comprende:

Livello infrastrutturale (4 container):

  • cloudflare-tunnel: connettività esterna sicura
  • traefik: reverse proxy con SSL automatico e rilevamento dei servizi
  • postgres: database centrale con pooling delle connessioni
  • whoami: monitoraggio dello stato e verifica dell’instradamento

Livello applicativo (7 container):

  • n8n: automazione dei flussi di lavoro con backend PostgreSQL
  • nocodb: interfaccia no-code per il database
  • supabase-studio: dashboard per lo sviluppo del backend
  • supabase-meta: servizio di introspezione del database
  • supabase-auth: autenticazione e gestione degli utenti
  • supabase-rest: API REST generata automaticamente
  • supabase-realtime: sottoscrizioni e aggiornamenti in tempo reale

Livello IA e gateway (2 container):

  • ollama: IA locale con supporto all’accelerazione GPU
  • supabase-kong: gateway API con limitazione della frequenza delle richieste e CORS

Livello di sicurezza aziendale (3 container):

  • authentik-server: server di autenticazione SSO
  • authentik-worker: attività in background e notifiche
  • authentik-redis: gestione delle sessioni e cache

Adattare le risorse per cliente

Regola le risorse in base alle esigenze e alle modalità di utilizzo del cliente:

# High-performance client configuration
services:
  n8n:
    deploy:
      resources:
        limits:
          cpus: '4.0'
          memory: 8G
        reservations:
          cpus: '2.0'
          memory: 4G
  
  postgres:
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 4G
        reservations:
          cpus: '1.0'
          memory: 2G
    environment:
      - POSTGRES_MAX_CONNECTIONS=200
      - POSTGRES_SHARED_BUFFERS=1GB
      - POSTGRES_EFFECTIVE_CACHE_SIZE=3GB

  ollama:
    deploy:
      resources:
        limits:
          memory: 32G
        reservations:
          memory: 16G
        devices:
          - driver: nvidia
            count: 1
            capabilities: [gpu]

Vantaggi concreti per la tua attività

Isolamento completo dei clienti con funzionalità aziendali

Ogni cliente dispone di un ambiente completo con SSO di livello aziendale, funzionalità IA e un’infrastruttura backend completa. Dati, configurazioni, personalizzazioni e policy di sicurezza rimangono separati. Un problema a livello applicativo nell’ambiente di un cliente resta confinato a quell’ambiente, ma poiché tutti i clienti condividono un host, problemi a livello di host, come l’esaurimento delle risorse di CPU, memoria o disco, possono comunque coinvolgere tutti. L’isolamento è simile a quello ottenuto con le nostre istanze n8n distribuite separatamente.

Attivazione rapida dei clienti con tutte le funzionalità

I nuovi clienti possono essere operativi con uno stack completo di sviluppo e automazione in meno di 10 minuti. Lo script automatizza la configurazione, la generazione delle credenziali e l’inizializzazione dei servizi; i record DNS di Cloudflare e l’SSO opzionale richiedono ancora brevi passaggi manuali, come indicano le istruzioni finali dello script.

Costi aziendali prevedibili

Dopo la configurazione iniziale, non ci sono costi di hosting per singolo cliente oltre a quelli dell’infrastruttura di base. A differenza delle soluzioni SaaS che addebitano costi per utente, flusso di lavoro o chiamata API, paghi un importo fisso per il server e puoi gestire tanti ambienti cliente con tutte le funzionalità aziendali quanti ne consentono le sue risorse.

Coerenza professionale del marchio

Ogni cliente ha domini con il proprio marchio e sottodomini professionali (workflows.client.com, auth.client.com, ecc.) e può personalizzare completamente gli ambienti. Nessuna dicitura «powered by» a piè di pagina o interfaccia condivisa che indebolisca l’identità del marchio.

Integrazione n8n: automazione dei flussi di lavoro aziendali su larga scala

Qui le possibilità si ampliano davvero. Come abbiamo mostrato nella guida per gestire n8n sulla propria infrastruttura per automatizzare i flussi di lavoro, questa configurazione multi-tenant fornisce a ogni cliente un’istanza n8n completa, integrata con uno stack aziendale completo.

Ogni cliente può creare flussi di lavoro sofisticati che:

  • Si collegano ai propri database (NocoDB, Supabase PostgreSQL)
  • Usano i propri modelli IA (Ollama) per l’automazione intelligente
  • Si autenticano tramite SSO aziendale (Authentik)
  • Si integrano con i propri strumenti aziendali e le proprie API
  • Elaborano i propri dati nel proprio ambiente isolato

La combinazione crea una piattaforma efficace per fornire servizi ai clienti, che consente di:

  • Distribuire rapidamente funzionalità di automazione standardizzate
  • Personalizzare i flussi di lavoro per cliente senza influire sugli altri
  • Ampliare l’erogazione dei servizi senza aumenti lineari dei costi
  • Mantenere la piena sovranità dei dati per ogni cliente
  • Offrire un controllo degli accessi basato su SSO come fondamento per le attività di sicurezza e conformità

L’approccio si basa sugli stessi principi della nostra guida alla configurazione di Windmill con Docker, estendendoli a un’architettura multi-tenant completa.

Opzioni di configurazione avanzate

Implementare SSO aziendale con Authentik

Abilita il single sign-on su tutti i servizi cliente configurando l’autenticazione delegata di Authentik:

# Add to Traefik middleware configuration
middlewares:
  auth-global:
    forwardAuth:
      address: "http://authentik-server:9000/outpost.goauthentik.io/auth/traefik"
      trustForwardHeader: true
      authResponseHeaders:
        - X-authentik-username
        - X-authentik-groups
        - X-authentik-email
        - X-authentik-name
        - X-authentik-uid

Aggiorna poi le etichette dei servizi per utilizzare il middleware:

labels:
  - "traefik.http.routers.n8n.middlewares=auth-global"
  - "traefik.http.routers.nocodb.middlewares=auth-global"
  - "traefik.http.routers.supabase-studio.middlewares=auth-global"

Aggiungere un database vettoriale per l’IA avanzata

Amplia le funzionalità IA con il database vettoriale Qdrant:

qdrant:
  image: qdrant/qdrant:latest
  container_name: ${TENANT_PREFIX}-qdrant
  environment:
    QDRANT__SERVICE__HTTP_PORT: 6333
    QDRANT__SERVICE__GRPC_PORT: 6334
  volumes:
    - qdrant_data:/qdrant/storage
  networks:
    - default
  labels:
    - "traefik.enable=true"
    - "traefik.http.routers.qdrant.rule=Host(`vector.${CLIENT_DOMAIN}`)"
    - "traefik.http.routers.qdrant.tls.certresolver=letsencrypt"
    - "traefik.http.services.qdrant.loadbalancer.server.port=6333"
    - "traefik.http.routers.qdrant.middlewares=${AUTH_MIDDLEWARE}"

Implementare un’architettura IA ibrida

Per prestazioni ottimali, considera un approccio ibrido che combina IA in container e IA eseguita nativamente:

# Install Ollama natively on host for GPU acceleration
brew install ollama

# Configure containers to use native Ollama
# In docker-compose.yml, services can access via host.docker.internal:11434
n8n:
  environment:
    - OLLAMA_HOST=host.docker.internal:11434

Su macOS questo è rilevante perché Docker Desktop non supporta le GPU nei container, mentre Ollama eseguito in modo nativo usa direttamente l’accelerazione GPU. Nella nostra configurazione, questo ha migliorato le prestazioni di circa 5-6 volte, mentre gli altri servizi sono rimasti nei container.

Stack di monitoraggio e osservabilità

Aggiungi un monitoraggio completo per ogni cliente:

prometheus:
  image: prom/prometheus:latest
  container_name: ${TENANT_PREFIX}-prometheus
  volumes:
    - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
    - prometheus_data:/prometheus
  networks:
    - default
  labels:
    - "traefik.enable=true"
    - "traefik.http.routers.prometheus.rule=Host(`metrics.${CLIENT_DOMAIN}`)"

grafana:
  image: grafana/grafana:latest
  container_name: ${TENANT_PREFIX}-grafana
  environment:
    GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD}
    GF_USERS_ALLOW_SIGN_UP: "false"
  volumes:
    - grafana_data:/var/lib/grafana
    - ./monitoring/dashboards:/var/lib/grafana/dashboards
  networks:
    - default
  labels:
    - "traefik.enable=true"
    - "traefik.http.routers.grafana.rule=Host(`monitoring.${CLIENT_DOMAIN}`)"
    - "traefik.http.routers.grafana.middlewares=${AUTH_MIDDLEWARE}"

Problemi comuni e soluzioni

Errori «Service Unavailable» o HTTP 502

Di solito significa che Traefik non riesce a raggiungere il container di destinazione. Verifica quanto segue:

# Verify container is running and healthy
docker-compose ps
docker-compose logs traefik --tail=20

# Check container is on correct network
docker network ls
docker network inspect ${CLIENT_PREFIX}-network

# Verify Traefik labels are correct
docker-compose config --services

Problemi di risoluzione DNS

La configurazione DNS wildcard è fondamentale per l’instradamento dei sottodomini:

# Correct Cloudflare DNS configuration
*.client-a.com CNAME tunnel-uuid.cfargotunnel.com
*.client-b.org CNAME tunnel-uuid2.cfargotunnel.com

# Test DNS resolution
nslookup workflows.client-a.com
dig workflows.client-a.com

Esaurimento delle risorse con più clienti

Monitora l’utilizzo delle risorse in tutti gli ambienti cliente:

# Check overall system resource usage
docker stats --no-stream
htop

# Check disk usage per client
du -sh deployments/*/
df -h

# Monitor container memory usage
docker-compose -f deployments/*/docker-compose.yml ps --format "table {{.Name}}\t{{.Size}}"

Esaurimento del pool di connessioni al database

Con molti clienti si possono raggiungere i limiti di connessione PostgreSQL. Configura ogni distribuzione:

-- Connect to client database
docker-compose exec postgres psql -U postgres

-- Increase connection limit
ALTER SYSTEM SET max_connections = 300;
ALTER SYSTEM SET shared_buffers = '256MB';
ALTER SYSTEM SET effective_cache_size = '1GB';

-- Reload configuration
SELECT pg_reload_conf();

Problemi di configurazione SSO di Authentik

Problemi comuni di configurazione SSO e relative soluzioni:

# Check Authentik containers are running
docker-compose ps | grep authentik

# Verify database initialization
docker-compose exec postgres psql -U postgres -d authentik -c "\dt"

# Check Authentik logs for startup issues
docker-compose logs authentik-server --tail=50

# Reset Authentik admin password if needed
docker-compose exec authentik-server ak create_admin_group
docker-compose exec authentik-server ak bootstrap_tasks

Problemi di connessione di Cloudflare Tunnel

Diagnostica i problemi di connettività del tunnel:

# Check tunnel status
docker-compose logs cloudflare-tunnel --tail=20

# Verify tunnel configuration in Cloudflare dashboard
# Ensure wildcard routing: *.client-domain.com

# Test tunnel connectivity
curl -I https://test.client-domain.com

Considerazioni sull’infrastruttura

Dimensionare l’infrastruttura per più clienti

Per una configurazione tipica che gestisce 10-15 clienti contemporaneamente con stack completi a 16 container:

Requisiti minimi:

  • CPU: 16-24 core (2 core per ambiente cliente attivo)
  • RAM: 64-128GB (4-8GB per cliente in base all’utilizzo dell’IA)
  • Archiviazione: SSD NVMe da 2TB+ (database, modelli IA e log crescono nel tempo)
  • Rete: connessione Gigabit per un accesso reattivo dei clienti

Configurazione consigliata per la produzione:

  • Server: Hetzner CCX62 o equivalente (48 vCPU, 192GB di RAM)
  • Archiviazione: NVMe da 4TB con sistema di backup automatico
  • Rete: più connessioni ridondanti
  • Monitoraggio: stack completo di osservabilità con avvisi

Strategia di backup per ambienti con più clienti

Implementa backup automatici per ogni cliente:

#!/bin/bash
# backup-all-clients.sh - Comprehensive backup solution
BACKUP_DIR="/opt/backups"
BACKUP_DATE=$(date +%Y%m%d_%H%M%S)
STACK_DIR=$(pwd)  # run from the development-stack directory
mkdir -p "$BACKUP_DIR"

for client_dir in "$STACK_DIR"/deployments/*/; do
    if [ -d "$client_dir" ]; then
        CLIENT_DOMAIN=$(basename "$client_dir")
        echo "📦 Backing up client: $CLIENT_DOMAIN"
        
        cd "$client_dir"
        
        # Backup databases with compression
        docker-compose exec -T postgres pg_dumpall -U postgres | gzip > "${BACKUP_DIR}/${CLIENT_DOMAIN}_db_${BACKUP_DATE}.sql.gz"
        
        # Backup persistent volumes
        docker run --rm \
            -v "${BACKUP_DIR}":/backup \
            -v "${CLIENT_DOMAIN//.}_n8n_data":/data/n8n:ro \
            -v "${CLIENT_DOMAIN//.}_nocodb_data":/data/nocodb:ro \
            -v "${CLIENT_DOMAIN//.}_ollama_data":/data/ollama:ro \
            alpine tar czf "/backup/${CLIENT_DOMAIN}_volumes_${BACKUP_DATE}.tar.gz" -C /data .
        
        # Backup configuration files
        tar czf "${BACKUP_DIR}/${CLIENT_DOMAIN}_config_${BACKUP_DATE}.tar.gz" \
            docker-compose.yml .env traefik/ supabase/ authentik/
        
        echo "✅ Backup completed for $CLIENT_DOMAIN"
    fi
done

# Cleanup old backups (keep 30 days)
find "$BACKUP_DIR" -name "*.gz" -mtime +30 -delete

# Optional: Upload to cloud storage
# rclone sync "$BACKUP_DIR" s3:backup-bucket/multi-tenant/

Rafforzamento della sicurezza per la produzione

Implementa un insieme completo di buone pratiche di sicurezza:

# Enhanced Traefik security configuration
traefik:
  command:
    - "--api.dashboard=true"
    - "--api.debug=false"
    - "--log.level=WARN"
    - "--accesslog=true"
    - "--entrypoints.web.address=:80"
    - "--entrypoints.websecure.address=:443"
    - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
    - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
    - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
    - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
    - "--providers.docker.exposedbydefault=false"
  labels:
    # Security headers middleware
    - "traefik.http.middlewares.security.headers.customRequestHeaders.X-Forwarded-Proto=https"
    - "traefik.http.middlewares.security.headers.customResponseHeaders.X-Frame-Options=DENY"
    - "traefik.http.middlewares.security.headers.customResponseHeaders.X-Content-Type-Options=nosniff"
    - "traefik.http.middlewares.security.headers.customResponseHeaders.Strict-Transport-Security=max-age=31536000"
    - "traefik.http.middlewares.security.headers.customResponseHeaders.Content-Security-Policy=default-src 'self'"
    
    # Rate limiting middleware
    - "traefik.http.middlewares.ratelimit.ratelimit.burst=100"
    - "traefik.http.middlewares.ratelimit.ratelimit.average=50"

Applica i middleware di sicurezza a tutti i servizi cliente:

labels:
  - "traefik.http.routers.n8n.middlewares=security,ratelimit,${AUTH_MIDDLEWARE}"

Analisi dei costi: i numeri che contano

Costi SaaS tradizionali (10 clienti aziendali con tutte le funzionalità)

Costi mensili per cliente:

  • n8n Pro: $50/mese per cliente = $500/mese
  • Supabase Pro: $25/mese per cliente = $250/mese
  • Piattaforma no-code (Airtable): $20/mese per cliente = $200/mese
  • SSO aziendale (Auth0): $23/mese per cliente = $230/mese
  • Costi API IA (OpenAI): $50/mese per cliente = $500/mese
  • Totale: $1.680/mese = $20.160/anno

Costi dello stack aziendale multi-tenant gestito sulla propria infrastruttura

Costi annuali dell’infrastruttura:

  • Server cloud con vCPU dedicate (Hetzner CCX62): $350/mese = $4.200/anno
  • Costi dei domini (10 clienti): $120/anno
  • Cloudflare Pro (opzionale): $240/anno
  • Totale: $4.560/anno

Risparmio annuale: $15.600 (riduzione dei costi del 77%)

Queste cifre riflettono i prezzi al momento della pubblicazione della guida, nell’agosto 2025. A ottobre 2026, il piano B2C Essentials di Auth0 parte da $35/mese e Hetzner non include più il CCX62 nel proprio listino; il successore CCX63, con 48 vCPU e 192 GB, è indicato a €853,99/mese IVA esclusa, incluso un indirizzo IPv4 primario. Ricalcola entrambe le opzioni con i prezzi attuali prima di decidere.

Inoltre ottieni:

  • Piena sovranità e riservatezza dei dati
  • Personalizzazione illimitata e possibilità di usare il proprio marchio
  • Nessuna dipendenza dal fornitore né limiti di frequenza dei piani SaaS
  • Controlli di sicurezza e conformità che gestisci direttamente
  • Possibilità di offrire servizi come rivenditore
  • Pieno controllo su aggiornamenti e funzionalità

Il vantaggio è ancora più significativo considerando che la configurazione offre funzionalità aziendali normalmente molto più costose negli abbonamenti SaaS, come i benefici economici dimostrati nella nostra analisi del self-hosting di n8n.

Integrazione WordPress: semplificare i flussi di lavoro dei contenuti

Per agenzie e team che gestiscono siti WordPress insieme agli ambienti di sviluppo, questo stack multi-tenant si integra con i flussi di automazione WordPress. Come il nostro plugin tva Duplicate Pro semplifica la gestione dei contenuti in WordPress, questo ambiente in container può automatizzare flussi complessi tra i siti WordPress e l’infrastruttura di sviluppo.

Possibilità di integrazione WordPress:

  • Distribuzione dei contenuti sui canali: flussi n8n che inviano automaticamente i contenuti WordPress ai sistemi dei clienti
  • Distribuzioni automatizzate: le modifiche ai siti WordPress attivano distribuzioni negli ambienti cliente
  • Sincronizzazione dei dati: le modifiche ai database dei clienti (tramite NocoDB) aggiornano automaticamente i contenuti WordPress
  • Contenuti generati con l’IA: i modelli Ollama generano contenuti che vengono trasferiti ai siti WordPress
  • Reportistica per i clienti: report WordPress automatici generati dalle metriche degli ambienti di sviluppo

Si crea così un ecosistema completo nel quale gestione dei contenuti WordPress, flussi di sviluppo ed erogazione dei servizi ai clienti lavorano insieme senza interruzioni.

Il tempo di configurazione vale la pena?

Se gestisci ambienti di sviluppo per più clienti, stai costruendo un’attività SaaS o dirigi un’agenzia che fornisce soluzioni tecniche, sì. La configurazione iniziale richiede circa un giorno, ma ottieni:

Vantaggi immediati:

  • Attivazione automatizzata dei clienti in meno di 10 minuti con uno stack aziendale completo
  • Isolamento solido tra gli ambienti cliente con personalizzazione professionale del marchio
  • Risparmi considerevoli rispetto ai servizi gestiti (riduzione del 75%+)
  • Pieno controllo su dati, personalizzazioni e conformità
  • Architettura scalabile che cresce con la tua attività
  • Controllo degli accessi basato su SSO con Authentik

Valore a lungo termine:

  • Fidelizzazione dei clienti grazie a servizi migliori e una presentazione professionale
  • Crescita dei ricavi grazie alla capacità di servire più clienti in modo efficiente
  • Vantaggio competitivo grazie a funzionalità aziendali offerte a prezzi competitivi
  • Competenza tecnica che ti distingue sul mercato

Per le agenzie con più clienti o le startup SaaS che vogliono mantenere il controllo durante la crescita, questa configurazione offre capacità di livello aziendale senza costi altrettanto elevati. La combinazione di containerizzazione, distribuzione automatizzata, corretto instradamento dei domini e sicurezza aziendale crea una base per una crescita significativa che rimane interamente sotto il tuo controllo.

La configurazione acquista ancora più valore considerando le possibilità di integrazione con strumenti esistenti, come l’automazione WordPress e la stabilità comprovata delle piattaforme di automazione gestite sulla propria infrastruttura.

Quali sono i prossimi passi?

Stiamo lavorando a miglioramenti di questa architettura multi-tenant. I prossimi tutorial tratteranno:

Opzioni di distribuzione avanzate:

  • Guida alla migrazione Kubernetes per la massima scalabilità e la distribuzione in ambienti aziendali
  • Gestione automatizzata dei certificati SSL con flussi di lavoro Let’s Encrypt integrati
  • Monitoraggio avanzato e avvisi con Prometheus, Grafana e dashboard personalizzate
  • Automazione del ripristino in caso di disastro con strategie di backup multiregione

Miglioramenti dell’esperienza cliente:

  • Portale self-service per i clienti per gestire autonomamente ambienti e impostazioni
  • Template di personalizzazione con il proprio marchio per l’identità delle agenzie
  • Template avanzati di flussi di lavoro per processi aziendali comuni
  • Guide all’integrazione per strumenti aziendali e API diffusi

Funzionalità aziendali:

  • Rafforzamento avanzato della sicurezza con WAF e rilevamento delle intrusioni
  • Quadri di conformità per GDPR, SOC2 e altre normative
  • Strategie di distribuzione multiregione per clienti in tutto il mondo
  • Guide all’ottimizzazione delle prestazioni per ambienti ad alto traffico

Il futuro dell’erogazione dei servizi ai clienti consiste nel costruire sistemi che offrano sia controllo sia comodità, crescendo in modo efficiente e mantenendo standard professionali.

Assistenza professionale

Configurare un ambiente multi-tenant con 16 servizi in container richiede di coordinare molti componenti. Abbiamo fornito una documentazione completa, ma ogni azienda ha requisiti specifici e un’infrastruttura esistente da considerare.

Se stai implementando questa configurazione per la produzione o hai bisogno di personalizzazioni per le tue esigenze di erogazione dei servizi, il nostro team può aiutarti con:

  • Strategie di distribuzione personalizzate per la tua infrastruttura
  • Integrazione con sistemi e flussi di lavoro esistenti
  • Ottimizzazione delle prestazioni per il carico specifico dei tuoi clienti
  • Rafforzamento della sicurezza per i requisiti di conformità
  • Formazione del personale sulla gestione degli ambienti multi-tenant
  • Strategie di manutenzione e monitoraggio continui

Contattaci tramite tva.sg per discutere le tue esigenze di architettura multi-tenant e ricevere indicazioni professionali sull’implementazione.

Che tu stia ampliando un’agenzia esistente, lanciando una nuova piattaforma SaaS o costruendo capacità di erogazione dei servizi di livello aziendale, ti aiutiamo a raggiungere i tuoi obiettivi con soluzioni in container gestite sulla tua infrastruttura, che mantengono la tua indipendenza e offrono risultati professionali.

Approfondimenti correlati

Per approfondire