tva
← Analyses

Créer une pile 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 voir comment construire une pile de développement multi-tenant évolutive qui vous offre les deux : une isolation solide 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 des solutions auto-hébergées, comme nous l’avons montré avec l’auto-hébergement de n8n pour l’automatisation des flux de travail 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, ce qui contribue à ce que ce qui fonctionne en développement fonctionne aussi en production. C’est comme disposer de plusieurs serveurs séparés qui fonctionnent sur le même matériel, même s’ils partagent toujours son processeur, sa mémoire, son disque et son noyau.

L’avantage principal ? Une isolation solide entre les clients. Les données, configurations et personnalisations de chaque client résident dans des conteneurs, réseaux et volumes séparés, de sorte qu’elles n’interfèrent pas 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 aiguilleur intelligent, dirigeant 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 supervision détaillés.

Cloudflare Tunnels : accès externe sécurisé

Les tunnels Cloudflare fournissent un accès sécurisé à votre pile de développement locale sans configurations complexes de pare-feu ou de VPN. Chaque domaine client dispose de son propre tunnel, ce qui maintient séparé le chemin d’accès externe de chaque client.

L’avantage est que vos environnements de développement restent locaux et sécurisés, mais les clients peuvent accéder à leurs services depuis n’importe où avec une authentification adaptée — comme pour l’accès externe sécurisé que nous avons configuré dans notre guide d’hébergement n8n.

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

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

Automatisation des flux de travail et logique métier :

  • n8n : plateforme complète d’automatisation des flux de travail 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 prenant en charge tous les services avec une gestion optimisée du pool de connexions
  • Pile Supabase : backend complet fourni comme service avec 6 conteneurs spécialisés (Studio, Meta, 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 d’IA exécutés localement avec accélération GPU pour l’automatisation intelligente
  • Qdrant (facultatif) : base de données vectorielle pour les flux de travail d’IA avancés et la recherche par similarité

Infrastructure et supervision :

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

Comment tout fonctionne ensemble

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

  1. Le client ouvre son domaine personnalisé (par exemple, workflows.client-a.com)
  2. Le tunnel Cloudflare transmet la requête vers votre instance Traefik locale
  3. Traefik identifie 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 lorsque le SSO est configuré
  5. Le client obtient son propre environnement isolé avec ses données et configurations
  6. Les environnements des autres clients ne sont pas accessibles par les routes de ce client

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

Mise en place : les étapes pratiques

Préparer les fondations

Tout d’abord, vous devez disposer de Docker Desktop et d’une configuration de gestion des 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

L’approche repose 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. Il s’agit d’un modèle de développement : le tableau de bord de Traefik fonctionne sans authentification, le modèle le publie donc, ainsi que PostgreSQL, uniquement sur 127.0.0.1, où les autres machines ne peuvent pas les atteindre (voir la section sur le renforcement de la sécurité en production ci-dessous) :

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:

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 un script complet d’initialisation des bases 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";

-- 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é

Utilisez ce script de déploiement pour créer de nouveaux environnements clients en quelques minutes. Il calcule tous les ports de l’hôte (HTTP, HTTPS et tableau de bord de Traefik, ainsi que PostgreSQL) à partir d’une somme de contrôle du préfixe client, afin que plusieurs clients puissent partager un hôte. Vérifiez que les décalages de deux clients n’entrent pas en collision :

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

Utiliser votre pile multi-tenant

Déployer des environnements pour de nouveaux clients

La création d’un nouvel environnement client devient simple 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 dédié avec 16 conteneurs
  • Des volumes de données séparés pour le stockage persistant
  • Des conteneurs de services uniques avec supervision 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 gestion automatique du SSL et découverte de services
  • postgres : base de données centrale avec gestion d’un pool de connexions
  • whoami : supervision de l’état de santé et vérification du routage

Couche application (7 conteneurs) :

  • n8n : automatisation des flux de travail 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 prise en charge de l’accélération GPU
  • supabase-kong : passerelle API avec limitation de débit et CORS

Couche de sécurité d’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 habitudes 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 des fonctionnalités d’entreprise

Chaque client dispose de son propre environnement complet comprenant 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 séparées. Un problème applicatif dans l’environnement d’un client reste limité à cet environnement. Toutefois, comme tous les clients partagent un hôte, les problèmes au niveau de l’hôte, tels que la saturation du processeur, de la mémoire ou du disque, peuvent encore affecter tout le monde. Cette isolation est similaire à celle de nos déploiements n8n individuels.

Intégration rapide des clients avec toutes les fonctionnalités

Les nouveaux clients peuvent disposer d’une pile complète de développement et d’automatisation opérationnelle en moins de 10 minutes. Le script de déploiement automatise la configuration, la génération des identifiants et l’initialisation des services ; les enregistrements DNS Cloudflare et le SSO facultatif restent de courtes étapes manuelles, comme l’indiquent les instructions finales du script.

Des coûts prévisibles pour les entreprises

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 flux de travail ou par appel API, vous payez un montant fixe pour le serveur et pouvez exécuter autant d’environnements clients dotés de toutes les fonctionnalités d’entreprise que ses ressources le permettent.

Cohérence professionnelle de la marque

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

L’intégration n8n : automatisation des flux de travail 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 flux de travail, cette configuration multi-tenant donne à chaque client sa propre instance n8n complète intégrée à une pile logicielle d’entreprise complète.

Chaque client peut construire des flux de travail 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 à leurs outils métier et API spécifiques
  • Traitent leurs données dans leur propre environnement isolé

Cette combinaison crée une puissante plateforme de prestation de services aux clients qui vous permet de :

  • Déployer rapidement des capacités d’automatisation standardisées
  • Personnaliser les flux de travail 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 un contrôle d’accès fondé sur le SSO comme base du travail de sécurité et de conformité

Cette approche s’appuie sur les mêmes principes que ceux utilisés dans notre guide de configuration Docker de Windmill, mais les é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 du client en configurant l’authentification déléguée à 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:
    - 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}"

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

