Fait partie de Hébergement par cadricielHébergement · Déploiements

Où exécuter les migrations de base de données lors d’un déploiement.

Lancez les migrations dans une étape de mise en production : une fois par déploiement, après une compilation réussie et avant le démarrage de la nouvelle version. Pas dans la compilation, qui roule avant qu’on sache si le code fonctionne. Pas au démarrage, qui revient à chaque redémarrage. Et rendez chaque migration compatible avec la version encore en ligne.

Faits vérifiés le

Compilation, mise en production ou démarrage : ce que chacun coûte

Un déploiement offre trois endroits où mettre migrate. Un seul s’exécute exactement une fois, au bon moment.

OùQuand ça s’exécuteCe qui tourne mal
Commande de compilationÀ chaque compilation, production et aperçus, avant que le nouveau code ait démarré une seule foisUne compilation qui échoue ensuite à la vérification d’état a déjà modifié le schéma. L’aperçu d’une branche non fusionnée migre la base partagée.
Commande de mise en productionUne fois par déploiement en production, après une bonne compilation, avant le démarrage de la nouvelle versionRien de structurel. L’ancienne version sert encore pendant ce temps : la migration doit aussi convenir à l’ancien code (plus bas).
Commande de démarrageÀ chaque démarrage du processus : déploiements, redémarrages après plantage, réveilLe démarrage s’allonge de la durée de la migration, une longue migration peut dépasser la fenêtre de vérification d’état et, avec plusieurs instances, elles se font concurrence.

Pourquoi pas dans la compilation

C’est tentant : la compilation a déjà DATABASE_URL, et ça tient en une ligne : npm run build && npx prisma migrate deploy. Le problème, c’est l’ordre. La compilation se termine, la nouvelle version démarre, et c’est seulement là qu’on sait si elle tient debout. Sinon, le schéma a changé et le code qui lui correspond ne tourne pas. Revenir au commit précédent, c’est remettre du vieux code devant un nouveau schéma.

Les aperçus aggravent les choses. Sur la plupart des plateformes, Canner compris, l’aperçu d’une demande de tirage se compile avec la même base que la production, à moins de la séparer. Une migration dans la compilation veut dire qu’ouvrir une demande de tirage modifie la production.

Les images Docker ont le même piège, vu de l’autre côté : une étape RUN qui migre pendant la construction de l’image exige les identifiants de la base à ce moment-là et s’exécute une fois par image, pas une fois par déploiement.

Pourquoi pas dans la commande de démarrage

npx prisma migrate deploy && node server.js fonctionne, et la commande de démarrage de Canner l’accepte. Mais elle s’exécute à chaque démarrage du processus. Chez Canner, ça comprend les redémarrages après un plantage et, avec Démarrage, chaque réveil après les 3 heures de veille : le premier visiteur du matin attend la vérification des migrations en plus du démarrage.

Elle gruge aussi votre délai de vérification d’état. Canner donne 30 secondes à une nouvelle version pour répondre sur son chemin de vérification. Une migration qui réécrit une grosse table dans la commande de démarrage peut tout prendre : le déploiement est marqué en échec, et la migration est peut-être restée à moitié faite. Sur les plateformes à plusieurs instances, elles la lancent toutes en même temps, et seul le verrou de l’outil vous protège.

Deux exécutions en même temps : les verrous

L’étape de mise en production roule une fois par déploiement, mais deux déploiements peuvent se chevaucher : une poussée arrive pendant la mise en production précédente, ou quelqu’un lance la commande à la main. La plupart des outils prennent un verrou consultatif Postgres pour que la deuxième exécution attende. La documentation de Heroku sur la phase de mise en production recommande d’en prendre un vous-même si l’outil ne le fait pas.

