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 :
- Le client ouvre son domaine personnalisé (par exemple,
workflows.client-a.com) - Le tunnel Cloudflare transmet la requête vers votre instance Traefik locale
- Traefik identifie le domaine, applique les middlewares (authentification, SSL, limitation de débit) et transmet au bon conteneur client
- Authentik gère l’authentification SSO sur tous les services lorsque le SSO est configuré
- Le client obtient son propre environnement isolé avec ses données et configurations
- 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éetraefik: proxy inverse avec gestion automatique du SSL et découverte de servicespostgres: base de données centrale avec gestion d’un pool de connexionswhoami: supervision de l’état de santé et vérification du routage
Couche application (7 conteneurs) :
n8n: automatisation des flux de travail avec backend PostgreSQLnocodb: interface de base de données no-codesupabase-studio: tableau de bord de développement backendsupabase-meta: service d’introspection de la base de donnéessupabase-auth: authentification et gestion des utilisateurssupabase-rest: API REST auto-généréesupabase-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 GPUsupabase-kong: passerelle API avec limitation de débit et CORS
Couche de sécurité d’entreprise (3 conteneurs) :
authentik-server: serveur d’authentification SSOauthentik-worker: tâches en arrière-plan et notificationsauthentik-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.