Bonnes pratiques de déploiement
Règles concrètes pour qu'un service applicatif tourne correctement sur Wakastart : .env.example, Dockerfile, migrations Prisma, runtime config, S3, OAuth.
Bonnes pratiques de déploiement
Ces règles sont issues des incidents rencontrés sur des services réels. Elles couvrent tout ce qui peut casser silencieusement un déploiement : config manquante, image Docker mal construite, migrations DB oubliées, runtime config Next.js mal alignée, intégration OAuth/Keycloak bancale.
1. Variables d'environnement
Chaque fichier .env.example constitue un contrat de configuration. Il doit être exhaustif, auto-documenté et maintenu à jour à chaque évolution du projet.
1.1 Règles obligatoires pour chaque variable
| Icône | Attribut | Exigence |
|---|---|---|
| 📝 | Exemple de valeur | Fournir une valeur d'exemple réelle et fonctionnelle. Pas de placeholder vide type xxx ou changeme. |
| 📌 | Description contextuelle | Expliquer en une phrase pourquoi cette variable est nécessaire au fonctionnement de l'application. |
| 🔁 | Doublon inter-services | Si la même valeur est attendue dans un autre microservice (ex: front ET back), documenter le doublon, expliquer pourquoi il existe, et préciser que toute modification doit être répercutée sur tous les services concernés. |
| ✅ / ⚠️ | Obligatoire ou optionnelle | Indiquer clairement si la variable est requise ou optionnelle, et décrire l'impact de son omission (comportement dégradé, erreur fatale, fonctionnalité désactivée…). |
| 🌐 | Type URL | Si la valeur est une URL, préciser si elle doit être publique (accessible depuis le navigateur) ou privée (accessible uniquement depuis le réseau interne / pod K8s). |
| 🧪 | Dev uniquement | Si la variable n'est utilisée qu'en dev, l'indiquer explicitement et expliquer pourquoi elle n'est pas nécessaire en prod. |
| 🔐 | Secret | Si la lecture de cette valeur par une personne non autorisée constitue un risque (fuite de données, accès dangereux — ex: DATABASE_URL, clés API, tokens), la marquer comme SECRET. |
| 🔑 | Impact chiffrement / autorisation | Si la variable impacte un chiffrement ou un système d'autorisation, documenter : (1) les services devant partager la même valeur, (2) les risques de modification (ex: perte définitive d'accès aux données chiffrées), (3) le format attendu et la commande de génération. |
1.2 Exemple de documentation
env# URL de l'API backend consommée par le frontend. # TYPE: URL publique (accessible depuis le navigateur de l'utilisateur). # OBLIGATOIRE — sans cette variable, toutes les requêtes API échouent. # DOUBLON: Cette variable correspond à BACKEND_URL côté backend. # La modifier ici implique de mettre à jour BACKEND_URL dans le service backend. NEXT_PUBLIC_API_URL=https://api.exemple.com # Clé de chiffrement des tokens de session. # TYPE: SECRET — ne jamais committer en clair ni modifier sans plan de migration. # ATTENTION: Modifier cette valeur invalide tous les tokens actifs. # FORMAT: 32 bytes hex — générer avec: openssl rand -hex 32 ENCRYPTION_KEY=<générer avec openssl rand -hex 32>
1.3 NEXT_PUBLIC_* vs variables runtime (Next.js)
Les variables NEXT_PUBLIC_* sont compilées au moment du build par Next.js et inlinées dans le bundle JS. Elles ne peuvent pas être modifiées à l'exécution du conteneur.
Si une URL doit pouvoir être configurée sans rebuild (ex: URL de Keycloak, URL d'API), elle doit passer par le mécanisme window.__RUNTIME_CONFIG__ injecté par docker-entrypoint.sh (voir section 4).
Documenter explicitement dans le .env.example de chaque service :
- quelles variables sont compilées au build (
NEXT_PUBLIC_*) - lesquelles sont injectées à l'exécution via
window.__RUNTIME_CONFIG__ - en cas de doublon entre les deux mécanismes, laquelle a la priorité et pourquoi
⚠️ Ne jamais committer un fichier
.envréel dans le dépôt Git. Seul le.env.example(sans valeurs secrètes réelles) est versionné.
2. Dockerfile et migrations Prisma
En l'absence d'outillage CI/CD avancé, toute application nécessitant une base de données doit garantir l'application automatique des migrations Prisma au démarrage du conteneur.
2.1 Entrypoint obligatoire pour les applications avec Prisma
Le Dockerfile de toute application backend utilisant Prisma doit déléguer le démarrage à un script entrypoint.sh. Ce script applique les migrations avant de démarrer le processus Node.js principal.
Structure type d'un entrypoint.sh :
sh#!/bin/sh set -e echo '[entrypoint] Running Prisma migrations...' npx prisma migrate deploy echo '[entrypoint] Starting application...' exec node dist/main.js
Configuration Dockerfile associée :
dockerfileFROM node:20-alpine WORKDIR /app COPY . . RUN npm install --omit=dev RUN npx prisma generate RUN npm run build COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"]
2.2 Pièges à éviter
- ✅
prismadoit figurer dans lesdependencies(et nondevDependencies) pour être disponible lors de l'exécution du conteneur de production. - 🚨 Ne jamais utiliser
pnpm prune --prodsans avoir vérifié queprisma,prisma generateet tous les binaires natifs nécessaires (bcrypt,multer…) sont dans lesdependenciesde production.
3. TypeScript et build NestJS / Node.js
Les erreurs de build TypeScript sont une cause fréquente de déploiements silencieusement cassés.
| Règle | Détail |
|---|---|
rootDir obligatoire dans tsconfig.build.json | Toujours déclarer rootDir: "./src" pour garantir que la sortie compilée est dans dist/main.js et non dist/src/main.js. Sans rootDir, le chemin de sortie dépend du module résolu et casse le démarrage du conteneur. |
Éviter les imports hors rootDir | Ne jamais importer depuis ../package.json ou tout chemin situé hors du rootDir défini. Ces imports provoquent des erreurs tsc et ne sont pas accessibles dans l'image Docker de production. |
Scripts postinstall avec pnpm v10+ | pnpm v10 bloque les scripts postinstall par défaut. Configurer explicitement pnpm.onlyBuiltDependencies dans package.json pour autoriser la génération des binaires Prisma, bcrypt et autres dépendances natives. |
| Dépendances natives en production | multer, bcrypt et tous les packages requis à l'exécution doivent être dans dependencies et non devDependencies, même s'ils sont utilisés via un framework comme NestJS. |
prisma.config.ts sans dotenv | Ne pas importer dotenv/config dans prisma.config.ts. dotenv n'est pas disponible dans l'image de production et les variables d'environnement sont injectées par le système d'orchestration (K8s). |
4. Runtime config du frontend (Next.js)
Les applications Next.js déployées en conteneur ont deux sources de configuration : les variables NEXT_PUBLIC_* (compilées au build) et les variables injectées à l'exécution via window.__RUNTIME_CONFIG__. Il faut impérativement aligner les noms de variables entre docker-entrypoint.sh et le déploiement Kubernetes.
Règles pour la runtime config
- L'
entrypoint.shdu frontend doit lire les variables telles qu'elles sont définies dans le déploiement K8s (NEXT_PUBLIC_API_URL,KEYCLOAK_PUBLIC_URL, etc.), et non des alias inventés localement (API_URL,KEYCLOAK_URL…). - Toute URL susceptible de changer selon l'environnement sans rebuild de l'image (ex: URL Keycloak) doit être injectée via
window.__RUNTIME_CONFIG__et non lue viaprocess.env.NEXT_PUBLIC_*. - Documenter dans le
.env.examplede chaque service quelles variables sont compilées au build (NEXT_PUBLIC_*) et lesquelles sont injectées à l'exécution. - En cas de doublon entre une variable
NEXT_PUBLIC_*et une variable runtime, documenter explicitement laquelle a la priorité et pourquoi.
⚠️ Un
entrypoint.shqui lit des variables inexistantes laissewindow.__RUNTIME_CONFIG__avec les valeurs par défaut hardcodées (localhost,preprod…), ce qui est indétectable sans logs.
5. Intégration S3 et Object Storage
Les adaptateurs S3 doivent gérer correctement les URLs d'endpoint pour éviter des erreurs de connectivité silencieuses.
Règles pour les endpoints S3
- Ne jamais préfixer mécaniquement
https://sur une variable d'endpoint : si la variable contient déjà le protocole, la double-préfixation produit une URL invalide (https://https://…) non détectable par les linters. - L'adaptateur doit détecter si l'endpoint contient déjà un protocole, parser l'URL pour en déduire le port (
443pourhttps,80pourhttp), et ne pas forcer le port9000par défaut (qui est le port MinIO local, non le port OVH Object Storage). - Documenter dans le
.env.examplesi la variableMINIO_ENDPOINT/S3_ENDPOINTdoit ou non inclure le protocole, le port et le chemin. - Préférer les variables classiques S3 du type :
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY, etc.
6. Authentification et flux OAuth / Keycloak
Voir aussi Authentication (Phase intégration) pour le flux OAuth général. Les règles ci-dessous sont des pièges concrets rencontrés sur des services en production.
6.1 redirect_uri
Le redirect_uri envoyé dans la requête d'autorisation doit être strictement identique à celui utilisé lors de l'échange de code côté serveur. Toute divergence provoque un rejet Keycloak.
Le serveur doit utiliser le redirect_uri fourni par le client lors du callback, et non une valeur hardcodée.
6.2 Clés sessionStorage
Toutes les clés sessionStorage utilisées dans le flux OAuth (realm, client_id, state, code_verifier…) doivent être déclarées dans un fichier de constantes partagé entre tous les modules impliqués. L'utilisation de chaînes littérales dispersées est interdite.
6.3 login_hint
Lorsque l'email de l'utilisateur est connu avant la redirection vers Keycloak, transmettre le paramètre standard OIDC login_hint pour pré-remplir le formulaire Keycloak et éviter une double saisie.
6.4 Multi-organisations (Discovery)
Si l'API Discovery peut retourner plusieurs comptes pour un même email (plusieurs realms Keycloak), le frontend doit afficher un écran de sélection d'organisation avant de rediriger. Ne jamais prendre aveuglément users[0] si la réponse peut contenir plusieurs entrées.
- Vérifier la présence d'utilisateurs avec
users?.length > 0et non avec un champfoundqui peut ne pas exister selon la version de l'API. - Vérifier systématiquement le nom exact du champ d'identifiant retourné par l'API (
customer_widvsnetwork_id,customer_idvscustomer_wid…) et le documenter dans le contrat d'API.
6.5 URL de Keycloak côté serveur
La variable KEYCLOAK_URL (URL interne ou publique pour les appels server-side) doit être explicitement définie dans la configuration K8s. Sans elle, les appels tombent sur localhost:8080 et échouent silencieusement.
Documenter dans le .env.example la différence entre KEYCLOAK_URL (server-side) et NEXT_PUBLIC_KEYCLOAK_URL / KEYCLOAK_PUBLIC_URL (client-side / runtime).
7. Migrations Prisma (au-delà du runtime)
Les migrations de base de données doivent être gérées de manière déterministe et reproductible entre les environnements.
Règles pour les migrations Prisma
- Le répertoire
prisma/migrations/doit contenir une migration initiale générée parprisma migrate devqui crée l'intégralité du schéma. Les scripts SQL en vrac non gérés par Prisma ne sont pas acceptables. - Les migrations spécifiques (ex: index FTS, ajout de colonnes) doivent être encapsulées dans des migrations Prisma dédiées, et non sous forme de fichiers SQL isolés.
prismadoit figurer dans lesdependencies(nondevDependencies) pour queprisma migrate deploysoit disponible dans l'image de production.- L'
entrypoint.shexécuteprisma migrate deployavant tout démarrage de l'application — voir section 2. - Ne jamais importer
dotenv/configdansprisma.config.ts: les variables d'environnement sont injectées par K8s.