Un verrou consultatif appartient à une session. Derrière un regroupeur de connexions en mode transaction, comme PgBouncer, deux instructions de suite peuvent aboutir sur des connexions différentes et le verrou ne protège rien. C’est pourquoi Rails documente la désactivation d’advisory_locks derrière PgBouncer. Chez Canner, DATABASE_URL et la connexion de migration sont des connexions de session : laissez les verrous actifs.

  • Prisma : migrate deploy prend un verrou consultatif avec un délai de 10 secondes, non modifiable. S’il expire, relancez la commande. Depuis la 5.3.0, PRISMA_SCHEMA_DISABLE_ADVISORY_LOCK le désactive; abstenez-vous, sauf si rien d’autre ne peut lancer Migrate en même temps.
  • Rails : Active Record utilise des verrous consultatifs sur Postgres par défaut.
  • Laravel : php artisan migrate --isolated prend un verrou atomique par votre pilote de cache; tous les serveurs doivent partager le même cache. Une deuxième exécution qui trouve le verrou pris se termine avec succès sans migrer.
  • Django : migrate ne prend aucun verrou. Fiez-vous à l’étape de mise en production unique, ou entourez la commande d’un pg_advisory_lock vous-même.

Rendre une relance sans danger

Un outil de migration note ce qu’il a appliqué dans une table registre (_prisma_migrations, schema_migrations, django_migrations, migrations) et l’ignore la fois suivante : lancer la commande deux fois ne pose pas de problème. Ce qui casse une relance, c’est une migration appliquée à moitié.

Postgres peut annuler des changements de schéma dans une transaction. Django exécute les migrations dans une transaction sur Postgres par défaut, et Rails en entoure chaque migration. Gardez ce comportement. Les exceptions sont les instructions que Postgres refuse dans une transaction, comme CREATE INDEX CONCURRENTLY : elles exigent atomic = False dans Django ou disable_ddl_transaction! dans Rails, et méritent une migration à part pour qu’un échec ne laisse rien d’autre à moitié fait.

Les données de départ vont aussi dans l’étape de mise en production, écrites en insertions idempotentes (INSERT … ON CONFLICT DO NOTHING) pour qu’une deuxième exécution ne change rien.

Migrer comme propriétaire, faire tourner l’appli avec moins

La connexion qui peut faire un ALTER TABLE est celle qu’un attaquant veut. Donnez à l’appli en marche un rôle qui lit et écrit des lignes sans pouvoir changer le schéma, et gardez le propriétaire pour les migrations.

Sur l’instance dédiée de Canner, canner db least-privilege (voir la CLI) le fait en une étape : l’appli reçoit un rôle lecture-écriture comme DATABASE_URL, tout registre de migrations connu reste en lecture seule pour lui, et le propriétaire est remis à la seule commande de mise en production sous DATABASE_URL_MIGRATE. Ni les compilations ni l’appli en marche ne le reçoivent. Pointez votre outil dessus dans la commande de mise en production :

# Prisma (schema.prisma : url = env("DATABASE_URL"))
DATABASE_URL=$DATABASE_URL_MIGRATE npx prisma migrate deploy

# Django, Rails, Laravel : même principe
DATABASE_URL=$DATABASE_URL_MIGRATE python manage.py migrate --noinput

La commande pour chaque cadriciel

Prisma et Drizzle ont besoin de leur CLI au moment de la mise en production. Si prisma ou drizzle-kit est dans les devDependencies et que votre installation les retire, la commande échoue avec command not found; déplacez la CLI dans dependencies. La documentation la plus récente de Prisma remplace migrate deploy par db migrate; la règle ne change pas, elle va dans la commande de mise en production.

Drizzle : `push` ou `migrate`? drizzle-kit push applique directement l’écart de schéma, sans fichiers SQL. Drizzle le présente comme la meilleure approche pour le prototypage rapide, et dit que certaines équipes l’utilisent en production. Il demande aussi confirmation avant une instruction qui perd des données, sauf avec --force, et un déploiement ne peut pas répondre. Pour une base de production partagée, generate puis migrate donne du SQL qu’on peut relire avant qu’il s’exécute.

Rails 8 chez Canner : sans commande de mise en production, Canner lance bundle exec rails db:prepare avant chaque nouvelle version, ce qui prépare aussi les bases Solid Cache, Queue et Cable. Voir Hébergement Rails.

