Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

Par mis à jour le

Améliorer cette page

Logo Docker

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.

  • 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 -Fc et lire le contenu de l'archive avec pg_restore --list.
  • Expliquer ce que -T change pour docker compose exec.
  • Expliquer pourquoi la copie à chaud du volume d'une base n'est pas une sauvegarde.
  • Sauvegarder les fichiers d'un volume avec tar et 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.

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.

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.

Fenêtre de terminal
mkdir -p ~/bibliotheque/nginx ~/bibliotheque/secrets
mkdir -m 700 -p ~/sauv
cd ~/bibliotheque
services:
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:ro
secrets:
base_mdp:
file: ./secrets/base_mdp.txt
volumes:
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.

Fenêtre de terminal
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";
}
}
EOF
cat > .env <<'EOF'
BASE_UTILISATEUR=biblio
BASE_NOM=bibliotheque
SITE_PORT=8080
EOF
printf 'mdp-de-demo-bibliotheque' > secrets/base_mdp.txt
chmod 600 .env secrets/base_mdp.txt
docker compose up -d --wait

Remplissez 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.

Fenêtre de terminal
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);
EOF

Puis 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.

Fenêtre de terminal
ALPINE=alpine:3.24@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a
docker 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.

Fenêtre de terminal
cat > ~/sauv/empreinte.sql <<'EOF'
select count(*)||' lignes, md5 '||md5(string_agg(id||titre||auteur||annee,'|' order by id)) from livres
EOF
docker compose exec -T base psql -U biblio -d bibliotheque -Atf - < ~/sauv/empreinte.sql
for i in 1 2 3; do curl -fsS http://localhost:8080/couvertures/couverture-$i.jpg | sha256sum; done > ~/sauv/sha-source.txt
cat ~/sauv/sha-source.txt
5 lignes, md5 b52719a3d3e67d9d6e6b36e8a31f2877

Le 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 compteOù ça vitComment le sauvegarderSensibilité
Les données de la baseLe volume donnees, géré par PostgreSQLpg_dump lancé dans le conteneurContient toutes les données métier
Les fichiers de l'applicationLe volume couverturestar lancé dans un conteneur qui monte le volumeSelon la nature des fichiers
La configuration du projetcompose.yaml, .env, nginx/, secrets/ dans le dossier du projettar du dossier, côté hôteContient 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.

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.

Fenêtre de terminal
cd ~/sauv
docker compose -f ~/bibliotheque/compose.yaml exec -T base pg_dump -Fc -U biblio bibliotheque > biblio.dump
ls -l biblio.dump

Sur 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.

-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.

Fenêtre de 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 biblio
219; 1259 16385 SEQUENCE public livres_id_seq biblio
3481; 0 0 SEQUENCE OWNED BY public livres_id_seq biblio
3323; 2604 16389 DEFAULT public livres id biblio
3474; 0 16386 TABLE DATA public livres biblio
3482; 0 0 SEQUENCE SET public livres_id_seq biblio
3325; 2606 16397 CONSTRAINT public livres livres_pkey biblio

L'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é.

Fenêtre de terminal
docker run --rm -v bibliotheque_donnees:/src:ro "$ALPINE" tar cf - -C /src . \
| docker run --rm -i -v copie1:/dst "$ALPINE" tar xf - -C /dst

Cinq 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 directory
tar: ./18/docker/pg_wal/00000001000000000000004A: No such file or directory

Le 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 record
2026-11-03 05:48:59.621 UTC [31] PANIC: could not locate a valid checkpoint record at 0/47AD55F8

Le 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.

Fenêtre de terminal
cd ~/sauv
docker run --rm -v bibliotheque_couvertures:/src:ro -v "$PWD":/sauvegarde "$ALPINE" \
tar czf /sauvegarde/couvertures.tar.gz -C /src .
ls -l couvertures.tar.gz

Sur 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.

