tva
← Insights

Construire une stack de développement multi-tenant avec Docker : configuration complète pour des déploiements clients évolutifs

Comment créer un environnement de développement multi-tenant basé sur des modèles, avec 16 services conteneurisés, fonctionnant hors ligne tout en restant accessible en ligne grâce au routage par sous-domaines

La gestion des environnements de développement pour plusieurs clients implique souvent de choisir entre des configurations manuelles complexes ou des solutions cloud coûteuses. Les déploiements manuels sont chronophages et sujets aux erreurs. Les plateformes cloud sont pratiques mais créent une dépendance vis-à-vis du fournisseur et des coûts récurrents qui augmentent avec l'utilisation.

Aujourd'hui, nous allons parcourir la construction d'une stack de développement multi-tenant évolutive qui vous offre les deux : une isolation complète entre les environnements clients avec des capacités de déploiement automatisé, tout en conservant un contrôle total sur votre infrastructure. Cette approche s'inscrit dans notre philosophie de solutions auto-hébergées — similaire à ce que nous avons montré avec l'auto-hébergement de n8n pour l'automatisation des workflows et le déploiement de Windmill avec Docker pour un contrôle opérationnel complet.

Les outils que nous utilisons

Commençons par comprendre le rôle de chaque composant dans notre architecture complète à 16 conteneurs :

Docker : votre fondation de conteneurisation

Docker fournit l'isolation et la cohérence dont nous avons besoin pour les environnements multi-tenant. Chaque client dispose de ses propres conteneurs avec des configurations identiques, garantissant que ce qui fonctionne en développement fonctionnera en production. Pensez-y comme si vous aviez plusieurs serveurs complètement séparés fonctionnant sur le même matériel.

L'avantage principal ? Une isolation parfaite entre les clients. Les données, configurations et personnalisations d'un client n'interfèrent jamais avec celles d'un autre. C'est crucial lorsqu'on gère plusieurs clients professionnels ayant des exigences et des besoins de sécurité différents.

Traefik : proxy inverse intelligent et répartiteur de charge

Traefik agit comme un directeur de trafic intelligent, routant automatiquement les requêtes vers le bon environnement client en fonction des noms de domaine. Au lieu de configurer manuellement des règles Apache ou Nginx complexes, Traefik lit les labels de vos conteneurs Docker et configure le routage automatiquement.

Pensez à Traefik comme un réceptionniste intelligent qui sait exactement vers quel bureau (conteneur) chaque visiteur (requête) doit être dirigé, sans que vous ayez à donner des indications à chaque fois. Dans notre configuration, Traefik gère la terminaison SSL, la découverte automatique de services et fournit des tableaux de bord de surveillance détaillés.

Cloudflare Tunnels : accès externe sécurisé

Les Cloudflare Tunnels fournissent un accès sécurisé à votre stack de développement locale sans configurations complexes de pare-feu ou de VPN. Chaque domaine client dispose de son propre tunnel, assurant une séparation complète au niveau réseau tout en maintenant une sécurité de niveau entreprise.

L'avantage est que vos environnements de développement restent locaux et sécurisés, mais les clients peuvent accéder à leurs services spécifiques depuis n'importe où avec une authentification appropriée — similaire à la façon dont nous avons configuré l'accès externe sécurisé dans notre guide d'hébergement n8n.

La stack de services complète : tout ce dont vos clients ont besoin

Notre stack multi-tenant comprend sept catégories de services de base réparties sur 16 conteneurs par client :

Automatisation des workflows et logique métier :

  • n8n : plateforme complète d'automatisation des workflows pour l'automatisation des processus métier
  • Authentik : gestion de l'authentification unique (SSO) et des identités de niveau entreprise (3 conteneurs : serveur, worker, cache Redis)

Base de données et services backend :

  • PostgreSQL : backend de base de données robuste supportant tous les services avec pooling de connexions optimisé
  • Stack Supabase : backend-as-a-service complet avec 5 conteneurs spécialisés (Studio, Auth, API REST, Realtime, Kong Gateway)
  • NocoDB : interface de base de données no-code pour la gestion des données clients

IA et intelligence :

  • Ollama : modèles de langage IA locaux avec accélération GPU pour l'automatisation intelligente
  • Qdrant (optionnel) : base de données vectorielle pour les workflows IA avancés et la recherche par similarité

Infrastructure et surveillance :

  • Cloudflare Tunnel : connectivité externe sécurisée
  • Traefik : proxy inverse avec SSL automatique et tableau de bord de surveillance