CadricielCommande de mise en productionTransaction sur PostgresExécutions simultanées
Rails † †bundle exec rails db:migrate (ou db:prepare, qui crée aussi une base manquante et peut être relancée sans risque)Chaque migration est enveloppée; désactivez avec disable_ddl_transaction!Verrou consultatif par défaut
Django †python manage.py migrate --noinputOui par défaut; désactivez avec atomic = FalseAucun verrou propre
Laravel †php artisan migrate --force (sans --force, il demande confirmation en production)Selon la migration--isolated avec un cache partagé
Prisma 7 † †npx prisma migrate deploy. Jamais migrate dev, que Prisma réserve au développementPar fichier de migrationVerrou consultatif, délai de 10 s
Drizzle † †npx drizzle-kit migrate, qui applique les fichiers SQL écrits par drizzle-kit generate et relusPar migrationVoir la documentation de Drizzle pour votre pilote

† Les détails sur les concurrents proviennent des pages publiées par chaque fournisseur aux dates indiquées; les prix et forfaits changent, vérifiez auprès du fournisseur avant de décider. Rails (2026-10-29) · Rails (2026-10-29) · Django (2026-10-29) · Laravel (2026-10-29) · Prisma 7 (2026-10-29) · Prisma 7 (2026-10-29) · Drizzle (2026-10-29) · Drizzle (2026-10-29)

L’ancienne version tourne sur le nouveau schéma

Une étape de mise en production modifie la base pendant que la version précédente sert encore. C’est vrai sur toute plateforme qui a une phase de mise en production. Pendant un moment, et plus longtemps si vous revenez en arrière, l’ancien code rencontre le nouveau schéma.

Chaque migration doit donc fonctionner avec le code d’avant. Ajouter une colonne qui accepte NULL ou une nouvelle table, c’est sans risque. Supprimer ou renommer une colonne que l’ancien code lit, non : les requêtes échouent jusqu’à ce que la nouvelle version soit en ligne, et un retour à l’ancien commit échoue carrément.

  • Renommer par étapes : ajouter la nouvelle colonne, écrire dans les deux, remplir, basculer les lectures, cesser d’écrire dans l’ancienne, la supprimer dans un déploiement suivant.
  • Supprimer en deux déploiements : d’abord du code qui ne lit plus la colonne (Rails : ignored_columns), ensuite la suppression.
  • NOT NULL sur une colonne existante : l’ajouter sans contrainte, remplir, puis ajouter la contrainte.
  • Index sur une grosse table : CREATE INDEX CONCURRENTLY, dans sa propre migration hors transaction.

Questions

Faut-il lancer les migrations dans le Dockerfile?

Non. La construction d’une image ne devrait pas avoir besoin des identifiants de la base de production, et une image construite une fois peut être déployée plusieurs fois, ou jamais. Lancez les migrations dans une étape de mise en production, ou une tâche ponctuelle, avec l’image que vous allez démarrer.

Est-ce sûr de lancer les migrations au démarrage de l’appli?

Ça fonctionne avec une seule instance et de petites migrations, mais ça s’exécute à chaque redémarrage et réveil, ralentit le démarrage, peut dépasser la fenêtre de vérification d’état et, avec plusieurs instances, dépend du verrou de l’outil. Une commande de mise en production ne s’exécute qu’une fois par déploiement.

Pourquoi prisma migrate deploy dit-il que l’attente du verrou consultatif a expiré?

Une autre commande Migrate a gardé le verrou plus de 10 secondes, ou la connexion passe par un regroupeur en mode transaction où les verrous de session ne tiennent pas. Relancez, vérifiez qu’aucun déploiement ne chevauche, et utilisez une connexion directe ou de session pour la mise en production.

Que se passe-t-il chez Canner si la commande de mise en production échoue?

Le déploiement s’arrête avant le démarrage de la nouvelle version, et la version actuelle reste en ligne. La sortie de la commande est dans le journal du déploiement. Corrigez, puis relancez le déploiement.

Peut-on utiliser drizzle-kit push en production?

Drizzle dit que certaines équipes le font. Il n’écrit aucun fichier de migration, donc rien à relire avant l’exécution, et il demande confirmation avant une perte de données sauf avec --force. Pour une base de production partagée, generate puis migrate se relit plus facilement.

Mettez vos migrations dans une commande de mise en production.

Chaque projet Canner reçoit une base Postgres et une commande de mise en production qui s’exécute une fois par déploiement, jamais sur les aperçus. La configuration pour chaque cadriciel est dans Hébergement par cadriciel.