Comment nous avons construit un site IA frugal et souverain : modèles bi-niveau, secrets Infisical, Docker Hub et Hugging Face pour Wagmi, garde-fous loi IA, et une stack sous 100 $/mois.
Ce site est la démo. Pas une vitrine à côté du produit : le site que vous lisez est la preuve — architecture, sécurité, économie de l’inférence, discipline réglementaire — livrés dans un seul artefact. Il a été codé à la main avec Cursor [1], sans low-code ni constructeur de pages.
Le récit que nous voulions tenir est volontaire : montrer comment construire une application économique, frugale et souveraine — code possédé, modèles possédés, moteurs d’inférence interchangeables — tout en faisant monter en puissance le modèle seulement quand cela en vaut la peine, en équilibrant en permanence coût et expérience utilisateur. Le visiteur anonyme reçoit un petit modèle CPU, rapide et peu coûteux ; l’utilisateur authentifié débloque un tier GPU plus lourd lorsque la capacité supplémentaire justifie la facture. La production reste prévisible ; le staging prouve le scale-to-zero sans parier le produit sur le runtime d’un seul fournisseur.
Ce qui suit est une vue CTO : principes, rôles de la stack, garde-fous, enveloppe mensuelle, feuille de route — formation continue, boucle d’évaluation AI Act, retour utilisateur lorsque le modèle dérape, puis pouvoirs élargis pour le grand modèle (e-mail, calendrier, documents, agents internes), toujours sous responsabilité croissante.
Frugal par défaut. Marketing et blog en export statique sur Cloudflare Pages (edge, coût marginal proche de zéro). Le runtime Node sur Koyeb porte les APIs et le chat ; le petit Wagmi (Qwen 2.5 1,5B, Q4) tourne en CPU dans le même conteneur pour le trafic anonyme — pas de GPU facturé tant que personne ne s’authentifie. L’inférence GPU scale à zéro à l’idle.
Souverain et portable. Un codebase TypeScript, des images Docker que nous construisons et épinglons, des modèles que nous affinons et bakons — pas un widget chat SaaS opaque. Nous pointons LLM_API_URL vers llama.cpp co-localisé ou distant (API compatible OpenAI) sans réécrire l’application.
Agnostique à l’inférence. Le Vercel AI SDK et les variables validées par Zod (LLM_RUNTIME, LLM_RUNTIME_CPU, LLM_RUNTIME_GPU, alias de modèles) séparent la logique produit du runtime. Le staging valide l’image serveur complète ; la production sert les assets statiques au bord et proxifie les APIs vers Koyeb.
Les droits suivent la capacité. Le petit modèle répond à partir de faits publics (RAG sur wagmi-skills.md, ai.txt, contenu blog). Le grand modèle gagnera des outils plus larges — et une surveillance plus stricte — à mesure que nous ajouterons e-mail, calendrier, documents et agents internes.
| Couche | Outil | Rôle |
|---|---|---|
| Secrets | Infisical (UE) | Source unique pour dev, staging, prod — URLs base, clés Supabase, endpoints LLM. Dev local via infisical run ; Koyeb et GitHub Actions alignés sur les mêmes espaces. Aucun secret dans git. |
| Données | PostgreSQL + Drizzle ORM | Schéma typé, migrations drizzle/, écritures transactionnelles. Commodité sans chaînes magiques. |
| Auth | Supabase | OTP e-mail (6 chiffres), cookies de session compatibles SSR. Supabase là où il excelle ; le reste dans notre app. |
| Contenu | Content Collections + Zod | Blog content/blog/ ; Markdown au build, bilingue EN/FR. |
| Images | Docker Hub (jeanbapt/…) | Registre de l’image web unifiée (Next.js + llama-server CPU + GGUF baké) et images GPU séparées (~10 Go). Pull Koyeb via secret de registre privé. |
| Usine à modèles | Hugging Face | Dépôts GGUF privés, datasets SFT (datasets/wagmi-sft/), entraînement et eval sur le Hub — là que nous cuisons Wagmi (wagmi-sft, wagmi-sft-14b). |
| Runtime staging | Koyeb | Validation full-stack : Standard xlarge, scale-to-zero, même image Docker qu’en secours production. |
| Front production | Cloudflare Pages | Export statique + Functions qui proxifient /api/* et l’auth vers Koyeb. |
| API / IA production | Koyeb | eco-xlarge always-on pour web + service GPU scale-to-zero pour le tier auth. |
| CI/CD | GitHub Actions | Lint, tests, build/push Docker, déploiements, release gate, porte AI Act (ci-dessous). |
| IDE | Cursor | Implémentation assistée ; l’architecture et la revue restent humaines. |
Visiteur anonyme Utilisateur authentifié
│ │
▼ ▼
Petit modèle (1,5B) Grand modèle (14B)
CPU, llama-server loopback Service GPU, scale-to-zero
Rapide, GPU marginal ~0 Wake + contexte plus profond
RAG BM25 Réponse CPU d’abord, puis GPU
wagmi-sft sur llama-server co-localisé (127.0.0.1:11434) — zéro saut réseau, latence maîtrisée après warm-up.wagmi-sft-14b sur image GPU dédiée ; LLM_GPU_WAKE_URL réveille le scale-to-zero depuis l’edge public lorsque le maillage privé ne suffit pas.LLM_MODEL_AUTH_FALLBACKS et chat CPU-first (CHAT_AUTH_CPU_FIRST) gardent le produit utilisable si le GPU est froid ou indisponible.Nous plafonnons maxTokens (300 / 1024) car les petits LLM omettent souvent le token EOS et tournent en boucle. Un benchmark comportemental (scripts/benchmark-rag-qwen15b.ts) bloque le déploiement d’un petit modèle qui hallucine ou ignore l’incitation à l’auth.
Pourquoi nous n’avons pas découpé le LLM CPU en micro-service « par session » sur Koyeb : le scale-to-zero plateforme suit l’idle trafic (minutes), pas la fin de session chat ; un découpage ajouterait un second cold start sans granularité session. L’image web monolithique (~1,5 Go) est le compromis pragmatique pour l’UX anonyme en staging scale-to-zero.
Avant les fonctionnalités, nous avons unifié la configuration :
.infisical.json ; CLI sur l’UE (https://eu.infisical.com).dev, staging, prod calqués sur le risque — pas des .env ad hoc par machine.scripts/infisical/placeholders-*.env) ; pnpm run infisical:push-placeholders pour amorcer les clés sans valeurs dans le dépôt.pnpm run dev enchaîne infisical run puis Next.js pour la parité locale/staging.GitHub Actions et Koyeb ne reçoivent que le nécessaire (tokens registry, API) ; les URLs LLM et base vivent sur le service Koyeb, synchronisées depuis Infisical prod — sans ré-injection complète à chaque deploy (ce qui effacerait la liste d’env).
PostgreSQL via Drizzle : schéma src/lib/db/schema.ts, migrations drizzle/, URL de pool Infisical, withTransaction pour les écritures.
Supabase : auth OTP, cookies de session. Persistance chat uniquement après consentement explicite ; pas de stockage des conversations anonymes.
APIs (App Router) : POST /api/chat en flux, health, statut LLM, classification contact, callback auth. Contact uniquement via le chat.
RAG local (local-rag.ts) : chevauchement de tokens type BM25 — pas de base vectorielle pour le petit tier. Pipeline SFT : scripts/generate-wagmi-sft-dataset.ts → datasets/wagmi-sft/ pour l’entraînement Hub.
Docker Hub héberge des tags immuables (staging-<sha>, prod-<sha>). Dockerfile multi-stage : Node 24 bookworm-slim, utilisateur non-root nextjs, sortie standalone Next, llama-server + GGUF wagmi-sft baké (docs/ci-gguf-registry.md). Les images GPU suivent leur propre cadence quand docker/llamacpp-wagmi-gpu/** change — pas à chaque retouche de copie.
Production hybride :
out/, CDN mondial, edge DDoS — idéal pour le marketing et le blog.Staging : push dev ou workflow manuel → tests → push Docker → Koyeb deal-ex-machina-staging/web, min-scale 0, grâce santé 360 s pour VM froide + warm-up modèle.
On pourrait objecter qu’un seul hébergeur suffirait. Pour autant, confondre simplicité opérationnelle et résilience architecturale serait une erreur. Cloudflare excelle là où l’edge excelle ; Koyeb porte le Node complet, les APIs et l’IA. Le staging valide le serveur en continu ; la production prouve l’export statique. Si l’un des deux devenait inadapté, le basculement relèverait de la reconfiguration, pas de la refonte.
Docker reste l’abstraction la plus portable : Dockerfile, registre, runtime interchangeable (Koyeb, ECS, Cloud Run, VPS). L’image qui tourne en staging est celle qui pourrait tourner en production serveur. Pour un cabinet qui vend des systèmes, démontrer cette rigueur sur son propre site relève du devoir, pas de l’élégance accessoire.
Hugging Face est l’atelier : entraînement, versioning, GGUF privés. La CI précharge les poids dans un contexte BuildKit ou une image compagnon pour éviter de retélécharger des gigaoctets à chaque deploy.
Aujourd’hui en production :
scripts/release/release-gate.mjs) : santé, rounds chat SSE, Lighthouse optionnel.scripts/release/ai-act-gate.mjs) : intégrée à deploy-production.yml — famille de modèle, runtime llamacpp, URLs de preuve red-team / tool-eval lors des changements de famille (ex. LFM2). Nous étendons cela en boucle d’évaluation intégrée (jeux d’eval automatisés, rétention d’artefacts, no-go plus strict).Feuille de route — toujours protéger l’utilisateur :
Sécurité (headers production) : CSP, HSTS, X-Frame-Options: DENY, nosniff, Referrer-Policy stricte, Permissions-Policy sans caméra/micro/géo. Allowlist CORS pour les mutations. Rate limits (ex. chat 20 req / 15 min). Zod sur toutes les entrées ; SQL uniquement via Drizzle ; filtre profanity sur le chat. Health : { "status": "ok" } seulement.
Confidentialité / RGPD : minimisation ; rétention chat consenti 7 jours ; cookies essentiels uniquement ; pages confidentialité EN/FR et case de consentement avant persistance.
Loi européenne sur l’IA : transparence (l’utilisateur sait qu’il parle à une IA) ; posture risque limité ; pas de pratiques interdites ; CGU et confidentialité limitent la reliance sur le contenu généré ; chemin d’incident via contact. La documentation technique et la supervision humaine s’étoffent avec l’usage d’outils.
Qualité : Biome, Vitest, Playwright, Lighthouse CI sur les PR ; lockfile gelé en CI ; Dependabot ; garde supply-chain pre-commit sur les workflows.
Nous visons moins de ~100 $/mois pour le site public + bursts staging + GPU occasionnel — pas une tarification « scale infini », mais une souveraineté maîtrisée :
| Poste | Coût typique | Notes |
|---|---|---|
| Koyeb web (prod) | ~43 $/mois | eco-xlarge (8 Go), min=1 — CPU + petit modèle résident |
| Koyeb GPU (prod) | 0–30 $/mois variable | Scale-to-zero ; GPU facturé à la seconde quand les auth le réveillent |
| Koyeb staging | 0–15 $/mois | Standard xlarge, min=0 ; paiement à l’usage |
| Cloudflare Pages | 0 $ | Tier gratuit statique + proxy Functions |
| Supabase + Postgres | 0–25 $/mois | Tiers free/faible pour OTP + DB modeste |
| Infisical | 0 $ | Tier équipe/dev pour les secrets |
| Docker Hub + GitHub | 0 $ | Quotas gratuits |
| Hugging Face | 0 $ | Stockage Hub + fetch CI ; jobs d’entraînement épisodiques |
Socle always-on : environ 45–55 $/mois (web prod + DB). Mois chargé avec réveils GPU et deploys staging reste souvent sous 100 $ car GPU et staging sont tarifés à la seconde, pas 24 h/24. Le choix de conception — petit modèle pour tous, grand modèle après auth — rend cette enveloppe tenable.
Node 24, TypeScript 5.9 strict, Next.js 16 App Router (standalone + export statique), React 19, Tailwind, Radix, next-intl EN/FR, Assistant UI + Vercel AI SDK. Critical CSS, bundle chat lazy, AVIF/WebP, JSON-LD, sitemaps, llms.txt / ai.txt. Cibles perf : Lighthouse performance ~96 %, CLS 0 — détail dans docs/PERFORMANCE_OPTIMIZATIONS_FINAL_STATUS.md.
Pas de WordPress/Wix/low-code pour le cœur du site. Pas de secrets dans git ou les bundles client. Pas d’any arbitraire. Pas de livraison sans lint, tests et type-check en CI. Pas de health endpoint bavard en production. Pas de cookies analytics tiers.
Cette stack montre qu’une surface IA niveau cabinet peut être :
Le dépôt et le site en ligne sont le livrable. La feuille de route ne remplace pas cette discipline — elle l’étend, pas à pas, en ne donnant au grand modèle plus de pouvoir que de responsabilité en face.
Synthèse : Infisical (secrets) · Supabase + Drizzle + Postgres (auth/données) · Docker Hub (images) · Hugging Face (modèles Wagmi) · Koyeb (API + inférence CPU/GPU) · Cloudflare Pages (statique production) · GitHub Actions (CI, release gate, porte AI Act) · LLM bi-niveau · cible ops < ~100 $/mois.