Migrations de base de données avec Claude Code en production
Guide pratique pour utiliser Claude Code sur les migrations DB : expand/contract, Prisma, CI, backfill et rollback.
Une migration de base de données en production ne doit pas commencer par un simple prompt du type “modifie le schéma”. Le risque principal n’est pas seulement la syntaxe SQL. Il vient de l’ordre des opérations : déploiement applicatif, verrous de base de données, sauvegarde, backfill, feature flags, contrôles CI et stratégie de rollback.
Claude Code devient utile quand on l’utilise comme relecteur et assistant de mise en œuvre. Il devient risqué si on lui demande de générer une grosse migration puis qu’on l’applique sans découpage. Ce guide vise les équipes qui utilisent PostgreSQL et Prisma Migrate, mais le même raisonnement fonctionne avec des migrations SQL écrites à la main.
Gardez les sources officielles ouvertes pendant la review : documentation Claude Code, PostgreSQL sur explicit locking et ALTER TABLE, Prisma sur development and production et CLI reference, ainsi que GitHub Actions workflow syntax.
Le modèle expand/contract
Le modèle de base est expand/contract. Expand veut dire élargir la base pour que l’ancienne version et la nouvelle version de l’application puissent fonctionner en même temps. Contract veut dire retirer l’ancien chemin seulement après validation : ancienne colonne, ancienne lecture, ancienne contrainte ou ancien index.
flowchart LR
A["Backup and review"]
B["Expand: add nullable column or new table"]
C["Deploy code with dual write or feature flag"]
D["Backfill data in small batches"]
E["Validate staging and production metrics"]
F["Contract: add NOT NULL, remove old path"]
A --> B --> C --> D --> E --> F
L’échec classique consiste à laisser Claude Code ajouter une colonne, copier les données, poser NOT NULL et supprimer l’ancienne colonne dans une seule migration. Cela peut marcher sur une petite base locale, puis bloquer une table users ou orders de plusieurs millions de lignes en production.
Quelques termes sont importants. Un verrou est le mécanisme par lequel la base évite des opérations concurrentes incompatibles. Un backfill est un traitement qui remplit de nouvelles colonnes pour les lignes déjà existantes. Une shadow database est une base temporaire que Prisma utilise en développement pour rejouer l’historique et détecter le drift. Ce n’est pas une protection magique en production.
Commencer par une review
Le premier prompt doit demander une review, pas une modification de fichiers. Donnez à Claude Code la taille des tables, le mode de déploiement, l’ORM et les attentes de rollback.
Review this database migration plan before editing files.
Context:
- Production database: PostgreSQL
- ORM: Prisma Migrate
- Hot tables: users has about 8 million rows, orders has about 25 million rows
- Deploy style: blue/green app deploy, database migration runs in CI/CD
- Requirement: split users.name into users.full_name and users.display_name
Check:
1. Can old and new app versions run at the same time?
2. Which SQL statements may take strong locks or scan the whole table?
3. Which steps must be expand, backfill, validate, and contract?
4. What backup or point-in-time recovery check is needed before deploy?
5. What can be rolled back by app deploy, and what can only be rolled forward?
Return a migration plan first. Do not edit files yet.
La dernière phrase est essentielle. Claude Code peut modifier vite, mais une migration de base de données exige une pause de conception. Si le plan mélange des étapes dangereuses, demandez une version plus prudente.
Rewrite the plan so that no step drops a column, rewrites a large table, or sets NOT NULL before the backfill is verified. Include a staging rehearsal and a production abort condition.
Avec ce cadrage, Claude Code agit comme un reviewer de migration. L’humain garde le jugement de risque, et l’agent aide à lister les fichiers, les commandes, le SQL, les checks CI et les signaux de monitoring.
SQL pour expand
Prenons le cas où users.name doit être remplacé par full_name et display_name. La migration expand ajoute uniquement des colonnes nullable et un index. Elle ne fait pas de backfill, ne pose pas NOT NULL et ne supprime pas users.name.
-- 20260602090000_expand_users_names.sql
-- Keep this migration small. Do not backfill and do not drop users.name here.
ALTER TABLE users
ADD COLUMN full_name text,
ADD COLUMN display_name text;
-- Run outside a transaction in PostgreSQL migration tools that support it.
-- CREATE INDEX CONCURRENTLY cannot run inside a transaction block.
CREATE INDEX CONCURRENTLY IF NOT EXISTS users_display_name_idx
ON users (display_name);
La documentation PostgreSQL explique que ALTER TABLE peut prendre différents niveaux de verrou selon le sous-ordre. S’il n’existe pas de mention d’un verrou plus léger, il faut relire de façon conservatrice. Ne demandez pas à Claude Code de deviner : demandez-lui de rattacher son analyse aux docs officielles.
Avec Prisma, créez la migration sans l’appliquer, puis relisez le SQL.
npx prisma migrate dev --name expand-users-names --create-only
npx prisma validate
Le guide Prisma indique que npx prisma migrate deploy est le chemin pour les environnements de test et de production. Il précise aussi que migrate deploy applique les migrations en attente, mais ne détecte pas le drift et n’utilise pas de shadow database. Un deploy réussi n’est donc pas une répétition complète de production.
npx prisma migrate deploy
Adapter l’application
Après expand, l’application doit accepter les deux schémas. La lecture a besoin d’un fallback, et l’écriture doit remplir les anciens et les nouveaux champs pendant la transition.
// src/domain/userName.ts
type UserNameRow = {
name: string | null;
fullName: string | null;
displayName: string | null;
};
export function readDisplayName(user: UserNameRow): string {
return user.displayName ?? user.fullName ?? user.name ?? "Unknown user";
}
export function buildNameUpdate(input: { name: string }) {
const normalized = input.name.trim().replace(/\s+/g, " ");
return {
name: normalized,
fullName: normalized,
displayName: normalized.length > 40 ? `${normalized.slice(0, 39)}...` : normalized,
};
}
Une feature flag sépare le changement de structure de base du comportement visible par l’utilisateur. Gardez la nouvelle lecture désactivée pendant le backfill, activez-la après validation, puis désactivez-la si les métriques se dégradent. Pour la mise en œuvre, lisez aussi le guide interne feature flags avec Claude Code.
Ce modèle couvre plusieurs cas concrets : renommer ou découper des champs de profil, ajouter des colonnes calculées comme un solde de facture ou un libellé de recherche, et ajouter des index ou foreign keys sur des tables très utilisées. Dans chaque cas, le découpage réduit le risque.
Backfill par petits lots
Un énorme UPDATE peut augmenter la durée des verrous, le volume WAL, le retard de réplication et le risque opérationnel. Demandez plutôt à Claude Code un script de backfill relançable.
// scripts/backfill-user-names.mjs
import pg from "pg";
const { Client } = pg;
const batchSize = Number(process.env.BATCH_SIZE ?? 1000);
const sleepMs = Number(process.env.SLEEP_MS ?? 200);
const client = new Client({ connectionString: process.env.DATABASE_URL });
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
await client.connect();
try {
let total = 0;
while (true) {
const result = await client.query(
`
WITH target AS (
SELECT id, name
FROM users
WHERE full_name IS NULL
AND name IS NOT NULL
ORDER BY id
LIMIT $1
FOR UPDATE SKIP LOCKED
)
UPDATE users AS u
SET
full_name = target.name,
display_name = CASE
WHEN length(target.name) > 40 THEN substring(target.name from 1 for 39) || '...'
ELSE target.name
END
FROM target
WHERE u.id = target.id
RETURNING u.id
`,
[batchSize],
);
total += result.rowCount;
console.log(`updated=${result.rowCount} total=${total}`);
if (result.rowCount === 0) break;
await sleep(sleepMs);
}
} finally {
await client.end();
}
Avant de l’utiliser, relisez l’idempotence, la reprise après échec, l’exécution parallèle et les contrôles opérationnels. Un bon backfill a une taille de lot, une pause, des logs, une condition d’arrêt et une requête qui peut être rejouée sans corrompre les données.
CI et staging
La CI doit appliquer tout l’historique de migrations sur une base temporaire. Dans GitHub Actions, les workflows sont des fichiers YAML sous .github/workflows.
name: migration-check
on:
pull_request:
paths:
- "prisma/**"
- "scripts/backfill-*.mjs"
- ".github/workflows/migration-check.yml"
jobs:
prisma-migrations:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: app
ports:
- "5432:5432"
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/app?schema=public
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
- run: npm ci
- run: npx prisma validate
- run: npx prisma migrate deploy
- run: npx prisma migrate status
- name: Detect schema drift after migrations
run: |
npx prisma migrate diff \
--exit-code \
--from-config-datasource \
--to-schema=prisma/schema.prisma
Cet exemple utilise la syntaxe Prisma ORM v7 avec config datasource. Ne recopiez pas d’anciens flags comme --from-url ou --shadow-database-url sans vérifier la référence actuelle.
Le staging doit être plus réaliste que la CI : volume de lignes comparable, index similaires, timeouts proches et même runner de migration. Demandez à Claude Code de produire une checklist avec attente de verrous, retard de réplication, latence, logs applicatifs et seuils d’abandon.
Contract et limites du rollback
Ne faites contract qu’après stabilité de la nouvelle application et validation du backfill. Pour NOT NULL, utilisez d’abord une contrainte de vérification.
-- 20260602120000_contract_users_names.sql
-- Run only after the new application version has been stable in production.
ALTER TABLE users
ADD CONSTRAINT users_full_name_present
CHECK (full_name IS NOT NULL) NOT VALID;
ALTER TABLE users
VALIDATE CONSTRAINT users_full_name_present;
ALTER TABLE users
ALTER COLUMN full_name SET NOT NULL;
ALTER TABLE users
DROP CONSTRAINT users_full_name_present;
-- Drop old columns in a later deploy, not in the same deploy that changes reads.
-- ALTER TABLE users DROP COLUMN name;
La mauvaise hypothèse la plus dangereuse consiste à croire qu’une down migration restaure tout. Une colonne supprimée, une valeur écrasée ou une conversion avec perte ne reviennent pas par magie. Le rollback réaliste est souvent applicatif : version précédente ou feature flag. Côté base, il faut penser backup, point-in-time recovery ou correction vers l’avant.
prisma migrate resolve --rolled-back ne défait pas non plus une migration réussie. Cette commande sert à résoudre l’état de l’historique après une migration échouée. Demandez à Claude Code de séparer rollback applicatif, correction DB vers l’avant et restauration de données.
Échecs fréquents et workflow d’équipe
Premier échec : traiter un rename comme un drop and add. L’ORM ne lit pas toujours votre intention, donc relisez le SQL généré. Deuxième échec : mélanger changement de schéma et réécriture massive de données. Troisième échec : faire trop confiance à la shadow database, qui ne simule pas les données de production, le bloat, les files de locks ou le retard de réplication. Quatrième échec : ne pas vérifier concrètement les backups.
Écrivez les règles DB dans CLAUDE.md : pas de suppression de colonne de table chaude dans le même PR, pas de gros backfill dans une migration de schéma, migrations Prisma générées avec --create-only pour review SQL, liens officiels dans la review. Pour la structure, utilisez le guide CLAUDE.md best practices.
Dans un SaaS qui facture réellement, une mauvaise migration peut stopper onboarding, paiement et support. Les produits ClaudeCodeLab proposent prompts et checklists réutilisables, et la formation Claude Code aide à installer ce flux sur un vrai dépôt.
En pratique, le plus grand gain ne vient pas d’un SQL plus astucieux. Il vient du fait que chaque PR fait moins de choses. Un PR d’expand, un job de backfill séparé et un PR de contract rendent la sortie de Claude Code plus facile à relire. Avant de demander le SQL, demandez toujours les conditions d’arrêt et les étapes de récupération.
Résumé
Claude Code ne rend pas une migration de production automatiquement sûre. Il devient puissant quand l’équipe lui donne un modèle sûr : expand/contract, déploiement applicatif par étapes, petits backfills, CI, répétition en staging, feature flags et limites claires de rollback.
Pour votre prochaine migration, demandez d’abord une review des risques. Si le plan tient, laissez ensuite Claude Code écrire le SQL, la migration Prisma, le workflow GitHub Actions et le script de backfill.
PDF gratuit: cheatsheet Claude Code
Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.
Nous protégeons vos données et n'envoyons pas de spam.
À propos de l'auteur
Masa
Ingénieur spécialisé dans les workflows pratiques avec Claude Code.
Articles liés
Workflow Obsidian vers CLAUDE.md avec Claude Code
Transformer des notes Obsidian en notes CLAUDE.md concises pour reprendre les sessions sans réexpliquer.
Claude Code Revenue CTA Routing : relier articles, PDF, Gumroad et consultation
Un workflow Claude Code pour orienter les lecteurs vers PDF gratuit, Gumroad ou consultation selon l'intention.
Règles de handoff Claude Code en équipe: preuves, permissions, rollback et revenus
Un format concret pour transmettre un travail Claude Code avec preuves, permissions, rollback, PDF gratuit, Gumroad et consultation.