Fenêtre de terminal
cd ~/bibliotheque
tar czf ~/sauv/config.tar.gz compose.yaml .env nginx secrets
tar 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 .env
drwxrwxr-x ansible/ansible 0 2026-11-03 05:46 nginx/
-rw-rw-r-- ansible/ansible 187 2026-11-03 05:47 nginx/default.conf
drwxrwxr-x ansible/ansible 0 2026-11-03 05:46 secrets/
-rw------- ansible/ansible 24 2026-11-03 05:47 secrets/base_mdp.txt

Dans 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.

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.

Schéma en deux temps. En haut, trois sources sont sauvegardées : le dossier du projet devient config.tar.gz par tar, le volume des couvertures devient couvertures.tar.gz par tar, la base PostgreSQL devient biblio.dump par pg_dump. En bas, une chaîne de cinq étapes sur la cible vierge : extraire la configuration, docker compose create, remettre les fichiers, docker compose up -d --wait, puis pg_restore. config.tar.gz alimente la première étape, couvertures.tar.gz la troisième et biblio.dump la cinquième.tartarpg_dumpDossier du projetconfig.tar.gzVolume des couverturescouvertures.tar.gzBase PostgreSQLbiblio.dump1 · Extraire la configuration2 · docker compose create3 · Remettre les fichiers4 · docker compose up -d --wait5 · pg_restore
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

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.

Fenêtre de terminal
mkdir -p ~/bibliotheque-restauree && cd ~/bibliotheque-restauree
tar xzf ~/sauv/config.tar.gz
sed -i 's/^SITE_PORT=8080/SITE_PORT=8081/' .env
cat .env
BASE_UTILISATEUR=biblio
BASE_NOM=bibliotheque
SITE_PORT=8081

Il 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.

Fenêtre de terminal
docker compose create
docker 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 Created
DRIVER VOLUME NAME
local bibliotheque-restauree_couvertures
local bibliotheque-restauree_donnees
local bibliotheque_couvertures
local bibliotheque_donnees

Le 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.

Fenêtre de terminal
docker run --rm -v bibliotheque-restauree_couvertures:/dst -v "$HOME/sauv":/sauvegarde:ro "$ALPINE" \
tar xzf /sauvegarde/couvertures.tar.gz -C /dst

Démarrez la cible et attendez que la base soit prête. Puis regardez ce qu'elle contient.

Fenêtre de terminal
docker compose up -d --wait
docker 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 Healthy
Did 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.

Fenêtre de terminal
docker compose exec -T base pg_restore -U biblio -d bibliotheque --exit-on-error --no-owner < ~/sauv/biblio.dump
echo "rc=$?"
docker compose exec -T base psql -U biblio -d bibliotheque -Atf - < ~/sauv/empreinte.sql
rc=0
5 lignes, md5 b52719a3d3e67d9d6e6b36e8a31f2877

La 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.

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.

Fenêtre de terminal
docker compose exec -T base pg_restore -U biblio -d bibliotheque --exit-on-error < ~/sauv/biblio.dump
echo "rc=$?"
pg_restore: error: could not execute query: ERROR: relation "livres" already exists
Command was: CREATE TABLE public.livres (
id integer NOT NULL,
titre text NOT NULL,
auteur text NOT NULL,
annee integer NOT NULL
);
rc=1

Le 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.

Fenêtre de terminal
docker compose exec -T base pg_restore -U biblio -d bibliotheque --clean --if-exists --exit-on-error < ~/sauv/biblio.dump
echo "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.

Fenêtre de terminal
docker compose -f ~/bibliotheque/compose.yaml exec -T base psql -U biblio -d bibliotheque -Atf - < ~/sauv/empreinte.sql
docker 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.

Fenêtre de terminal
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'"
6
INSERT 0 1
DELETE 1

Le 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é.

Fenêtre de terminal
for i in 1 2 3; do curl -fsS http://localhost:8081/couvertures/couverture-$i.jpg | sha256sum; done > ~/sauv/sha-cible.txt
diff ~/sauv/sha-source.txt ~/sauv/sha-cible.txt
curl -s http://localhost:8081/sante
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8081/sante
ok
200

