Une sauvegarde que personne n'a jamais restaurée est une hypothèse, pas une garantie. Vous savez déjà lancer une stack Compose avec une base et des fichiers. Ici, vous sauvegardez les trois morceaux d'une application (la base, les fichiers des volumes, la configuration du projet), vous les restaurez sur un environnement vierge et vous prouvez que le résultat est identique.
Vous croiserez aussi deux pièges. Copier le volume d'une base en marche donne une copie qui peut refuser de démarrer. Et l'option -T de docker compose exec ne protège pas ce qu'on croit : une sortie redirigée vers un fichier reste intacte sans elle.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Dire ce qu'il faut sauvegarder dans une application Compose, et ce qui est sensible dans la sauvegarde.
- Sauvegarder une base PostgreSQL avec
pg_dump -Fcet lire le contenu de l'archive avecpg_restore --list. - Expliquer ce que
-Tchange pourdocker compose exec. - Expliquer pourquoi la copie à chaud du volume d'une base n'est pas une sauvegarde.
- Sauvegarder les fichiers d'un volume avec
taret restaurer la configuration d'un projet. - Garder la base et les fichiers cohérents entre eux quand l'application écrit dans les deux.
- Restaurer sur une cible vierge, comparer une empreinte de contrôle et rejouer une restauration sur une base déjà remplie.
Prérequis
Section intitulée « Prérequis »Il vous faut un hôte Linux avec Docker Engine 29.9.2 et son plugin Compose 5.6.1, installés depuis le dépôt officiel comme le montre la leçon d'installation de Docker. Les mesures de cette leçon ont été faites sur Debian 13 avec PostgreSQL 18.6.
Vous savez écrire une stack Compose avec un volume nommé, ce que montre Persister les données d'une stack recréée, et vous connaissez la différence entre un volume et un montage de fichier, présentée dans Volumes Docker. La stack lit ses réglages dans un fichier .env (Interpolation et fichiers d'environnement) et le mot de passe de la base dans un secret (Injecter les secrets). L'option --wait de docker compose up est expliquée dans Démarrage, disponibilité et santé. Enfin, la leçon Lire les journaux et attribuer une saturation a déjà présenté -T comme la forme à prendre pour un docker compose exec lancé dans un script.
Cette leçon traite la sauvegarde logique d'une base, c'est-à-dire un export de son contenu. Les sauvegardes physiques (une copie des fichiers de la base, comme le fait pg_basebackup), l'archivage des journaux de transactions et la restauration à un instant donné sont décrits dans Sauvegarder et restaurer PostgreSQL et ne sont pas repris ici.
Quelle stack sert de démonstration ?
Section intitulée « Quelle stack sert de démonstration ? »Une petite bibliothèque dans un dossier ~/bibliotheque : un service base (PostgreSQL) et un service site (nginx) qui sert des images de couvertures. Le nom du dossier devient le nom du projet, donc les conteneurs s'appellent bibliotheque-... et les volumes bibliotheque_donnees et bibliotheque_couvertures.
Créez les dossiers, puis le fichier compose.yaml. Le dossier ~/sauv recevra les sauvegardes, qui contiennent toutes les données de la base : mkdir -m 700 le réserve à votre compte dès sa création, au lieu de restreindre chaque fichier après coup.
mkdir -p ~/bibliotheque/nginx ~/bibliotheque/secretsmkdir -m 700 -p ~/sauvcd ~/bibliothequeservices: base: image: postgres:18.6-alpine3.24@sha256:10a5c69a83035ab5239f5620d708520e8b28f5aa1f2b20143e6024532edadfb5 environment: POSTGRES_USER: ${BASE_UTILISATEUR} POSTGRES_DB: ${BASE_NOM} POSTGRES_PASSWORD_FILE: /run/secrets/base_mdp secrets: - base_mdp volumes: - donnees:/var/lib/postgresql healthcheck: test: ["CMD-SHELL", "pg_isready -U ${BASE_UTILISATEUR} -d ${BASE_NOM}"] interval: 2s timeout: 3s retries: 30 site: image: nginx:1.30.6-alpine@sha256:0e52bd054126cdc040b1276c0af347aeb02a41b9cfe305d6094767ac30e97949 ports: - "${SITE_PORT}:80" volumes: - couvertures:/usr/share/nginx/html/couvertures:ro - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:rosecrets: base_mdp: file: ./secrets/base_mdp.txtvolumes: donnees: couvertures:Le volume donnees est monté sur /var/lib/postgresql : avec PostgreSQL 18, l'image range les données dans un sous-dossier de ce chemin, ce qui comptera dans la section sur la copie à chaud. Le volume couvertures est monté en lecture seule dans nginx.
Écrivez ensuite la configuration de nginx, le fichier .env et le secret, en gardant des droits stricts sur les deux derniers. Le mot de passe est un faux, propre à la démonstration.
cat > nginx/default.conf <<'EOF'server { listen 80; location /couvertures/ { root /usr/share/nginx/html; } location = /sante { default_type text/plain; return 200 "ok\n"; }}EOFcat > .env <<'EOF'BASE_UTILISATEUR=biblioBASE_NOM=bibliothequeSITE_PORT=8080EOFprintf 'mdp-de-demo-bibliotheque' > secrets/base_mdp.txtchmod 600 .env secrets/base_mdp.txtdocker compose up -d --waitRemplissez la base avec cinq livres témoins. psql lit sur son entrée standard le texte qui suit (le « heredoc »). L'option -T dit à Compose de ne pas allouer de pseudo-terminal, un faux écran-clavier que la section sur -T explique.
docker compose exec -T base psql -U biblio -d bibliotheque -v ON_ERROR_STOP=1 <<'EOF'CREATE TABLE livres (id serial PRIMARY KEY, titre text NOT NULL, auteur text NOT NULL, annee int NOT NULL);INSERT INTO livres (titre, auteur, annee) VALUES ('Les Misérables','Victor Hugo',1862), ('Germinal','Émile Zola',1885), ('Le Petit Prince','Antoine de Saint-Exupéry',1943), ('L''Étranger','Albert Camus',1942), ('Bonjour tristesse','Françoise Sagan',1954);EOFPuis créez trois fichiers de couverture de 20 000 octets aléatoires dans le volume couvertures. L'image Alpine est épinglée par son digest, comme les deux autres : sans tag ni digest, docker run alpine tirerait alpine:latest, une référence qui change au fil des publications. La variable ALPINE évite de répéter la référence.
ALPINE=alpine:3.24@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9adocker run --rm -v bibliotheque_couvertures:/dst "$ALPINE" \ sh -c 'for i in 1 2 3; do head -c 20000 /dev/urandom > /dst/couverture-$i.jpg; done'Il reste à fixer ce que la restauration devra retrouver. Une empreinte de contrôle est un court résultat calculé sur les données : si elle est identique avant et après, les données le sont. Écrivez la requête dans un fichier, puis relevez l'empreinte de la base et celles des fichiers tels que nginx les sert. Dans curl -fsS, l'option -f fait échouer la commande sur une erreur HTTP (404, 500) au lieu de renvoyer la page d'erreur : sans elle, sha256sum hacherait cette page et l'empreinte aurait l'air valide. -sS garde le silence sauf en cas d'erreur.
cat > ~/sauv/empreinte.sql <<'EOF'select count(*)||' lignes, md5 '||md5(string_agg(id||titre||auteur||annee,'|' order by id)) from livresEOFdocker compose exec -T base psql -U biblio -d bibliotheque -Atf - < ~/sauv/empreinte.sqlfor i in 1 2 3; do curl -fsS http://localhost:8080/couvertures/couverture-$i.jpg | sha256sum; done > ~/sauv/sha-source.txtcat ~/sauv/sha-source.txt5 lignes, md5 b52719a3d3e67d9d6e6b36e8a31f2877Le nombre de lignes et le md5 forment la valeur que la cible devra reproduire. Les options -Atf - de psql demandent une sortie brute, sans en-tête, avec la requête lue sur l'entrée standard.
Le fichier sha-source.txt garde l'empreinte des trois couvertures. cat doit afficher trois lignes, chacune une empreinte SHA-256 de 64 caractères hexadécimaux, et les trois sont différentes, puisque les fichiers sont aléatoires. Trois fois la même empreinte signalerait que curl a échoué et que sha256sum a haché une entrée vide.
Que faut-il sauvegarder dans une application Compose ?
Section intitulée « Que faut-il sauvegarder dans une application Compose ? »Trois choses, qui ne se sauvegardent pas de la même manière : les données de la base, les fichiers des volumes et la configuration du projet. Les images ne font pas partie de la liste, puisqu'elles se retrouvent par leur digest tant que le registre les sert.
| Ce qui compte | Où ça vit | Comment le sauvegarder | Sensibilité |
|---|---|---|---|
| Les données de la base | Le volume donnees, géré par PostgreSQL | pg_dump lancé dans le conteneur | Contient toutes les données métier |
| Les fichiers de l'application | Le volume couvertures | tar lancé dans un conteneur qui monte le volume | Selon la nature des fichiers |
| La configuration du projet | compose.yaml, .env, nginx/, secrets/ dans le dossier du projet | tar du dossier, côté hôte | Contient le mot de passe de la base |
La dernière ligne du tableau est la plus piégeuse. L'archive de configuration contient le fichier de secret et le .env : elle est aussi sensible que le secret lui-même. Gardez-la hors de tout dépôt Git. Le dump de la base est tout aussi sensible : il porte l'ensemble des données métier. C'est pourquoi le dossier de sauvegarde est créé en 700.
Une sauvegarde ne se juge qu'à la restauration. Chaque morceau sera donc restauré plus bas sur une cible vierge, avec la même empreinte de contrôle pour conclure.
Comment sauvegarder la base avec pg_dump ?
Section intitulée « Comment sauvegarder la base avec pg_dump ? »En lançant pg_dump dans le conteneur de la base avec docker compose exec, et en redirigeant sa sortie vers un fichier de l'hôte. pg_dump interroge le serveur comme n'importe quel client et lit les données par une transaction : selon la documentation de pg_dump, il obtient une vue cohérente de la base même pendant que d'autres clients écrivent. Le format -Fc, dit format personnalisé, produit une archive compressée propre à PostgreSQL que pg_restore sait relire, table par table si on le souhaite.
cd ~/sauvdocker compose -f ~/bibliotheque/compose.yaml exec -T base pg_dump -Fc -U biblio bibliotheque > biblio.dumpls -l biblio.dumpSur la machine de mesure, la commande a pris 0,1 s et l'archive fait 2 887 octets : la base est minuscule. Retenez le principe plutôt que la taille : le fichier biblio.dump est créé par le shell de l'hôte (c'est la redirection >), alors que pg_dump tourne dans le conteneur et écrit sur sa sortie standard.
L'option -f de Compose désigne le fichier du projet, ce qui permet de lancer la commande depuis ~/sauv. Vous pouvez aussi vous placer dans ~/bibliotheque.
Que change l'option -T de docker compose exec ?
Section intitulée « Que change l'option -T de docker compose exec ? »-T empêche Docker d'allouer un pseudo-terminal, c'est-à-dire un faux écran-clavier, pour la commande. Sans l'option, docker compose exec peut en allouer un, et ce qui compte pour le résultat, c'est l'endroit où va la sortie standard : un fichier ou un terminal.
On pourrait croire que sans -T, un pseudo-terminal abîme la sortie redirigée vers un fichier. La mesure dit autre chose. Sur la machine de mesure, pg_dump -Fc lancé sans -T et redirigé vers un fichier a rendu le code 0, et un fichier de la même taille que le premier (2 887 octets), avec ou sans pseudo-terminal sur l'entrée. pg_restore --list a lu ce fichier. La redirection > ne corrompt donc pas l'archive.
Le défaut apparaît quand la sortie standard est un terminal : un pg_dump en texte, sans redirection, lancé sous pseudo-terminal a produit des fins de ligne CRLF (un retour chariot avant chaque saut de ligne, 47 comptés dans la sortie). N'envoyez jamais pg_dump -Fc vers un terminal : une archive binaire n'a rien à faire à l'écran, redirigez-la toujours vers un fichier.
Le vrai piège est ailleurs, et -T ne le règle pas.
Dans un script lu sur l'entrée standard (une commande lancée par ssh ... bash -s, ou une boucle while read), un docker compose exec qui n'a pas de redirection d'entrée consomme la suite du script : les commandes suivantes ne s'exécutent jamais. La parade est de lui donner une entrée à lui, avec < /dev/null quand la commande n'a rien à lire. La règle tient donc en deux gestes : -T pour toute commande redirigée ou scriptée, qui supprime le pseudo-terminal, et < /dev/null pour que exec ne touche pas à l'entrée du script.
Comment vérifier le contenu d'une sauvegarde sans la restaurer ?
Section intitulée « Comment vérifier le contenu d'une sauvegarde sans la restaurer ? »Avec pg_restore --list, qui lit l'archive et en affiche la table des matières sans rien écrire dans une base. Vous le lancez dans le conteneur, l'archive arrive sur l'entrée standard, et -T évite le pseudo-terminal.
docker compose -f ~/bibliotheque/compose.yaml exec -T base pg_restore --list < biblio.dump;; Archive created at 2026-11-03 05:47:10 UTC; dbname: bibliotheque; TOC Entries: 11; Compression: gzip; Dump Version: 1.16-0; Format: CUSTOM; Integer: 4 bytes; Offset: 8 bytes; Dumped from database version: 18.6; Dumped by pg_dump version: 18.6;;; Selected TOC Entries:;220; 1259 16386 TABLE public livres biblio219; 1259 16385 SEQUENCE public livres_id_seq biblio3481; 0 0 SEQUENCE OWNED BY public livres_id_seq biblio3323; 2604 16389 DEFAULT public livres id biblio3474; 0 16386 TABLE DATA public livres biblio3482; 0 0 SEQUENCE SET public livres_id_seq biblio3325; 2606 16397 CONSTRAINT public livres livres_pkey biblioL'en-tête se lit en premier : Format: CUSTOM confirme le format, et Dumped from database version: 18.6 dit de quelle version vient l'archive. Cherchez ensuite la ligne TABLE DATA public livres : c'est elle qui porte les lignes de la table. Sans elle, l'archive contiendrait la structure mais aucune donnée. La ligne SEQUENCE SET porte la position de la séquence, c'est-à-dire le dernier numéro attribué, que la restauration doit aussi retrouver.
Une limite à connaître : pg_dump ne sauvegarde que la base visée, pas les rôles (les utilisateurs et leurs droits), qui appartiennent à l'ensemble du serveur. La section pg_dumpall --globals-only de Sauvegarder et restaurer PostgreSQL explique comment les exporter.
Comment garder la base et les fichiers cohérents entre eux ?
Section intitulée « Comment garder la base et les fichiers cohérents entre eux ? »Quand l'application écrit à la fois dans la base et dans les fichiers, les deux sauvegardes, prises à deux moments différents, peuvent se contredire : la base référence un fichier absent de l'archive, ou l'inverse. La parade est d'arrêter le service qui écrit pendant les deux sauvegardes, puis de le relancer : docker compose stop site avant, docker compose start site après (remplacez site par le service qui écrit chez vous). Dans cette démonstration, le site ne fait que lire, la précaution est donc inutile ; le guide Sauvegarder et restaurer Forgejo traite le cas d'une application réelle.
Pourquoi copier le volume d'une base en marche n'est-il pas une sauvegarde ?
Section intitulée « Pourquoi copier le volume d'une base en marche n'est-il pas une sauvegarde ? »Parce que les fichiers d'une base en marche changent pendant la copie : PostgreSQL crée, remplit et supprime en continu des segments de son journal de transactions (le WAL, ses fichiers pg_wal). Une copie de fichiers prise à ce moment mélange des états de dates différentes, et la base copiée peut ne pas redémarrer dessus. Pour une copie de fichiers ordinaire (tar, cp), il faut donc arrêter le serveur avant de copier. Un instantané du système de fichiers, image figée du disque prise en une seule fois, ferait aussi l'affaire, mais seuls certains systèmes de fichiers ou volumes savent en produire.
Le banc de mesure a rejoué ce cas cinq fois sur une base de 539 Mo, alimentée en continu par un écrivain (des lots de 5 000 lignes, avec un CHECKPOINT toutes les 0,3 s). C'est une charge volontairement forte. Pendant ce temps, le volume était copié par un tar lancé dans un conteneur, puis un PostgreSQL de même image était démarré sur la copie.
Voici la copie réalisée. Sur votre petite base au repos, la rejouer ne reproduirait pas ces échecs : lisez la commande et ce qu'elle a donné.
docker run --rm -v bibliotheque_donnees:/src:ro "$ALPINE" tar cf - -C /src . \ | docker run --rm -i -v copie1:/dst "$ALPINE" tar xf - -C /dstCinq essais sur cinq ont échoué, de la même façon. Le tar de lecture a rendu le code 1, avec des messages de ce type pour des segments du WAL disparus pendant la copie.
tar: ./18/docker/pg_wal/000000010000000000000048: No such file or directorytar: ./18/docker/pg_wal/00000001000000000000004A: No such file or directoryLe PostgreSQL démarré sur la copie s'est ensuite arrêté en une seconde.
2026-11-03 05:48:59.621 UTC [31] LOG: invalid checkpoint record2026-11-03 05:48:59.621 UTC [31] PANIC: could not locate a valid checkpoint record at 0/47AD55F8Le mot PANIC dit que la base refuse de démarrer : elle ne se dégrade pas en silence. Un essai sur cinq a donné un message voisin, PANIC: could not find redo location ... referenced by checkpoint record. Un détail piège : la partie qui écrit la copie (tar xf) a rendu le code 0 dans les cinq essais. Avec un tube (le | entre les deux docker run), le shell ne rend que le code de la dernière commande : l'échec de la lecture se perd, et la copie défaillante a l'air réussie.
Un contrôle a montré le revers. Sur une base au repos, avec un CHECKPOINT juste avant la copie (l'ordre qui force PostgreSQL à écrire sur le disque ce qu'il garde en mémoire) et aucune écriture, la copie a démarré et affiché database system was not properly shut down; automatic recovery in progress, puis redo done, avec le même nombre de lignes. PostgreSQL a traité la copie comme une base arrêtée brutalement et l'a récupérée. C'est un coup de chance, pas une garantie : une base au repos ne se distingue de l'autre qu'au moment où l'on a besoin de la restaurer.
Une réserve s'impose : la charge de ce banc est volontairement forte, et sous une charge normale l'échec peut être moins fréquent. La conclusion tient quand même, parce qu'un tar lancé sur un volume en marche est une copie de fichiers ordinaire : elle exige que le serveur soit arrêté.
Pour une copie à froid (base arrêtée proprement) ou une sauvegarde physique avec pg_basebackup, lisez Sauvegarder et restaurer PostgreSQL et vérifiez par une restauration sur votre version. Cette leçon retient pg_dump, dont la restauration est prouvée plus bas.
Comment sauvegarder les fichiers et la configuration ?
Section intitulée « Comment sauvegarder les fichiers et la configuration ? »Les fichiers d'un volume se sauvegardent avec tar, lancé dans un conteneur qui monte le volume en lecture seule, avec le dossier de sauvegarde de l'hôte. Les fichiers d'un site ne sont pas une base : ils ne changent que quand l'application les écrit, et ici le site les sert en lecture seule.
cd ~/sauvdocker run --rm -v bibliotheque_couvertures:/src:ro -v "$PWD":/sauvegarde "$ALPINE" \ tar czf /sauvegarde/couvertures.tar.gz -C /src .ls -l couvertures.tar.gzSur la machine de mesure, l'archive pèse environ 60 000 octets (60 319 au dernier essai) : trois fichiers aléatoires de 20 000 octets ne se compressent pas, et la valeur exacte varie d'un essai à l'autre. Le fichier appartient à root, parce que tar tourne en root dans le conteneur. Vous pouvez le lire, et le supprimer avec rm, puisque le dossier est le vôtre ; dans un terminal, rm demande d'abord confirmation pour ce fichier protégé en écriture.
Pour la configuration, c'est un simple tar du dossier, côté hôte. L'archive inclut le .env et le secret.
cd ~/bibliothequetar czf ~/sauv/config.tar.gz compose.yaml .env nginx secretstar tzvf ~/sauv/config.tar.gz-rw-rw-r-- ansible/ansible 866 2026-11-03 05:47 compose.yaml-rw------- ansible/ansible 61 2026-11-03 05:47 .envdrwxrwxr-x ansible/ansible 0 2026-11-03 05:46 nginx/-rw-rw-r-- ansible/ansible 187 2026-11-03 05:47 nginx/default.confdrwxrwxr-x ansible/ansible 0 2026-11-03 05:46 secrets/-rw------- ansible/ansible 24 2026-11-03 05:47 secrets/base_mdp.txtDans la liste, tar a conservé le 600 du .env et du secret à l'intérieur de l'archive. Mais le fichier d'archive lui-même reçoit les droits que votre umask (le masque qui retire des droits à tout fichier créé) laisse : sur la machine de mesure (umask 0002), il est créé en rw-rw-r--. Ici, le dossier ~/sauv en 700 empêche les autres comptes d'y entrer, donc de lire l'archive. Si vous la déplacez ailleurs, vérifiez ses droits avec ls -l et restreignez-la avec chmod 600.
Comment restaurer sur une cible vierge ?
Section intitulée « Comment restaurer sur une cible vierge ? »En recréant le projet dans un autre dossier, dans cet ordre : configuration, volumes, fichiers, démarrage, puis base. Une cible vierge est un environnement qui ne contient encore rien de la stack : ni volume, ni conteneur, ni donnée. Ici, c'est un second projet, bibliotheque-restauree, lancé sur la même machine pour la démonstration. Sur une vraie reprise, ce serait une autre machine : copiez d'abord le dossier de sauvegarde, puis suivez les mêmes étapes. C'est aussi la raison de garder une copie hors de la machine : une sauvegarde restée sur l'hôte disparaît avec lui. Le guide Sauvegarder et restaurer Forgejo montre une copie distante chiffrée.
Le schéma montre le chemin de chaque morceau. En haut, chaque source devient un fichier de sauvegarde. En bas, les traits rouges indiquent à quelle étape chaque fichier est utilisé : la configuration d'abord, les fichiers une fois les volumes créés, la base en dernier, quand le service tourne.
Restaurez d'abord la configuration. Comme la cible tourne à côté de la source, un seul réglage change : le port publié, 8081 au lieu de 8080.
mkdir -p ~/bibliotheque-restauree && cd ~/bibliotheque-restaureetar xzf ~/sauv/config.tar.gzsed -i 's/^SITE_PORT=8080/SITE_PORT=8081/' .envcat .envBASE_UTILISATEUR=biblioBASE_NOM=bibliothequeSITE_PORT=8081Il faut ensuite que les volumes de la cible existent, et qu'ils appartiennent au projet, avant d'y poser des fichiers. docker compose create crée les volumes avec les étiquettes qui les rattachent au projet, puis le réseau et les conteneurs, sans rien démarrer.
docker compose createdocker volume ls Volume bibliotheque-restauree_donnees Creating Network bibliotheque-restauree_default Creating Volume bibliotheque-restauree_couvertures Creating Volume bibliotheque-restauree_couvertures Created Volume bibliotheque-restauree_donnees Created Network bibliotheque-restauree_default Created Container bibliotheque-restauree-site-1 Creating Container bibliotheque-restauree-base-1 Creating Container bibliotheque-restauree-site-1 Created Container bibliotheque-restauree-base-1 CreatedDRIVER VOLUME NAMElocal bibliotheque-restauree_couvertureslocal bibliotheque-restauree_donneeslocal bibliotheque_couvertureslocal bibliotheque_donneesLe préfixe du projet sépare les volumes de la cible de ceux de la source. Restaurez ensuite les fichiers dans le volume de la cible, avec le même tar, en sens inverse et le dossier de sauvegarde monté en lecture seule.
docker run --rm -v bibliotheque-restauree_couvertures:/dst -v "$HOME/sauv":/sauvegarde:ro "$ALPINE" \ tar xzf /sauvegarde/couvertures.tar.gz -C /dstDémarrez la cible et attendez que la base soit prête. Puis regardez ce qu'elle contient.
docker compose up -d --waitdocker compose exec -T base psql -U biblio -d bibliotheque -c '\dt' Container bibliotheque-restauree-site-1 Starting Container bibliotheque-restauree-base-1 Starting Container bibliotheque-restauree-site-1 Started Container bibliotheque-restauree-base-1 Started Container bibliotheque-restauree-base-1 Waiting Container bibliotheque-restauree-site-1 Waiting Container bibliotheque-restauree-site-1 Healthy Container bibliotheque-restauree-base-1 HealthyDid not find any tables.La dernière ligne dit que la base bibliotheque existe mais n'a aucune table. L'image postgres l'a créée au premier démarrage, d'après POSTGRES_DB, donc il n'y a pas de CREATE DATABASE à faire. Reste à y verser la sauvegarde.
docker compose exec -T base pg_restore -U biblio -d bibliotheque --exit-on-error --no-owner < ~/sauv/biblio.dumpecho "rc=$?"docker compose exec -T base psql -U biblio -d bibliotheque -Atf - < ~/sauv/empreinte.sqlrc=05 lignes, md5 b52719a3d3e67d9d6e6b36e8a31f2877La restauration a pris 0,12 s sur la machine de mesure. --exit-on-error arrête pg_restore à la première erreur au lieu de continuer, et --no-owner évite de rattribuer chaque objet à son propriétaire d'origine : ils appartiennent à l'utilisateur de la connexion. Comparez surtout l'empreinte : le nombre de lignes et le md5 sont ceux de la source, relevés plus haut.
Que faire de l'erreur « already exists » ?
Section intitulée « Que faire de l'erreur « already exists » ? »Rien n'est cassé : pg_restore refuse de recréer des tables qui existent déjà. Vous le voyez en relançant la même restauration, maintenant que la base est remplie. L'option --no-owner est omise ici et plus bas : ces deux commandes se jouent avec le même utilisateur biblio qui a créé les objets, donc il n'y a aucun propriétaire à rattribuer.
docker compose exec -T base pg_restore -U biblio -d bibliotheque --exit-on-error < ~/sauv/biblio.dumpecho "rc=$?"pg_restore: error: could not execute query: ERROR: relation "livres" already existsCommand was: CREATE TABLE public.livres ( id integer NOT NULL, titre text NOT NULL, auteur text NOT NULL, annee integer NOT NULL);
rc=1Le code retour vaut 1 et la requête citée est celle qui échoue : pg_restore voulait recréer la table et elle existe. Deux issues. La plus propre est de restaurer sur une base vierge, comme ci-dessus. Pour écraser une base qui existe, ajoutez --clean --if-exists : pg_restore supprime chaque objet avant de le recréer, sans erreur si l'objet est absent.
docker compose exec -T base pg_restore -U biblio -d bibliotheque --clean --if-exists --exit-on-error < ~/sauv/biblio.dumpecho "rc=$?"Cette fois le code retour est 0. Relevez l'empreinte de la cible avec la commande psql -Atf - déjà utilisée : elle doit redonner la valeur de la source. Mais --clean détruit ce qui existe dans la base visée : ne l'utilisez que sur celle que vous voulez remplacer.
Comment prouver que la restauration est complète ?
Section intitulée « Comment prouver que la restauration est complète ? »En comparant la cible à la source sur chacun des trois morceaux, et en vérifiant que l'application se comporte comme avant. L'empreinte de la base a déjà été comparée. Relevez de nouveau celle de la source, puis lisez les lignes de la cible.
docker compose -f ~/bibliotheque/compose.yaml exec -T base psql -U biblio -d bibliotheque -Atf - < ~/sauv/empreinte.sqldocker compose exec -T base psql -U biblio -d bibliotheque -c 'select id, titre, annee from livres order by id'5 lignes, md5 b52719a3d3e67d9d6e6b36e8a31f2877 id | titre | annee----+-------------------+------- 1 | Les Misérables | 1862 2 | Germinal | 1885 3 | Le Petit Prince | 1943 4 | L'Étranger | 1942 5 | Bonjour tristesse | 1954(5 rows)Les accents sont conservés. Vérifiez ensuite la séquence, le compteur qui donne le numéro des nouvelles lignes. Une restauration qui l'oublierait rejouerait des numéros déjà pris. Ajoutez une ligne, puis supprimez-la.
docker compose exec -T base psql -U biblio -d bibliotheque -Atc "insert into livres (titre,auteur,annee) values ('Candide','Voltaire',1759) returning id"docker compose exec -T base psql -U biblio -d bibliotheque -Atc "delete from livres where titre='Candide'"6INSERT 0 1DELETE 1Le nouveau livre a reçu l'identifiant 6 : la séquence a repris après le dernier numéro de la source. Pour les fichiers, comparez ce que nginx sert sur la cible avec ce que vous avez relevé sur la source, puis testez la route de santé.
for i in 1 2 3; do curl -fsS http://localhost:8081/couvertures/couverture-$i.jpg | sha256sum; done > ~/sauv/sha-cible.txtdiff ~/sauv/sha-source.txt ~/sauv/sha-cible.txtcurl -s http://localhost:8081/santecurl -s -o /dev/null -w '%{http_code}\n' http://localhost:8081/santeok200La commande diff n'affiche rien : les trois empreintes sont identiques. La route /sante répond ok avec le code 200. Il reste la configuration. docker compose config affiche le fichier tel que Compose l'interprète, variables remplacées : comparez la source et la cible. Pour la source, -f désigne son fichier depuis le dossier de la cible, et -p bibliotheque fixe explicitement son nom de projet, celui sous lequel elle tourne.
docker compose -f ~/bibliotheque/compose.yaml -p bibliotheque config > ~/sauv/cfg-source.txtdocker compose config > ~/sauv/cfg-cible.txtdiff ~/sauv/cfg-source.txt ~/sauv/cfg-cible.txt1c1< name: bibliotheque---> name: bibliotheque-restauree37c37< source: /home/ansible/bibliotheque/nginx/default.conf---> source: /home/ansible/bibliotheque-restauree/nginx/default.conf44c44< published: "8080"---> published: "8081"48c48< name: bibliotheque_default---> name: bibliotheque-restauree_default51c51< name: bibliotheque_couvertures---> name: bibliotheque-restauree_couvertures53c53< name: bibliotheque_donnees---> name: bibliotheque-restauree_donnees56,57c56,57< name: bibliotheque_base_mdp< file: /home/ansible/bibliotheque/secrets/base_mdp.txt---> name: bibliotheque-restauree_base_mdp> file: /home/ansible/bibliotheque-restauree/secrets/base_mdp.txtChaque différence s'explique : le nom du projet, les chemins absolus du dossier (le montage de nginx et le fichier du secret), le port publié, choisi pour la démonstration, et les noms préfixés par le projet (réseau, volumes, secret). Rien d'autre ne diffère : la structure est la même. Un écart que vous ne savez pas expliquer, lui, signale une configuration restaurée incomplète.
Le test est complet quand les trois lectures concordent : l'empreinte de la base, les empreintes des fichiers servis, et une configuration qui ne diffère que par ce que vous avez changé.
Que faire quand la sauvegarde ou la restauration échoue ?
Section intitulée « Que faire quand la sauvegarde ou la restauration échoue ? »Le tableau part de ce que vous observez, puis donne la cause probable et le geste qui règle le problème.
| Ce que vous observez | Cause probable | Ce que vous faites |
|---|---|---|
pg_restore: error: ... relation "..." already exists, code 1 | La base visée contient déjà les tables | Restaurez sur une base vierge, ou ajoutez --clean --if-exists si vous voulez écraser cette base |
Did not find any tables. juste après up -d --wait sur la cible | La base a été créée vide par l'image | C'est normal : lancez pg_restore |
tar rend No such file or directory sur des fichiers de pg_wal, puis la copie refuse de démarrer avec PANIC | Copie à chaud du volume d'une base en marche | Abandonnez cette copie ; sauvegardez avec pg_dump |
| La copie a l'air réussie mais la base ne démarre pas | Dans un tube, le shell rend le code de la dernière commande, pas celui de tar en lecture | Testez le code de retour de la lecture, et surtout restaurez avant de croire la sauvegarde |
| Les commandes suivantes d'un script ne s'exécutent jamais | docker compose exec lit l'entrée standard du script | Ajoutez < /dev/null si la commande ne lit rien (et gardez -T pour supprimer le pseudo-terminal) |
Des fins de ligne CRLF dans une sortie texte de pg_dump | La sortie standard est un terminal avec pseudo-terminal | Redirigez la sortie vers un fichier avec -T |
Comment tout nettoyer après la démonstration ?
Section intitulée « Comment tout nettoyer après la démonstration ? »La démonstration laisse deux projets, quatre volumes (cinq si vous avez rejoué la copie à chaud), trois images et trois dossiers. docker compose down -v supprime les conteneurs, le réseau et les volumes de chaque projet. Les images se retirent par leur digest, ce qui évite de toucher à une autre version.
cd ~/bibliotheque-restauree && docker compose down -vcd ~/bibliotheque && docker compose down -vdocker rmi postgres:18.6-alpine3.24@sha256:10a5c69a83035ab5239f5620d708520e8b28f5aa1f2b20143e6024532edadfb5 \ nginx:1.30.6-alpine@sha256:0e52bd054126cdc040b1276c0af347aeb02a41b9cfe305d6094767ac30e97949cd ~ && rm -r ~/sauv ~/bibliotheque ~/bibliotheque-restaureeLe dossier ~/sauv est supprimé par le dernier rm, y compris couvertures.tar.gz (propriété de root, mais dans un dossier qui est le vôtre ; rm -r peut demander confirmation, répondez y). Si vous avez rejoué la copie à chaud, retirez aussi son volume, copie1, avec docker volume rm copie1. Gardez l'image Alpine si vous vous en servez ailleurs, sinon retirez-la par son digest : docker rmi alpine:3.24@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a. Après cela, docker ps -a et docker volume ls ne montrent plus rien de la démonstration.
Mettre en pratique
Section intitulée « Mettre en pratique »Le lab vous confie une stack Compose qui tourne, avec une base PostgreSQL et un service qui publie des fichiers. Vous devez la sauvegarder, la remonter dans un projet neuf qui tourne à côté d'elle, puis prouver que les données servies sont les mêmes, sans rien coûter à la source.
La validation ne regarde pas les commandes que vous avez tapées. Elle relit la sauvegarde de la base pour vérifier qu'elle est logique, contrôle que les fichiers et la configuration sont archivés à part, que la cible est un projet distinct et debout sur son propre port, puis compare les empreintes de la cible à celles de la source, restée intacte. Les fichiers sont lus tels que le service web les sert, pas seulement dans le volume.
À retenir
Section intitulée « À retenir »- Une sauvegarde ne vaut que restaurée : prouvez-la par une empreinte de contrôle comparée entre la source et la cible.
- Une application Compose a trois morceaux à sauvegarder : la base (
pg_dump), les fichiers des volumes (tardans un conteneur) et la configuration du projet. - L'archive de configuration et le dump de la base sont sensibles : créez le dossier de sauvegarde en
700dès le départ. docker compose exec -T base pg_dump -Fc ... > fichierproduit une archive quepg_restore --listpermet de relire sans rien restaurer.-Tdésactive le pseudo-terminal : rediriger la sortie vers un fichier ne corrompt pas l'archive, mais une sortie vers un terminal peut ajouter desCRLF. Le piège des scripts est autre : dans un script lu sur l'entrée standard, unexecsans redirection d'entrée consomme la suite du script, et la parade est< /dev/null.- Copier à chaud avec
tarle volume d'une base n'est pas une sauvegarde : sur le banc de mesure, sous forte charge, cinq copies sur cinq ont échoué avecPANIC. Sous charge normale l'échec peut être moins fréquent, et une copie qui démarre reste un coup de chance. - Restaurez sur une cible vierge dans l'ordre : configuration,
docker compose create, fichiers,up -d --wait, puispg_restore. L'erreuralready existsse règle par une base vierge ou par--clean --if-exists, qui détruit l'existant. - Épinglez par digest les images de vos commandes de sauvegarde, y compris Alpine : sans tag,
docker run alpinetirelatest. - Si l'application écrit à la fois dans la base et dans les fichiers, arrêtez le service qui écrit pendant les deux sauvegardes, puis relancez-le.
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Score par domaine
Un domaine regroupe plusieurs guides et reçoit plusieurs questions : son score est plus fiable qu'un score par thème, qui porte souvent sur une seule question.
Bilan
Points forts
Aucun sujet réussi à 80 % sur au moins deux questions pour l’instant.
Points faibles
Aucun sujet sous le seuil de réussite.
À faire ensuite
Des indices pour retenter votre chance ?
Continuer le cours : Mettre à jour, et revenir en arrière sans perdre les données
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications