Bases de données¶
PostgreSQL et MySQL suivent la même recette en quatre temps :
fetch— récupérer le dump ;unpack— le décompresser si besoin ;start_sandbox— démarrer un serveur neuf, vide, jetable ;restore_postgres/restore_mysql— rejouer le dump dedans.
Puis les sondes comptent ce qui est arrivé dans ce serveur.
La version du bac à sable
Prenez la même version majeure qu'en production. Un dump pris sur un serveur plus récent ne se rejoue pas sur un serveur plus ancien. Si votre plan échoue sur une erreur de syntaxe SQL incompréhensible, c'est presque toujours ça.
Les mots de passe ci-dessous (demo-docmost, restore) sont ceux du conteneur
jetable créé pour l'exécution. Ils n'ont aucun rapport avec votre serveur
d'origine. Voir aussi comment adapter le bloc fetch
et comment trouver les seuils.
PostgreSQL¶
La sauvegarde en entrée¶
Un pg_dump compressé, un fichier par nuit, dans un dossier que le runner
peut lire : docmost_2026-09-02T03-00.sql.gz.
Le champ format de l'étape restore_postgres dit comment le rejouer :
| Votre dump | format |
Outil utilisé |
|---|---|---|
pg_dump sans option (SQL en clair) |
plain |
psql |
pg_dump -Fc (format custom) |
custom |
pg_restore |
pg_dump -Fd (répertoire) |
directory |
pg_restore |
| vous ne savez pas | auto |
détecté au contenu |
Le plan¶
{
"steps": [
{
"type": "fetch",
"name": "Récupérer le dump Docmost",
"config": {
"source": "local",
"path": "/backups/docmost/postgres",
"file_pattern": "docmost_*.sql.gz",
"selection_strategy": "latest_modified",
"max_age": "3h",
"output": "docmost.sql.gz"
}
},
{
"type": "unpack",
"name": "Décompresser",
"config": { "format": "gzip", "input": "docmost.sql.gz", "output": "docmost.sql" }
},
{
"type": "start_sandbox",
"name": "Démarrer PostgreSQL",
"config": {
"image": "postgres:16-alpine",
"name": "demo-docmost-restore",
"env": { "POSTGRES_DB": "docmost", "POSTGRES_USER": "docmost", "POSTGRES_PASSWORD": "demo-docmost" },
"healthcheck": { "cmd": ["pg_isready", "-U", "docmost"], "interval": "5s", "timeout": "5s", "retries": 12 }
}
},
{
"type": "restore_postgres",
"name": "Restaurer la base",
"config": {
"database": "docmost",
"user": "docmost",
"password": "demo-docmost",
"input": "docmost.sql",
"format": "plain"
}
}
],
"probes": [
{
"name": "schema",
"type": "postgres",
"timeout": 60,
"config": {
"PGHOST": "demo-docmost-restore",
"PGDATABASE": "docmost",
"PGUSER": "docmost",
"PGPASSWORD": "demo-docmost",
"RESTOREPROOF_PG_TABLE": "information_schema.tables WHERE table_schema = 'public'",
"RESTOREPROOF_PG_ASSERT": "gte",
"RESTOREPROOF_PG_EXPECTED_ROWS": "10"
}
},
{
"name": "comptes",
"type": "postgres",
"timeout": 60,
"config": {
"PGHOST": "demo-docmost-restore",
"PGDATABASE": "docmost",
"PGUSER": "docmost",
"PGPASSWORD": "demo-docmost",
"RESTOREPROOF_PG_TABLE": "users",
"RESTOREPROOF_PG_ASSERT": "gte",
"RESTOREPROOF_PG_EXPECTED_ROWS": "1"
}
}
]
}
Ce que chaque sonde prouve¶
schema— le dump a recréé au moins dix tables dans le schémapublic. C'est le contrôle grossier : il attrape le dump tronqué qui s'arrête au milieu, pas la table vidée.comptes— il reste au moins un compte. Une base parfaitement restaurée mais sans utilisateur est une base dans laquelle personne n'entre.
Le nom de la sonde (schema, comptes) est libre : c'est ce que vous lirez
dans le rapport, écrivez-le pour vous.
Les variables de la sonde postgres¶
Dans l'éditeur, vous remplissez un champ cible au format mabase ou
mabase/matable ; l'éditeur en dérive PGDATABASE et RESTOREPROOF_PG_TABLE.
En JSON, écrivez directement :
- connexion :
PGHOST,PGPORT,PGUSER,PGPASSWORD,PGDATABASE(les variablesRESTOREPROOF_PG_HOST,RESTOREPROOF_PG_PORT,RESTOREPROOF_PG_USER,RESTOREPROOF_PG_PASSWORD,RESTOREPROOF_PG_DATABASE,RESTOREPROOF_PG_SSLMODEmarchent aussi) ; - ce qu'on compte :
RESTOREPROOF_PG_TABLE, qui accepte une clauseWHEREet même une jointure, ouRESTOREPROOF_PG_QUERYpour votre propreSELECT; - l'affirmation :
RESTOREPROOF_PG_ASSERT(gte,eq,lte,zero) etRESTOREPROOF_PG_EXPECTED_ROWS.
zero est l'opérateur qui prouve une absence : « aucune ligne ne correspond ».
C'est lui qui attrape les données incohérentes — un article sans auteur, une
entrée sans propriétaire. Voir
Gitea et
Vaultwarden pour des plans qui
s'en servent.
Trouver les bons nombres¶
10 et 1 ci-dessus sont les valeurs de la pile de démonstration. Pour votre
base : laissez 1 partout, lancez un essai, et laissez l'écran vous
proposer les seuils à partir de ce que la sonde a réellement compté. Les
chiffres remontent dans le rapport, vous n'avez rien à compter vous-même.
Un cas à connaître : quand la cible ne nomme qu'une base et pas une table, le nombre observé est un nombre de tables, pas un nombre de lignes. RestoreProof ne vous propose alors pas ce chiffre comme seuil de lignes — il voudrait dire tout autre chose une fois figé.
MySQL¶
La sauvegarde en entrée¶
Un mysqldump compressé : wordpress_2026-09-02T03-00.sql.gz. Pas de champ
format ici : restore_mysql rejoue le fichier avec le client mysql.
Le plan¶
C'est le plan WordPress de la pile de démonstration ; hors WordPress, seuls les noms de tables changent.
{
"steps": [
{
"type": "fetch",
"name": "Récupérer le dump WordPress",
"config": {
"source": "local",
"path": "/backups/wordpress/mysql",
"file_pattern": "wordpress_*.sql.gz",
"selection_strategy": "latest_modified",
"max_age": "3h",
"output": "wordpress.sql.gz"
}
},
{
"type": "unpack",
"name": "Décompresser",
"config": { "format": "gzip", "input": "wordpress.sql.gz", "output": "wordpress.sql" }
},
{
"type": "start_sandbox",
"name": "Démarrer MySQL",
"config": {
"image": "mysql:8.0",
"name": "demo-wp-restore",
"env": { "MYSQL_ROOT_PASSWORD": "restore", "MYSQL_DATABASE": "wordpress" },
"healthcheck": {
"cmd": ["mysqladmin", "ping", "-h", "127.0.0.1", "-u", "root", "-prestore"],
"interval": "5s",
"timeout": "5s",
"retries": 24
}
}
},
{
"type": "restore_mysql",
"name": "Restaurer la base",
"config": {
"database": "wordpress",
"user": "root",
"password": "restore",
"input": "wordpress.sql"
}
}
],
"probes": [
{
"name": "wp_admin",
"type": "mysql",
"timeout": 60,
"config": {
"MYSQL_HOST": "demo-wp-restore",
"MYSQL_DATABASE": "wordpress",
"MYSQL_USER": "root",
"MYSQL_PASSWORD": "restore",
"RESTOREPROOF_MYSQL_TABLE": "wp_users",
"RESTOREPROOF_MYSQL_ASSERT": "gte",
"RESTOREPROOF_MYSQL_EXPECTED_ROWS": "1"
}
},
{
"name": "wp_content",
"type": "mysql",
"timeout": 60,
"config": {
"MYSQL_HOST": "demo-wp-restore",
"MYSQL_DATABASE": "wordpress",
"MYSQL_USER": "root",
"MYSQL_PASSWORD": "restore",
"RESTOREPROOF_MYSQL_TABLE": "wp_posts WHERE post_status = 'publish' AND post_type = 'post'",
"RESTOREPROOF_MYSQL_ASSERT": "gte",
"RESTOREPROOF_MYSQL_EXPECTED_ROWS": "12"
}
},
{
"name": "wp_site_url",
"type": "mysql",
"timeout": 60,
"config": {
"MYSQL_HOST": "demo-wp-restore",
"MYSQL_DATABASE": "wordpress",
"MYSQL_USER": "root",
"MYSQL_PASSWORD": "restore",
"RESTOREPROOF_MYSQL_TABLE": "wp_options WHERE option_name IN ('siteurl','home') AND option_value <> ''",
"RESTOREPROOF_MYSQL_ASSERT": "eq",
"RESTOREPROOF_MYSQL_EXPECTED_ROWS": "2"
}
},
{
"name": "wp_orphans",
"type": "mysql",
"timeout": 60,
"config": {
"MYSQL_HOST": "demo-wp-restore",
"MYSQL_DATABASE": "wordpress",
"MYSQL_USER": "root",
"MYSQL_PASSWORD": "restore",
"RESTOREPROOF_MYSQL_TABLE": "wp_posts p LEFT JOIN wp_users u ON u.ID = p.post_author WHERE p.post_author <> 0 AND u.ID IS NULL",
"RESTOREPROOF_MYSQL_ASSERT": "zero"
}
}
]
}
Ce que chaque sonde prouve¶
wp_admin— au moins un compte peut encore se connecter.wp_content— les articles publiés sont revenus. C'est le nombre qui distingue un site restauré d'une installation neuve : une installation neuve a déjà toutes ses tableswp_, et zéro contenu à vous.wp_site_url—siteurlethomesont là et non vides. Sans elles, WordPress sert un site que personne n'atteint, aussi complète que soit la restauration.eq: 2parce qu'on en attend exactement deux.wp_orphans— aucun article n'a perdu son auteur.zero: la restauration n'a pas seulement rempli les tables, elle a gardé les liens entre elles.
Les variables de la sonde mysql¶
Mêmes règles que PostgreSQL : MYSQL_HOST, MYSQL_PORT, MYSQL_USER,
MYSQL_PASSWORD, MYSQL_DATABASE (ou leurs équivalents
RESTOREPROOF_MYSQL_HOST, RESTOREPROOF_MYSQL_PORT,
RESTOREPROOF_MYSQL_USER, RESTOREPROOF_MYSQL_PASSWORD,
RESTOREPROOF_MYSQL_DATABASE, plus RESTOREPROOF_MYSQL_CHARSET et
RESTOREPROOF_MYSQL_TLS), puis RESTOREPROOF_MYSQL_TABLE ou
RESTOREPROOF_MYSQL_QUERY, RESTOREPROOF_MYSQL_ASSERT et
RESTOREPROOF_MYSQL_EXPECTED_ROWS.
MariaDB se teste avec le même plan : changez l'image du bac à sable pour
mariadb:11 ou mariadb:10.11.
Le préfixe des tables¶
wp_ est un défaut, pas une règle. Un site qui l'a changé a besoin de son
propre préfixe partout, sinon les quatre sondes interrogent des tables qui
n'existent pas — et une sonde qui échoue parce qu'elle regarde au mauvais
endroit ne prouve rien sur votre sauvegarde. Le modèle WordPress Database
de l'interface demande ce préfixe comme première question.