Runbooks OrbitRunbook — Base de données

Runbook — Base de données

Migrations, sauvegardes et restauration de la base Neon d’Orbit.

Ce runbook est pour toi, le fondateur : tout se fait depuis les tableaux de bord (Neon, GitHub, Vercel, éventuellement Cloudflare). La version agent, avec le détail technique, est la skill .claude/skills/database/SKILL.md ; l'agent la suit avant toute modification du schéma.

Rappel des règles (AGENTS.md) : la base d'Orbit est sa propre base Neon ; chaque changement de schéma est une migration committée ; jamais de db push ; jamais d'action sur la base d'un autre produit.

1. La phase : packages/db/PHASE

Un fichier d'un seul mot dit à l'agent à quel point il doit être prudent.

ValeurQuandCe que ça change
pre-userspersonne d'autre que toi n'a de comptel'agent peut fusionner les migrations ou proposer une réinitialisation (toujours avec ton GO explicite)
productionle premier vrai utilisateur s'est inscritaucune donnée ne doit jamais être perdue : migrations en plusieurs étapes, sauvegarde avant chaque migration, ton GO à chaque fois

Passer en production (le jour où tu ouvres les inscriptions, au plus tard à la première inscription réelle) :

  1. GitHub → dépôt orbit.easyconnector.app → packages/db/PHASE → icône crayon.
  2. Remplace pre-users par production (un seul mot, rien d'autre).
  3. Commit changes (via une PR si main est protégée).
  4. Active les sauvegardes externes (section 4) si ce n'est pas déjà fait, et règle Neon (section 3).

On ne revient jamais de production à pre-users, sauf si tu as volontairement supprimé toutes les données des utilisateurs et que tu l'écris noir sur blanc à l'agent.

2. Appliquer une migration en production

L'agent prépare la migration dans une PR et termine sa description par un bloc « Base de données — action du propriétaire ». Après le merge :

  1. Sauvegarde — Neon → projet Orbit → branche de production → Backup & restore → Create snapshot, nom avant-<nom de la migration>. (Sur le plan Free, un seul snapshot manuel est possible : supprime l'ancien d'abord, ou crée plutôt une branche avant-<nom> depuis Branches → Create branch.)
  2. Migration — GitHub → Actions → Deploy production database → Run workflow → branche main → Run workflow.
  3. Contrôle — le run doit être vert, y compris l'étape « Verify schema drift ».
  4. Si le run est rouge : ne relance pas. Envoie le lien du run à l'agent. Il te dira quoi faire (souvent une petite correction SQL à coller dans l'éditeur SQL de Neon, qu'il aura écrite et relue).

En phase production, l'agent découpe tout changement risqué (renommer, supprimer, changer un type) en plusieurs PR : d'abord on ajoute (ce qui ne casse rien), puis le code utilise la nouveauté, et seulement à la fin on retire l'ancien. Pourquoi : Vercel met le code en ligne dès le merge, alors que la migration ne part que quand tu lances le workflow ; et un Instant Rollback Vercel remet l'ancien code en ligne sans toucher à la base. La base doit donc toujours convenir à l'ancien et au nouveau code. Ne merge jamais une PR qui dit « nécessite la migration X » tant que X n'est pas passée.

Tester une migration risquée sur une copie (optionnel, conseillé en production)

  1. Neon → Branches → Create branch depuis la production, nom test-<migration>, avec une date d'expiration.
  2. Donne à l'agent… rien : il n'a pas accès à Neon. Il te fournit le SQL de la migration ; colle-le dans l'éditeur SQL de la branche de test (vérifie le nom de la branche en haut de l'éditeur), puis les requêtes de vérification qu'il t'aura données.
  3. Supprime la branche de test.

3. Sauvegardes dans Neon (à régler une fois)

  • Historique (restauration à la seconde près) : Neon → projet → Settings → fenêtre d'historique (« history window ») au maximum du plan : 6 h sur Free (fixe), jusqu'à 7 jours sur Launch, jusqu'à 30 jours sur Scale.
  • Snapshots automatiques (plans payants) : Backup & restore → Edit schedule → Daily, conservation 35 jours.
  • Snapshot manuel avant chaque migration (section 2).

4. Sauvegarde externe (hors Neon)

But : si le compte Neon disparaît, Orbit garde une copie chiffrée ailleurs. Le workflow Back up production database (.github/workflows/db-backup.yml) fait chaque nuit une copie de la base, vérifie qu'elle se restaure dans une base vide, la chiffre, puis la range comme artefact GitHub (et, si tu le configures, dans un bucket Cloudflare R2). Il est éteint tant que tu ne l'actives pas.

