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)
- 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:
- Il cliente visita il proprio dominio personalizzato (per esempio
workflows.client-a.com) - Cloudflare Tunnel instrada la richiesta alla tua istanza Traefik locale
- Traefik legge il dominio, applica i middleware (autenticazione, SSL, limitazione della frequenza delle richieste) e inoltra la richiesta al container del cliente corretto
- Authentik gestisce l’autenticazione SSO su tutti i servizi, se configurato
- Il cliente accede al proprio ambiente isolato, con i suoi dati e le sue configurazioni
- 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 sicuratraefik: reverse proxy con SSL automatico e rilevamento dei servizipostgres: database centrale con pooling delle connessioniwhoami: monitoraggio dello stato e verifica dell’instradamento
Livello applicativo (7 container):
n8n: automazione dei flussi di lavoro con backend PostgreSQLnocodb: interfaccia no-code per il databasesupabase-studio: dashboard per lo sviluppo del backendsupabase-meta: servizio di introspezione del databasesupabase-auth: autenticazione e gestione degli utentisupabase-rest: API REST generata automaticamentesupabase-realtime: sottoscrizioni e aggiornamenti in tempo reale
Livello IA e gateway (2 container):
ollama: IA locale con supporto all’accelerazione GPUsupabase-kong: gateway API con limitazione della frequenza delle richieste e CORS
Livello di sicurezza aziendale (3 container):
authentik-server: server di autenticazione SSOauthentik-worker: attività in background e notificheauthentik-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.