Sur macOS, cela compte parce que Docker Desktop ne prend pas en charge les GPU dans les conteneurs, alors qu’Ollama exécuté nativement utilise directement l’accélération GPU. Dans notre configuration, cela a multiplié les performances par environ 5 à 6, tandis que les autres services restaient conteneurisés.

Pile de supervision et d’observabilité

Ajoutez une supervision 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:
    - 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}"

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 les points suivants :

# 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 avec caractères génériques 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-les pour chaque 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éboguez 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

Points à prendre en compte pour l’infrastructure

Dimensionner votre infrastructure pour plusieurs clients

Pour une configuration typique gérant 10 à 15 clients simultanément avec des piles 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 de 2 To ou plus (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 de stockage NVMe avec système de sauvegarde automatisé
  • Réseau : connexions redondantes multiples
  • Supervision : pile 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)
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/

Renforcement de la sécurité pour la production

Appliquez l’ensemble des bonnes pratiques de sécurité :

# 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 des clients :

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

Analyse des coûts : les chiffres qui comptent

Coûts SaaS traditionnels (10 entreprises clientes disposant de toutes les fonctionnalités)

Coûts mensuels par client :

  • n8n Pro : 50 $/mois par client = 500 $/mois
  • Supabase Pro : 25 $/mois par client = 250 $/mois
  • Plateforme no-code (Airtable) : 20 $/mois par client = 200 $/mois
  • SSO entreprise (Auth0) : 23 $/mois par client = 230 $/mois
  • Coûts des API d’IA (OpenAI) : 50 $/mois par client = 500 $/mois
  • Total : 1 680 $/mois = 20 160 $/an

Coûts de la pile logicielle d’entreprise multi-tenant auto-hébergée

Coûts d’infrastructure annuels :

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

Économies annuelles : 15 600 $ (réduction des coûts de 77 %)

Ces chiffres reflètent les prix en vigueur lors de la publication de ce guide en août 2025. En octobre 2026, le forfait B2C Essentials d’Auth0 débute à 35 $/mois et Hetzner ne propose plus le CCX62 ; son successeur, le CCX63, avec 48 vCPU et 192 GB, est affiché à 853,99 €/mois hors TVA, avec une adresse IPv4 principale incluse. Recalculez les deux options avec les prix actuels avant de décider.

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 limite de débit imposée par les forfaits SaaS
  • Des contrôles de sécurité et de conformité que vous gérez vous-même
  • Possibilité de proposer des services de revente
  • Contrôle total sur les mises à jour et les fonctionnalités

Cette approche est particulièrement intéressante, car notre configuration offre des fonctionnalités d’entreprise qui coûteraient généralement beaucoup plus cher avec des abonnements SaaS, comme l’illustrent les économies présentées dans notre analyse de l’auto-hébergement de n8n.

Intégration WordPress : simplifier les flux de travail liés au contenu

Pour les agences et les équipes qui gèrent des sites WordPress en parallèle de leurs environnements de développement, cette pile multi-tenant s’intègre très bien aux flux de travail d’automatisation de WordPress. Tout comme notre extension tva Duplicate Pro simplifie la gestion du contenu dans WordPress, cet environnement conteneurisé peut automatiser des flux de travail complexes entre les sites WordPress et votre infrastructure de développement.

Possibilités d’intégration avec WordPress :

  • Diffusion de contenu : des flux de travail n8n transmettent automatiquement le contenu WordPress aux systèmes des clients
  • Déploiements automatisés : les modifications des sites WordPress déclenchent des déploiements dans les environnements clients
  • Synchronisation des données : les modifications des bases de données clients (via NocoDB) mettent automatiquement à jour le contenu WordPress
  • Contenu généré par l’IA : les modèles Ollama génèrent du contenu qui est ensuite transmis aux sites WordPress
  • Rapports pour les clients : des rapports WordPress sont générés automatiquement à partir des indicateurs des environnements de développement