Mise en route (une fois, ~20 minutes)

  1. Clé de chiffrement (le seul moment en terminal) : installe age (Mac : brew install age ; Windows : winget install FiloSottile.age), puis age-keygen -o orbit-backup-key.txt.
    • La ligne AGE-SECRET-KEY-… est la clé privée : mets-la dans ton gestionnaire de mots de passe et garde une copie hors ligne, puis supprime le fichier. Sans elle, aucune sauvegarde n'est lisible.
    • La ligne # public key: age1… est la clé publique (pas secrète).
  2. GitHub → Settings → Environments → New environment → db-backup → Deployment branches : main uniquement. Dans cet environnement :
    • Secret DB_BACKUP_DATABASE_URL : dans Neon → Connect → désactive Connection pooling → copie la chaîne (l'hôte ne doit pas contenir -pooler). Idéalement celle d'un rôle en lecture seule (l'agent te donne le SQL, voir la skill references/backup-restore.md).
    • Variable DB_BACKUP_AGE_RECIPIENT : la clé publique age1….
  3. GitHub → Actions → Back up production database → Run workflow. Le run doit être vert ; le résumé affiche « Restore test: OK », le nombre de lignes par table et l'empreinte SHA-256 ; l'artefact orbit-db-… apparaît en bas de la page.
  4. Pour l'activer chaque nuit : GitHub → Settings → Secrets and variables → Actions → onglet Variables → New repository variable DB_BACKUP_ENABLED = true.
  5. (Conseillé en phase production) Deuxième copie chez un autre fournisseur : Cloudflare → R2 → crée un bucket privé orbit-db-backups → règle de cycle de vie « supprimer après 90 jours » → crée un jeton API R2 limité à ce bucket, droits Object Read & Write. Ajoute dans l'environnement db-backup les secrets DB_BACKUP_S3_ENDPOINT (https://<id du compte>.r2.cloudflarestorage.com), DB_BACKUP_S3_BUCKET, DB_BACKUP_S3_ACCESS_KEY_ID, DB_BACKUP_S3_SECRET_ACCESS_KEY (les quatre, ou aucun).

Facultatif : DB_BACKUP_ARTIFACT_RETENTION_DAYS (variable, 30 par défaut) pour la durée de conservation des artefacts GitHub.

Tous ces noms appartiennent à Orbit. Ne copie jamais la valeur d'un autre produit.

Pourquoi ces choix

  • Artefact GitHub par défaut : aucun nouveau compte, bouton de téléchargement sur la page du run.
  • Cloudflare R2 en plus : un fournisseur différent de Neon, Vercel et GitHub, sans frais de sortie.
  • Pas Vercel Blob : c'est la même plateforme que l'application ; un incident ou une fuite de jeton toucherait la production et ses sauvegardes en même temps.
  • Chiffré avec une clé publique : GitHub peut chiffrer, jamais déchiffrer. Seule ta clé privée ouvre une sauvegarde.

5. Restaurer

Choisis l'outil selon l'incident, et préviens toujours l'agent avant de cliquer (il prépare les vérifications) :

SituationQuoi faire
Une migration ou un bug a abîmé des données, il y a moins que la fenêtre d'historiqueNeon → Backup & restore → Restore from history : d'abord sur une nouvelle branche pour regarder, puis soit l'agent te donne le SQL pour recopier seulement les lignes perdues, soit (si tout est à reprendre) restauration de la branche de production
Revenir à l'état d'avant une opération prévueNeon → Backup & restore → le snapshot avant-… → Restore → Multi-step (pour vérifier) ou One-step
Plus ancien que l'historique, moins de 35 jourssnapshot automatique, même chemin
Le projet Neon est perdusauvegarde externe : nouveau projet Neon, restauration par l'agent avec toi (tu tapes la commande de déchiffrement, tu ne colles jamais la clé privée dans une conversation), puis mise à jour de DATABASE_URL et DATABASE_URL_UNPOOLED dans Vercel et dans l'environnement GitHub production

Une restauration Neon de la branche de production efface tout ce qui a été écrit après l'instant choisi, sur toutes les bases de la branche. Neon garde l'état d'avant dans une branche <nom>_old_<horodatage> : on peut annuler en restaurant depuis celle-ci.

6. Exercice de restauration (tous les trois mois, phase production)

  1. Télécharge l'artefact du dernier run vert de Back up production database et vérifie l'empreinte SHA-256 affichée dans le résumé.
  2. Neon → crée une branche exercice-<date> ; dans son éditeur SQL : DROP SCHEMA public CASCADE; CREATE SCHEMA public; (vérifie trois fois le nom de la branche).
  3. Avec l'agent : déchiffrement avec ta clé privée, pg_restore dans cette branche.
  4. Compare quelques comptages avec le résumé du run. Note la date et le résultat.
  5. Supprime la branche exercice-<date>.

7. Réinitialiser la base (phase pre-users uniquement)

Tant qu'Orbit n'a aucun utilisateur, l'agent peut te proposer de fusionner les migrations et de repartir d'une base vide. Il ne le fait jamais sans ton GO écrit. La marche à suivre est Runbook reinitialiser-la-base. En phase production, c'est interdit.

8. Ce que l'agent ne fera jamais

  • toucher la base de production autrement que par le workflow Deploy production database ;
  • utiliser prisma db push, ou réécrire une migration déjà appliquée en production ;
  • donner un GO à ta place, ou considérer un merge comme un GO pour une réinitialisation ou une restauration ;
  • copier un secret d'un autre produit.

Sources officielles