Comment tout fonctionne ensemble

Voici le flux complet lorsqu'un client accède à son environnement :

  1. Le client navigue vers son domaine personnalisé (par ex., workflows.client-a.com)
  2. Le Cloudflare Tunnel route la requête vers votre instance Traefik locale
  3. Traefik lit le domaine, applique les middlewares (authentification, SSL, limitation de débit) et transmet au bon conteneur client
  4. Authentik gère l'authentification SSO sur tous les services si configuré
  5. Le client obtient son environnement complètement isolé avec ses données et configurations
  6. Tous les autres clients restent complètement non affectés et inaccessibles

Tout reste organisé et séparé, chaque client disposant de sa propre structure de sous-domaines comme auth.client-a.comdatabase.client-a.combackend.client-a.com, etc.

Mise en place : les étapes pratiques

Préparer les fondations

Tout d'abord, vous aurez besoin de Docker Desktop installé et d'une configuration de gestion de domaines. Nous recommandons la mise en place d'une structure DNS avec caractères génériques (wildcard) pour faciliter l'intégration des clients :

# 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

Créer le système de modèles

La magie opère grâce à une approche basée sur des modèles. Au lieu de configurer manuellement chaque client, nous créons des modèles qui peuvent être déployés instantanément avec des configurations spécifiques au client.

Créez la structure de répertoires complète :

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

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

Configuration complète du modèle multi-services

Créez un modèle complet de docker-compose.yml avec les 16 services :

version: '3.8'