Vous obtenez ainsi un écosystème complet dans lequel la gestion du contenu WordPress, les flux de travail de développement et la prestation de services aux clients fonctionnent ensemble de façon fluide.

Le temps consacré à la configuration en vaut-il la peine ?

Si vous gérez des environnements de développement pour plusieurs clients, développez une activité SaaS ou dirigez une agence qui fournit des solutions techniques, absolument. La configuration initiale prend environ une journée, mais vous obtenez :

Avantages immédiats :

  • Intégration automatisée des clients en moins de 10 minutes avec une pile logicielle d’entreprise complète
  • Isolation solide entre les environnements clients, avec une présentation professionnelle à leur marque
  • Économies considérables par rapport aux services gérés (réduction de 75 % ou plus)
  • Contrôle total sur les données, les personnalisations et la conformité
  • Architecture évolutive qui accompagne la croissance de votre entreprise
  • Contrôle d’accès fondé sur le SSO avec Authentik

Valeur à long terme :

  • Fidélisation des clients grâce à une qualité de service supérieure et à une présentation professionnelle
  • Croissance du chiffre d’affaires grâce à la capacité de servir efficacement davantage de clients
  • Avantage concurrentiel grâce à des fonctionnalités d’entreprise proposées à des prix compétitifs
  • Expertise technique qui vous distingue sur le marché

Pour les agences qui servent plusieurs clients ou les startups SaaS qui souhaitent garder le contrôle tout en se développant, cette configuration offre des capacités de niveau entreprise sans les coûts correspondants. La combinaison de la conteneurisation, du déploiement automatisé, d’un routage adapté des domaines et d’une sécurité de niveau entreprise constitue une base solide pour développer votre activité, tout en restant entièrement sous votre contrôle.

Cette configuration devient encore plus intéressante lorsque l’on considère les possibilités d’intégration avec des outils existants, comme l’automatisation de WordPress, et la stabilité éprouvée des plateformes d’automatisation auto-hébergées.

Quelles sont les prochaines étapes ?

Nous travaillons activement à l’amélioration de cette architecture multi-tenant. Les prochains tutoriels aborderont :

Options de déploiement avancées :

  • Guide de migration vers Kubernetes pour maximiser la capacité de montée en charge et permettre les déploiements en entreprise
  • Gestion automatisée des certificats SSL avec des flux de travail Let’s Encrypt intégrés
  • Supervision et alertes avancées avec Prometheus, Grafana et des tableaux de bord personnalisés
  • Automatisation de la reprise après sinistre avec des stratégies de sauvegarde multirégionales

Améliorations de l’expérience client :

  • Portail client en libre-service pour gérer leurs propres environnements et paramètres
  • Personnalisation en marque blanche avec des modèles adaptés à l’identité de l’agence
  • Modèles de flux de travail avancés pour les processus métier courants
  • Guides d’intégration pour les outils métier et les API les plus utilisés

Fonctionnalités d’entreprise :

  • Renforcement avancé de la sécurité avec WAF et détection des intrusions
  • Cadres de conformité pour le RGPD, SOC2 et d’autres réglementations
  • Déploiement multirégional avec des stratégies adaptées aux clientèles internationales
  • Optimisation des performances avec des guides pour les environnements à fort trafic

L’avenir de la prestation de services aux clients ne consiste pas à choisir entre contrôle et simplicité d’utilisation : il s’agit de construire des systèmes qui offrent les deux, tout en permettant une montée en charge efficace et en maintenant des standards professionnels.

Bénéficiez d’un accompagnement professionnel

La mise en place d’un environnement multi-tenant avec 16 services conteneurisés implique de nombreux composants. Même si nous avons fourni une documentation complète, chaque entreprise a des exigences particulières et doit tenir compte de son infrastructure existante.

Si vous mettez en œuvre cette configuration en production ou si vous avez besoin de l’adapter à vos prestations de services aux clients, notre équipe peut vous accompagner sur les points suivants :

  • Stratégies de déploiement personnalisées adaptées à votre infrastructure
  • Intégration avec les systèmes et les flux de travail existants
  • Optimisation des performances en fonction de la charge générée par vos clients
  • Renforcement de la sécurité pour répondre aux exigences de conformité
  • Formation du personnel à la gestion des environnements multi-tenant
  • Stratégies de maintenance et de supervision continues

Contactez-nous sur tva.sg pour discuter de vos besoins en architecture multi-tenant et bénéficier de conseils professionnels pour sa mise en œuvre.

Que vous développiez une agence existante, lanciez une nouvelle plateforme SaaS ou mettiez en place des capacités de prestation de services de niveau entreprise, nous vous aidons à réussir avec des solutions auto-hébergées et conteneurisées qui préservent votre indépendance tout en offrant des résultats professionnels.

Analyses connexes

Pour aller plus loin