Introduction
Les services stateful comme PostgreSQL, MySQL, Redis et les stockages de fichiers exigent un stockage durable qui dépasse le cycle de vie des containers. Docker propose deux options principales :
- Volumes (gérés par Docker) : portables, sûrs par défaut, et avec un bon comportement multi-plateforme.
- Bind mounts (chemins hôtes directs) : excellents pour le développement local et l'itération rapide ; plus risqués en production sans contrôles stricts.
Ce guide explique comment choisir entre ces options, déclarer le stockage dans Docker Compose, régler les file permissions, exécuter des Docker backups et des restaurations fiables, tester localement et éviter les pièges courants en production. Il s'adresse aux opérateurs qui veulent des étapes claires et des exemples prêts à copier-coller.
Concepts clés en 3 minutes
- Named volume : stockage géré par Docker et identifié par un nom. Il réside dans le répertoire de données de Docker.
- Bind mount : projection d'un chemin spécifique de l'hôte dans le container. Vous contrôlez entièrement le chemin et son contenu.
- tmpfs : montage en mémoire qui disparaît au redémarrage ; utile pour les secrets ou caches éphémères.
- Persistence boundary : l'ensemble des répertoires dont les données doivent être conservées à travers les mises à jour et redéploiements (ex. /var/lib/postgresql/data).
Quand utiliser quoi :
- Préférez les volumes pour les bases de données et services qui doivent fonctionner de manière cohérente sur différents OS et machines.
- Utilisez les bind mounts en développement local lorsque vous souhaitez éditer des fichiers sur l'hôte et voir instantanément les changements dans le container.
Vue d'ensemble du workflow
- Identifier les répertoires de données
- Lisez la documentation de l'image ou le Dockerfile pour trouver le chemin des données (par ex. /var/lib/postgresql/data).
- Choisir le type de stockage
- Par défaut en production : named volumes.
- En dev local : bind mounts pour le code source ou les assets statiques ; volumes pour les données de base de données.
- Déclarer dans Compose
- Définissez des volumes nommés au niveau supérieur.
- Montez-les au niveau service aux bons chemins.
- Régler l'utilisateur du container et les permissions
- Faites correspondre les UID/GID entre l'hôte et le container, ou appliquez un chown des répertoires de données au build/au runtime.
- Démarrer les services et écrire un enregistrement de test
- Vérifiez que les données survivent aux redémarrages de containers et aux mises à niveau d'images.
- Sauvegarder et restaurer
- Exercez-vous avant d'en avoir besoin. Conservez les scripts dans votre contrôle de version.
- Surveiller et maintenir
- Surveillez l'espace libre, purgez prudemment les volumes inutilisés et faites tourner les sauvegardes.
Exemples Compose
Exemple : PostgreSQL avec un named volume
docker-compose.yml :
version: "3.9"
services:
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: appdb
volumes:
- db_data:/var/lib/postgresql/data
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
interval: 10s
timeout: 5s
retries: 5
volumes:
db_data: {}
Démarrer :
docker compose up -d
Confirmer la persistance :
docker exec -it $(docker compose ps -q db) psql -U app -d appdb -c "CREATE TABLE t(x int); INSERT INTO t VALUES (1); SELECT * FROM t;"
docker compose restart db
# Relancez le SELECT pour confirmer que la ligne est toujours là
Exemple : Nginx servant des fichiers statiques locaux via bind mount (dev uniquement)
version: "3.9"
services:
web:
image: nginx:1.27-alpine
volumes:
- ./public:/usr/share/nginx/html: ro
ports:
- "8080:80"
Cela offre un retour instantané pendant l'édition sur l'hôte. En production, utilisez des volumes ou un artefact d'image pour éviter les dérives de chemins et les surprises de permissions.
Exemple Dockerfile pour des permissions sûres
Exécutez les containers avec un utilisateur non-root et assurez-vous que les répertoires de données lui appartiennent.
FROM postgres:16
# Exemple générique : créer un utilisateur non-root avec un UID/GID stables
RUN groupadd -g 1001 app && useradd -u 1001 -g 1001 -m app
# Préparer un répertoire de données et en attribuer la propriété
RUN mkdir -p /data && chown -R app: app /data
USER app
# L'image postgres gère /var/lib/postgresql/data; adaptez si besoin
Notes :
- Gardez les UID/GID stables entre environnements pour éviter les Permission denied sur les bind mounts.
- Si vous devez exécuter une initialisation en root, revenez à un utilisateur non-root via USER pour l'exploitation normale.
File permissions et ownership
Problèmes courants et correctifs :
- Symptôme : Permission denied sur un bind mount.
- Vérifiez l'UID/GID du process :
docker exec -it <cid> id. - Côté hôte, ajustez la propriété :
sudo chown -R 1001:1001 ./data. - Envisagez des ACL POSIX quand plusieurs utilisateurs ont besoin d'accès :
setfacl -m u:1001: rwx ./data. - SELinux (RHEL, Fedora, CentOS) : ajoutez
:Z(privé) ou:z(partagé) aux bind mounts, ex../data:/var/lib/app: Z. - Partage de fichiers Windows/macOS : assurez-vous que le chemin est autorisé dans Docker Desktop.
Backups et restaurations
Les Docker backups doivent être scriptés et testés. Utilisez des outils conscients du moteur de base de données quand c'est possible.
Sauvegarder un named volume avec tar
Créer une archive de sauvegarde d'un volume Docker :
# Sauvegarder le volume db_data dans backups/db_data_$(date +%F).tgz
mkdir -p backups
V=backups/db_data_$(date +%F).tgz
docker run --rm \
-v db_data:/data: ro \
-v "$(pwd)":/backup \
alpine:3.19 sh -c "tar -czf /backup/$(basename \"$V\") -C /data ."
ls -lh "$V"
Restaurer vers un volume neuf :
# Arrêter le service et créer un volume propre
docker compose down
docker volume rm db_data || true
docker volume create db_data
# Restaurer depuis l'archive
docker run --rm \
-v db_data:/data \
-v "$(pwd)":/backup \
alpine:3.19 sh -c "tar -xzf /backup/db_data_YYYY-MM-DD.tgz -C /data"
# Relancer les services
docker compose up -d
Sauvegarder un bind mount avec tar
# En supposant que ./data est bind-monté dans le container
mkdir -p backups
tar -czf backups/data_$(date +%F).tgz -C ./data .
Backups logiques (instantanés cohérents)
Exemple PostgreSQL :
# Export du schéma et des données
CID=$(docker compose ps -q db)
docker exec -T "$CID" pg_dump -U app -d appdb -F c -f /tmp/appdb.dump
# Copie vers l'hôte
docker cp "$CID":/tmp/appdb.dump ./backups/appdb_$(date +%F).dump
Restauration :
CID=$(docker compose ps -q db)
# Créer une base vide si nécessaire
docker exec -T "$CID" dropdb -U app --if-exists appdb
docker exec -T "$CID" createdb -U app appdb
# Restaurer depuis le dump
cat ./backups/appdb_YYYY-MM-DD.dump | docker exec -i "$CID" pg_restore -U app -d appdb --clean
Exemple MySQL :
CID=$(docker compose ps -q db)
docker exec -T "$CID" mysqldump -uapp -psecret appdb > ./backups/mysql_appdb_$(date +%F).sql
# Restauration
cat ./backups/mysql_appdb_YYYY-MM-DD.sql | docker exec -i "$CID" mysql -uapp -psecret appdb
Conseils :
- Préférez les dumps logiques pour la cohérence sans arrêter le service.
- Pour les file stores, utilisez rsync avec checksums et excluez les chemins transitoires.
Plan pilote local
Objectif : prouver persistance, backup et restore de bout en bout avec un risque minimal.
Périmètre :
- Un container base de données (PostgreSQL), un named volume, une table avec une ligne de test, une archive de sauvegarde, une restauration dans un volume propre.
Étapes :
- Démarrer les services
docker compose up -d
- Écrire des données de test
CID=$(docker compose ps -q db)
docker exec -it "$CID" psql -U app -d appdb -c "CREATE TABLE pilot(k text); INSERT INTO pilot VALUES ('ok');"
- Vérifier la persistance au redémarrage
docker compose restart db
docker exec -it "$CID" psql -U app -d appdb -c "SELECT * FROM pilot;"
- Créer une sauvegarde du volume
mkdir -p backups
V=backups/db_data_$(date +%F).tgz
docker run --rm -v db_data:/data: ro -v "$(pwd)":/backup alpine:3.19 \
sh -c "tar -czf /backup/$(basename \"$V\") -C /data ."
- Simuler un sinistre et restaurer
docker compose down
docker volume rm db_data || true
docker volume create db_data
docker run --rm -v db_data:/data -v "$(pwd)":/backup alpine:3.19 \
sh -c "tar -xzf /backup/$(basename \"$V\") -C /data"
docker compose up -d
CID=$(docker compose ps -q db)
docker exec -it "$CID" psql -U app -d appdb -c "SELECT * FROM pilot;"
- Documenter les résultats et garder les commandes sous forme de scripts versionnés.
Critères de succès :
- Données présentes après redémarrage et après restauration.
- Aucun message de permission dans les logs des containers.
Conseils de production et pièges
- N'utilisez pas de bind mounts arbitraires en production. Préférez les named volumes pour la portabilité et des permissions prévisibles.
- Epinglez les utilisateurs des containers. Utilisez un UID/GID stable et évitez d'exécuter le process principal en root.
- Utilisez des montages en lecture seule quand c'est possible. Par exemple, montez les fichiers de configuration en
:roet ne donnez:rwqu'aux répertoires de données. - Multi-OS : les bind mounts se comportent différemment entre Linux, macOS et Windows. Les volumes évitent la plupart des surprises.
- SELinux : pensez à
:Zou:zpour les bind mounts sur hôtes SELinux. - Performances sur macOS/Windows : les bind mounts peuvent être plus lents. Envisagez des volumes ou des outils de synchronisation dédiés.
- Backups : pratiquez à la fois les sauvegardes tar de volume et les dumps logiques. Conservez au moins une copie hors hôte.
- Capacité : surveillez l'usage disque. Nettoyez avec précaution :
docker volume ls,docker volume rm, etdocker volume prune(vérifiez avant de purger).
Dépannage rapide
- Permission denied au démarrage :
- Vérifiez l'UID du container :
docker exec -it <cid> id. - Corrigez la propriété côté hôte :
sudo chown -R <uid>:<gid> <host-path>. - Vérifiez les options de montage, les flags SELinux et ro/rw.
- Les données n'ont pas persisté :
- Confirmez que le chemin de montage correspond au répertoire de données de l'app.
docker inspect <cid>et vérifiez la section Mounts.- Dans Compose, assurez-vous que le service utilise le bon named volume (pas un anonyme).
- Confusion sur les noms de volumes :
- Listez les volumes :
docker volume ls. - Inspectez :
docker volume inspect <name>pour voir le point de montage et les consommateurs. - Problèmes de chemins Windows :
- Utilisez des chemins absolus pour les bind mounts et assurez-vous que le lecteur est partagé dans Docker Desktop.
Commandes Docker utiles
- Créer et inspecter des volumes :
docker volume create mydata
docker volume inspect mydata
- Exécuter avec un named volume :
docker run -d --name app -v mydata:/var/lib/app myimage: tag
- Exécuter avec un bind mount (dev) :
docker run -d --name web -v "$(pwd)/public:/usr/share/nginx/html: ro" -p 8080:80 nginx: alpine
- Copier ponctuellement un fichier depuis un container (pas une stratégie de backup) :
docker cp <cid>:/path/in/container ./local/path
Conclusion
Pour les services stateful, par défaut utilisez les named volumes en production, réservez les bind mounts au développement, et exécutez avec des utilisateurs explicites et des permissions correctes. Déclarez clairement vos volumes dans Docker Compose, vérifiez la persistance en local, et scriptz les sauvegardes et restaurations avant le déploiement. Démarrez avec un pilote restreint que vous pouvez examiner de bout en bout, puis étendez à d'autres services une fois le workflow validé.