networks:
  ${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:
      - ${TENANT_NETWORK}
    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" 
      - "${TRAEFIK_DASHBOARD_PORT}:8080"
    networks:
      - ${TENANT_NETWORK}
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik/acme.json:/acme.json
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.rule=Host(`traefik.${CLIENT_DOMAIN}`)"
      - "traefik.http.services.traefik.loadbalancer.server.port=8080"
    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:
      - ${TENANT_NETWORK}
    ports:
      - "${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:
      - ${TENANT_NETWORK}
    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:
      - ${TENANT_NETWORK}
    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:
      - ${TENANT_NETWORK}
    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:
      - ${TENANT_NETWORK}
    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:
      - ${TENANT_NETWORK}
    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:
      - ${TENANT_NETWORK}
    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:
      - ${TENANT_NETWORK}
    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:
      - ${TENANT_NETWORK}
    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:
      - ${TENANT_NETWORK}
    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:
      - ${TENANT_NETWORK}
    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:
      - ${TENANT_NETWORK}
    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:
      - ${TENANT_NETWORK}
    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:
      - ${TENANT_NETWORK}
    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:

Modèle d'environnement complet

Créez le fichier .env.template pour les variables spécifiques à chaque client :

# 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=

Scripts d'initialisation de la base de données

Créez l'initialisation complète de la base de données dans 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";
CREATE EXTENSION IF NOT EXISTS "pgjwt";

-- 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';

Configuration de la passerelle Kong pour Supabase

Créez le fichier supabase/kong.yml pour le routage de la passerelle 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 de déploiement automatisé

Le script de déploiement complet qui crée de nouveaux environnements clients en quelques minutes :

#!/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))

# 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/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"

Utiliser votre stack multi-tenant

Déployer de nouveaux clients

La création d'un nouvel environnement client devient triviale avec notre script complet :

# 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"

Chaque déploiement crée :

  • Un réseau Docker complètement isolé avec 16 conteneurs
  • Des volumes de données séparés pour le stockage persistant
  • Des conteneurs de services uniques avec surveillance de l'état de santé
  • Une configuration individuelle de tunnel Cloudflare
  • Un routage de domaine personnalisé avec certificats SSL
  • Une infrastructure SSO de niveau entreprise prête à l'activation

Gestion de plusieurs environnements

Surveillez tous les environnements clients depuis un emplacement central :

# 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

Analyse approfondie de l'architecture des conteneurs

Notre architecture complète de 16 conteneurs par client comprend :

Couche infrastructure (4 conteneurs) :

  • cloudflare-tunnel : connectivité externe sécurisée
  • traefik : proxy inverse avec SSL automatique et découverte de services
  • postgres : base de données centrale avec pooling de connexions
  • whoami : surveillance de l'état de santé et vérification du routage

Couche application (7 conteneurs) :

  • n8n : automatisation des workflows avec backend PostgreSQL
  • nocodb : interface de base de données no-code
  • supabase-studio : tableau de bord de développement backend
  • supabase-meta : service d'introspection de la base de données
  • supabase-auth : authentification et gestion des utilisateurs
  • supabase-rest : API REST auto-générée
  • supabase-realtime : abonnements et mises à jour en temps réel

Couche IA et passerelle (2 conteneurs) :

  • ollama : IA locale avec support d'accélération GPU
  • supabase-kong : passerelle API avec limitation de débit et CORS

Couche sécurité entreprise (3 conteneurs) :

  • authentik-server : serveur d'authentification SSO
  • authentik-worker : tâches en arrière-plan et notifications
  • authentik-redis : gestion des sessions et mise en cache

Dimensionnement des ressources par client

Ajustez les ressources en fonction des besoins et des modèles d'utilisation de chaque client :

# 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]

Des avantages concrets pour votre entreprise

Isolation complète des clients avec fonctionnalités entreprise

Chaque client dispose de son propre univers complet incluant un SSO de niveau entreprise, des capacités d'IA et une infrastructure backend complète. Les données, configurations, personnalisations et politiques de sécurité restent complètement contenues. Un problème chez un client n'affecte jamais les autres, similaire à l'isolation que nous obtenons avec nos déploiements n8n individuels.

Intégration rapide des clients avec ensemble complet de fonctionnalités

Les nouveaux clients peuvent être opérationnels avec une stack complète de développement et d'automatisation en moins de 10 minutes. Le script de déploiement gère automatiquement toute la configuration complexe, la mise en place du DNS, l'initialisation des services et la configuration de la sécurité — bien plus complet que les approches traditionnelles.

Des coûts prévisibles de niveau entreprise

Après la configuration initiale, il n'y a aucun coût d'hébergement par client au-delà de votre infrastructure de base. Contrairement aux solutions SaaS qui facturent par utilisateur, par workflow ou par appel API, vous payez une fois pour le matériel et exécutez un nombre illimité d'environnements clients avec toutes les fonctionnalités entreprise.

Cohérence professionnelle de la marque

Chaque client dispose de ses propres domaines personnalisés avec des sous-domaines professionnels (workflows.client.comauth.client.com, etc.) et peut personnaliser entièrement son environnement. Pas de mentions « powered by » ou d'interfaces partagées qui diluent l'identité de la marque.

L'intégration n8n : automatisation des workflows d'entreprise à grande échelle

C'est ici que les choses deviennent vraiment puissantes. Tout comme nous vous avons montré comment auto-héberger n8n pour l'automatisation des workflows, cette configuration multi-tenant donne à chaque client sa propre instance n8n complète intégrée à une stack entreprise complète.

Chaque client peut construire des workflows sophistiqués qui :

  • Se connectent à leurs propres bases de données (NocoDB, Supabase PostgreSQL)
  • Utilisent leurs propres modèles d'IA (Ollama) pour l'automatisation intelligente
  • S'authentifient via le SSO d'entreprise (Authentik)
  • S'intègrent avec leurs outils métier et API spécifiques
  • Traitent leurs données avec une isolation et une sécurité complètes

Cette combinaison crée une puissante plateforme de livraison client où vous pouvez :

  • Déployer rapidement des capacités d'automatisation standardisées
  • Personnaliser les workflows par client sans affecter les autres
  • Développer votre offre de services sans augmentation linéaire des coûts
  • Maintenir une souveraineté totale des données pour chaque client
  • Offrir une sécurité et une conformité de niveau entreprise

Cette approche s'appuie sur les mêmes principes que ceux utilisés dans notre guide de configuration Docker de Windmill, mais l'étend à une architecture multi-tenant complète.

Options de configuration avancées

Mise en place du SSO d'entreprise avec Authentik

Activez l'authentification unique sur tous les services client en configurant l'authentification par transfert 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

Puis mettez à jour les labels de vos services pour utiliser le middleware :

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

Ajout d'une base de données vectorielle pour l'IA avancée

Améliorez les capacités d'IA avec la base de données vectorielle 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:
    - ${TENANT_NETWORK}
  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}"

Mise en place d'une architecture IA hybride

Pour des performances optimales, envisagez une approche hybride combinant IA conteneurisée et native :

# 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

Cela offre une amélioration des performances de 5 à 6 fois grâce à l'accès direct au GPU, tout en maintenant l'isolation des conteneurs pour les autres services.