La 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.

Fenêtre de terminal
docker compose -f ~/bibliotheque/compose.yaml -p bibliotheque config > ~/sauv/cfg-source.txt
docker compose config > ~/sauv/cfg-cible.txt
diff ~/sauv/cfg-source.txt ~/sauv/cfg-cible.txt
1c1
< name: bibliotheque
---
> name: bibliotheque-restauree
37c37
< source: /home/ansible/bibliotheque/nginx/default.conf
---
> source: /home/ansible/bibliotheque-restauree/nginx/default.conf
44c44
< published: "8080"
---
> published: "8081"
48c48
< name: bibliotheque_default
---
> name: bibliotheque-restauree_default
51c51
< name: bibliotheque_couvertures
---
> name: bibliotheque-restauree_couvertures
53c53
< name: bibliotheque_donnees
---
> name: bibliotheque-restauree_donnees
56,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.txt

Chaque 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 observezCause probableCe que vous faites
pg_restore: error: ... relation "..." already exists, code 1La base visée contient déjà les tablesRestaurez 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 cibleLa base a été créée vide par l'imageC'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 PANICCopie à chaud du volume d'une base en marcheAbandonnez cette copie ; sauvegardez avec pg_dump
La copie a l'air réussie mais la base ne démarre pasDans un tube, le shell rend le code de la dernière commande, pas celui de tar en lectureTestez 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 jamaisdocker compose exec lit l'entrée standard du scriptAjoutez < /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_dumpLa sortie standard est un terminal avec pseudo-terminalRedirigez la sortie vers un fichier avec -T

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.

Fenêtre de terminal
cd ~/bibliotheque-restauree && docker compose down -v
cd ~/bibliotheque && docker compose down -v
docker rmi postgres:18.6-alpine3.24@sha256:10a5c69a83035ab5239f5620d708520e8b28f5aa1f2b20143e6024532edadfb5 \
nginx:1.30.6-alpine@sha256:0e52bd054126cdc040b1276c0af347aeb02a41b9cfe305d6094767ac30e97949
cd ~ && rm -r ~/sauv ~/bibliotheque ~/bibliotheque-restauree

Le 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.

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.

  1. Une sauvegarde ne vaut que restaurée : prouvez-la par une empreinte de contrôle comparée entre la source et la cible.
  2. Une application Compose a trois morceaux à sauvegarder : la base (pg_dump), les fichiers des volumes (tar dans un conteneur) et la configuration du projet.
  3. L'archive de configuration et le dump de la base sont sensibles : créez le dossier de sauvegarde en 700 dès le départ.
  4. docker compose exec -T base pg_dump -Fc ... > fichier produit une archive que pg_restore --list permet de relire sans rien restaurer.
  5. -T désactive le pseudo-terminal : rediriger la sortie vers un fichier ne corrompt pas l'archive, mais une sortie vers un terminal peut ajouter des CRLF. Le piège des scripts est autre : dans un script lu sur l'entrée standard, un exec sans redirection d'entrée consomme la suite du script, et la parade est < /dev/null.
  6. Copier à chaud avec tar le volume d'une base n'est pas une sauvegarde : sur le banc de mesure, sous forte charge, cinq copies sur cinq ont échoué avec PANIC. Sous charge normale l'échec peut être moins fréquent, et une copie qui démarre reste un coup de chance.
  7. Restaurez sur une cible vierge dans l'ordre : configuration, docker compose create, fichiers, up -d --wait, puis pg_restore. L'erreur already exists se règle par une base vierge ou par --clean --if-exists, qui détruit l'existant.
  8. Épinglez par digest les images de vos commandes de sauvegarde, y compris Alpine : sans tag, docker run alpine tire latest.
  9. 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.

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

8 questions
8 min.
70% requis

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

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn