Mettre en production
Ce qui vit sur le serveur et ne quitte jamais le serveur, l’ordre d’un déploiement — le code, la migration, puis le nouveau conteneur —, ce que le site répare lui-même en démarrant, comment lire ses journaux, faire de la place sur le disque, et revenir en arrière.
Précédemment. Le Dockerfile produit deux images : un migrateur, qui applique les migrations, et le service, qui ne contient que ce qui s’exécute, sans un seul secret.
Le site tourne sur un serveur loué, dans des conteneurs Docker, derrière un serveur web (Caddy) qui reçoit les visiteurs, s’occupe du HTTPS et leur transmet les requêtes. La base Postgres tourne sur le même serveur, dans son propre conteneur. Les fichiers, eux, sont chez Cloudflare R2 (partie « Stockage et médias »).
Ce qui vit sur le serveur, et pas dans le dépôt#
Le dépôt contient le code et la recette de l’image. Il ne contient pas ce qui est propre au serveur :
Sur le serveur | Ce que c’est | Pourquoi hors du dépôt |
|---|---|---|
Un clone du dépôt | Le code du site, mis à jour par git pull ; l’image se construit à partir de lui. | C’est le dépôt lui-même. |
Le fichier docker-compose.yml de l’infrastructure | Tous les services du serveur (le site, Postgres, Caddy…), versionné dans un dépôt privé distinct. | Il décrit tout le serveur, pas seulement ce site. |
Le fichier d’environnement du site (env_file) | Les secrets et les adresses de production : base, jeton R2, SMTP, JWT_SECRET. | Des secrets : jamais dans un dépôt, jamais dans une image. |
Les arguments de build (build: args) | Les NEXT_PUBLIC_ à figer dans le code compilé. | Ils appartiennent au déploiement, pas au code ; NEXT_PUBLIC_APP_SHORT_NAME a désormais une valeur par défaut. |
Showing entries1to4entries
Le service du site, dans ce compose, se construit à partir du clone (build: context), reçoit ses arguments de build, et lit son fichier d’environnement au démarrage (env_file:). L’hôte de la base, dans ce fichier, est le nom du service Postgres dans le compose, pas localhost : dans un conteneur, localhost désigne le conteneur lui-même (partie « Configuration »).
Les secrets de production se tapent sur le serveur, dans son fichier d’environnement. Ils ne passent ni par le dépôt, ni par une image, ni par un message, ni par une machine de développement : le jeton de production ne se copie pas sur le Mac.
L’ordre d’un déploiement 🚀#
Étape | Commande (sur le serveur) | Pourquoi à ce moment |
|---|---|---|
1. Le code | git pull, dans le clone | Rien ne change encore pour les visiteurs : le conteneur en service tourne sur son image. |
2. La migration, puis le nouveau conteneur | docker compose up -d --build akfc | Les deux images sont reconstruites ; akfc-migrator applique les migrations, puis le conteneur du site est remplacé — seulement si la migration a réussi. |
3. La vérification | docker compose logs akfc-migrator, puis akfc | Ce que la migration a appliqué ; puis les lignes [instrumentation] : ce que le site a trouvé et réparé en démarrant. |
Showing entries1to3entries
Pourquoi la migration avant le conteneur#
L’inverse — le nouveau code d’abord — fait démarrer un site qui lit des colonnes qui n’existent pas encore. Le démarrage lui-même interroge la base (la section suivante) : chaque vérification échoue, et chaque page qui touche la nouvelle colonne répond une erreur, jusqu’à la migration.
L’ordre retenu demande en échange que l’ancien code supporte le nouveau schéma, le temps de reconstruire l’image. C’est le cas d’une migration qui ajoute (une table, une colonne facultative ou avec une valeur par défaut) : l’ancien code ne la voit pas. Une migration qui retire ou renomme se fait en deux déploiements : d’abord le code qui ne lit plus l’ancienne colonne, ensuite la migration qui la supprime.
Une migration ne se défait pas. Prisma n’a pas de « migrate down » : revenir en arrière, c’est écrire une nouvelle migration. C’est une raison de plus de n’écrire que des migrations qui ajoutent, et de relire chacune avant de la commiter (partie « Données »).
Lancer la migration#
Le dépôt fournit l’image migrator (chapitre 1), dont la seule commande est prisma migrate deploy. Le compose du serveur la lance comme un service à part, akfc-migrator, et le site en dépend :
docker-compose.yml du serveur (extrait, dépôt d’infrastructure)
akfc: # … depends_on: postgres: condition: service_started pgbouncer: condition: service_started akfc-migrator: condition: service_completed_successfully akfc-migrator: build: context: ./backends/akfc target: migrator container_name: akfc-migrator restart: "no" env_file: ./backends/akfc/.env networks: - web - internalÀ chaque docker compose up du site, Compose construit l’image du migrateur, la lance dans le réseau interne — où le nom du service Postgres se résout —, attend qu’elle se termine sans erreur (service_completed_successfully), et seulement alors démarre le nouveau conteneur du site. Sans migration nouvelle, le migrateur le dit et s’arrête :
docker compose logs akfc-migrator
21 migrations found in prisma/migrationsNo pending migrations to apply.Une migration qui échoue arrête la chaîne : le nouveau conteneur ne démarre pas, et le site continue de tourner sur l’ancien. La base, elle, a pu recevoir une partie de la migration — Prisma ne l’enveloppe pas dans une transaction. L’échec est noté dans _prisma_migrations, et les déploiements suivants s’arrêtent au même endroit tant qu’il n’a pas été examiné, la base remise d’aplomb, et l’échec résolu (prisma migrate resolve). C’est une raison de plus de tester chaque migration sur la base locale, puis dans les tests de bout en bout, qui rejouent toutes les migrations depuis zéro (partie « Qualité »). restart: "no" empêche Docker de relancer le migrateur en boucle : il tourne une fois par déploiement.
La sandbox enchaîne déjà ses services ainsi (chapitre 3). Sur le serveur, la migration a longtemps été une étape à la main du déploiement, et l’oublier mettait en ligne un code qui attendait une base plus récente que la vraie (partie « Données ») ; elle fait désormais partie du up.
En secours, une migration peut aussi partir du clone, avec la CLI Prisma de ses dépendances. Le fichier d’environnement désigne alors un hôte (le nom du service Postgres) que le serveur lui-même, hors des conteneurs, ne connaît pas : l’adresse de la base se donne le temps de la commande, pointée sur le conteneur Postgres. dotenv ne remplace pas une variable déjà présente : celle donnée sur la ligne de commande l’emporte sur le fichier.
migrate deploy applique les migrations qui manquent, dans l’ordre, et les enregistre dans la table _prisma_migrations : relancé, il ne refait rien. Il n’en crée jamais, et ne réinitialise jamais rien — contrairement à migrate dev et migrate reset, qui n’ont rien à faire en production.
Ce que le site fait en démarrant#
Au démarrage du serveur Node, Next appelle register() dans instrumentation.ts, une fois. Le site y vérifie et répare ce dont il a besoin pour fonctionner, puis lance ses tâches de fond :
instrumentation.ts — register()
Clique une étape pour mettre en évidence la zone correspondante dans le fichier complet.
// ⚠ Fichier introuvable : apps/web/src/instrumentation.ts// Il a été déplacé ou supprimé depuis la rédaction de ce chapitre.Une donnée dont le site a besoin pour fonctionner (un dossier racine, le groupe des administrateurs) n’est pas créée par une manipulation unique sur le serveur, qu’on oubliera de refaire : elle est garantie par le code, à chaque démarrage. Un serveur neuf, une base restaurée, une sandbox se retrouvent dans le même état.
Lire les journaux#
Après un déploiement, les journaux du conteneur disent ce qui s’est passé :
Ligne | Ce qu’elle dit |
|---|---|
akfc-migrator : No pending migrations to apply / Applying migration … | Ce que le déploiement a fait à la base : rien, ou les migrations appliquées, une par une. |
[instrumentation] ensure…: N … | Une réparation au démarrage, et combien d’éléments elle a dû créer : zéro, d’ordinaire. |
[instrumentation] … failed — app will still start | Une réparation qui a échoué : le site tourne, mais ce qu’elle garantit manque peut-être. À lire en premier. |
[instrumentation] purge RGPD … / relève … / récapitulatif … | L’état des tâches de fond : actives ou non, et à quelle heure. |
[trpc] <procédure> : … | Une erreur imprévue dans une procédure, avec sa cause complète (le visiteur, lui, reçoit un message neutre ; partie « API »). |
Showing entries1to5entries
Une erreur de connexion à la base au démarrage (P1001, « can’t reach database server ») se lit d’abord dans l’adresse : un hôte localhost dans le fichier d’environnement du conteneur, un nom de service mal orthographié, un rôle qui n’existe pas. Chaque erreur corrigée en révèle parfois une autre plus loin : c’est un progrès, pas un échec.
Faire de la place#
Chaque construction laisse des images et un cache de construction. Sur un serveur au disque modeste, ils finissent par le remplir, et la construction suivante échoue. Le dépôt fournit un nettoyage sûr :
// ⚠ Fichier introuvable : docker-cleanup.sh// Il a été déplacé ou supprimé depuis la rédaction de ce chapitre.Il supprime le cache de construction et les images qu’aucun conteneur n’utilise, et jamais les volumes : c’est dans les volumes (et les dossiers montés) que vivent les données, la base en premier. docker system prune --volumes, au contraire, efface tout volume qu’aucun conteneur n’utilise — celui d’une base dont le conteneur vient d’être supprimé par un docker compose down, par exemple.
Revenir en arrière#
Un déploiement qui casse se défait en reconstruisant l’image du commit précédent : git checkout <commit> dans le clone, puis docker compose up -d --build pour le service du site. La base, elle, reste au nouveau schéma — une migration ne se défait pas. C’est là que les migrations qui ajoutent paient : l’ancien code fonctionne sur la base migrée, et le retour arrière est immédiat. Une fois la cause trouvée et corrigée, le clone revient sur la branche (git checkout main), et le déploiement suivant repart de l’étape 1.
Récapitulatif#
Question | Réponse du projet |
|---|---|
Où sont les secrets de production ? | Dans le fichier d’environnement du site, sur le serveur seulement. |
Dans quel ordre déployer ? | git pull, puis docker compose up -d --build akfc : le service akfc-migrator applique les migrations, et le site ne redémarre que si elles ont réussi ; puis les journaux. |
Pourquoi la migration d’abord ? | Le nouveau code démarre sur une base prête ; en échange, les migrations doivent supporter l’ancien code (elles ajoutent). |
Peut-on défaire une migration ? | Non : on écrit une nouvelle migration. Retirer une colonne se fait en deux déploiements. |
Que fait le site en démarrant ? | Il garantit ses données indispensables (idempotent, étape par étape) et lance ses tâches de fond. |
Comment libérer le disque ? | docker-cleanup.sh : cache de construction et images inutilisées, jamais les volumes. |
Comment revenir en arrière ? | Reconstruire l’image du commit précédent ; la base reste migrée, ce que des migrations additives rendent sans danger. |
Showing entries1to7entries
À suivre#
La production est faite pour les membres. Pour essayer le site sans rien risquer — le montrer, tester une idée, tout casser —, il en faut une copie complète, qui tourne seule.
Chapitre suivant : la sandbox. Le site entier, sa base, son stockage et sa boîte e-mail dans une seule commande, avec des comptes de démonstration.