Stack de surveillance et d'observabilité

Ajoutez une surveillance complète par client :

prometheus:
  image: prom/prometheus:latest
  container_name: ${TENANT_PREFIX}-prometheus
  volumes:
    - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
    - prometheus_data:/prometheus
  networks:
    - ${TENANT_NETWORK}
  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:
    - ${TENANT_NETWORK}
  labels:
    - "traefik.enable=true"
    - "traefik.http.routers.grafana.rule=Host(`monitoring.${CLIENT_DOMAIN}`)"
    - "traefik.http.routers.grafana.middlewares=${AUTH_MIDDLEWARE}"

Problèmes courants et solutions

Erreurs « Service Unavailable » ou HTTP 502

Cela signifie généralement que Traefik ne peut pas atteindre le conteneur cible. Vérifiez que :

# 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

Problèmes de résolution DNS

La configuration DNS wildcard est cruciale pour le routage par sous-domaines :

# 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

Épuisement des ressources sur plusieurs clients

Surveillez l'utilisation des ressources sur tous les environnements clients :

# 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}}"

Épuisement du pool de connexions à la base de données

Les limites de connexions PostgreSQL peuvent être atteintes avec de nombreux clients. Configurez par déploiement :

-- 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();

Problèmes de configuration du SSO Authentik

Problèmes courants de configuration SSO et solutions :

# 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

Problèmes de connexion du tunnel Cloudflare

Déboguer les problèmes de connectivité du 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

Considérations d'infrastructure

Dimensionner votre infrastructure pour plusieurs clients

Pour une configuration typique gérant 10 à 15 clients simultanément avec des stacks complètes de 16 conteneurs :

Exigences minimales :

  • CPU : 16-24 cœurs (2 cœurs par environnement client actif)
  • RAM : 64-128 Go (4-8 Go par client selon l'utilisation de l'IA)
  • Stockage : SSD NVMe avec 2 To+ (les bases de données, modèles d'IA et journaux augmentent avec le temps)
  • Réseau : connexion Gigabit pour un accès client réactif

Recommandé pour la production :

  • Serveur : Hetzner CCX62 ou similaire (48 vCPU, 192 Go de RAM)
  • Stockage : 4 To NVMe avec système de sauvegarde automatisé
  • Réseau : connexions redondantes multiples
  • Surveillance : stack d'observabilité complète avec alertes

Stratégie de sauvegarde pour les environnements multi-clients

Mettez en place des sauvegardes automatisées par client :

#!/bin/bash
# backup-all-clients.sh - Comprehensive backup solution
BACKUP_DIR="/opt/backups"
BACKUP_DATE=$(date +%Y%m%d_%H%M%S)

for client_dir in 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 "${PWD}":/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/${BACKUP_DIR}/${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/

Renforcement de la sécurité pour la production

Mettez en œuvre les meilleures pratiques de sécurité complètes :

# 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"

Appliquez le middleware de sécurité à tous les services clients :

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

Analyse des coûts : les chiffres qui comptent

Coûts SaaS traditionnels (10 clients entreprise avec ensemble complet de fonctionnalités)

Coûts mensuels par client :

  • n8n Pro : 50 $/mois par client = 500 $/mois
  • Supabase Pro : 25 $/mois par client = 250 $/mois
  • Plateforme NoCode (Airtable) : 20 $/mois par client = 200 $/mois
  • SSO entreprise (Auth0) : 23 $/mois par client = 230 $/mois
  • Coûts API IA (OpenAI) : 50 $/mois par client = 500 $/mois
  • Total : 1 500 $/mois = 18 000 $/an

Coûts de la stack entreprise multi-tenant auto-hébergée

Coûts d'infrastructure annuels :

  • Serveur dédié (Hetzner CCX62) : 350 $/mois = 4 200 $/an
  • Coûts des domaines (10 clients) : 120 $/an
  • Cloudflare Pro (optionnel) : 240 $/an
  • Total : 4 560 $/an

Économies annuelles : 13 440 $ (réduction des coûts de 75 %)

De plus, vous bénéficiez de :

  • Souveraineté et confidentialité totales des données
  • Personnalisation et marque blanche illimitées
  • Aucune dépendance vis-à-vis d'un fournisseur ni limitation de débit API
  • Sécurité et conformité de niveau entreprise
  • Possibilité de proposer des services de revente
  • Contrôle total sur les mises à jour et les fonctionnalités