Docker Compose ne remplace ni une stratégie de sauvegarde, ni une politique de mise à jour, ni le durcissement du VPS. En revanche, il fournit un modèle déclaratif qui rend une stack reproductible : services, images, réseaux, volumes, ports et politiques de redémarrage sont décrits dans un fichier que l'on peut relire, tester et versionner.
Ce guide ne reprend pas l'installation de Docker. Le moteur et le plugin Compose doivent déjà être opérationnels selon le guide Installer Docker sur un VPS Linux. Le piège Docker/UFW y est également traité. Ici, l'objectif est d'exploiter durablement une application et sa base de données sur un VPS.
Le cas pratique utilise les images officielles WordPress et MariaDB. Les principes s'appliquent ensuite à Bitwarden, Pi-hole, Jellyfin, Portainer et à la plupart des stacks composées d'une application et d'un service de données.
Les trois conventions à appliquer aujourd'hui
1. Ne pas ajouter de clé version:
Un fichier moderne commence directement par services:. La propriété de premier niveau version est conservée uniquement pour la compatibilité. Docker la qualifie d'obsolète, précise qu'elle est seulement informative et indique que son utilisation produit un avertissement. Compose valide de toute façon le fichier avec la spécification la plus récente. Ajouter version: "3.8" ne sélectionne donc pas un moteur de compatibilité particulier.
Source : docs.docker.com, propriété version
2. Utiliser docker compose, en deux mots
Les commandes de ce guide utilisent le plugin intégré à la CLI Docker :
docker compose up -d La commande docker-compose, avec un tiret, désigne l'ancien outil v1 écrit en Python. Compose v2, annoncé en 2020, est écrit en Go et s'invoque avec docker compose. Compose v5, publié en 2025, est fonctionnellement identique à v2 côté CLI : sa principale nouveauté est un SDK Go officiel, et la numérotation a sauté directement à 5 pour éviter la confusion avec les anciens formats de fichier étiquetés « v2 » et « v3 ». Dans tous les cas, la forme à utiliser reste docker compose.
Source : docs.docker.com, historique de Docker Compose
3. Nommer le fichier compose.yaml
Le nom canonique est compose.yaml. Les variantes compose.yml, docker-compose.yaml et docker-compose.yml restent reconnues pour compatibilité, mais Docker recommande le nom canonique et le privilégie si plusieurs variantes sont présentes dans le même répertoire.
Source : docs.docker.com, modèle d'application Compose
Architecture retenue
La stack respecte quatre frontières simples :
- le service
appest le seul à publier un port sur l'hôte ; - ce port écoute uniquement sur
127.0.0.1, afin qu'un reverse proxy installé sur le VPS soit le seul point d'entrée public ; - le service
dbne publie aucun port et reste joignable uniquement par son nom DNSdbsur le réseau privébackend; - les données survivent à la recréation des conteneurs grâce à deux volumes nommés.
Compose crée un DNS interne pour les services d'un même réseau. L'application se connecte donc à db:3306, pas à une adresse IP de conteneur. Une IP de conteneur est éphémère et ne doit jamais être enregistrée dans la configuration.
Préparer un répertoire d'exploitation
Une stack doit avoir un emplacement connu, des permissions restrictives et une structure que l'on peut sauvegarder.
sudo install -d -m 0750 -o "$USER" -g "$USER" /opt/stacks/wordpress-prod
cd /opt/stacks/wordpress-prod
umask 077
mkdir -p secrets backups
openssl rand -base64 48 > secrets/db_password.txt
openssl rand -base64 48 > secrets/db_root_password.txt
chmod 600 secrets/*.txt
chmod 700 secrets backups Le compte qui lance Compose doit pouvoir lire les deux fichiers. Ils restent en clair sur le disque du VPS : les secrets Compose locaux sont fournis au conteneur sous forme de fichiers montés dans /run/secrets, mais leur source demeure un fichier local à protéger. Ce mécanisme limite leur exposition aux seuls services autorisés et évite de les placer directement dans les variables d'environnement. Il ne remplace pas un gestionnaire de secrets centralisé.
Source : docs.docker.com, gestion des secrets avec Compose
Le fichier compose.yaml, commenté ligne par ligne
Le fichier commence volontairement par services:. Il ne contient pas de clé version:.
services:
# Service HTTP de l'application.
app:
# La référence d'image vient de .env et doit être validée avant production.
image: "${WORDPRESS_IMAGE:?Définissez WORDPRESS_IMAGE dans .env}"
# Redémarre au reboot, sauf si un administrateur a volontairement arrêté le service.
restart: unless-stopped
# Évite les processus zombies si l'image ne les récolte pas elle-même.
init: true
# La base doit être déclarée saine avant la création de l'application.
depends_on:
db:
condition: service_healthy
# Le port 80 du conteneur écoute seulement sur la boucle locale du VPS.
ports:
- "${APP_BIND_IP:-127.0.0.1}:${APP_PORT:-8080}:80"
# Les valeurs non sensibles peuvent être interpolées depuis .env.
environment:
WORDPRESS_DB_HOST: "db:3306"
WORDPRESS_DB_NAME: "${DB_NAME:?Définissez DB_NAME dans .env}"
WORDPRESS_DB_USER: "${DB_USER:?Définissez DB_USER dans .env}"
# L'image officielle sait lire le mot de passe depuis un fichier.
WORDPRESS_DB_PASSWORD_FILE: /run/secrets/db_password
# Le contenu applicatif persiste hors du cycle de vie du conteneur.
volumes:
- wordpress_data:/var/www/html
# Seul le mot de passe nécessaire à l'application lui est accordé.
secrets:
- db_password
# L'application reçoit le trafic frontal et dialogue avec la base en privé.
networks:
- frontend
- backend
# Une limite locale évite que les journaux remplissent le disque du VPS.
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# Laisse le temps au serveur HTTP de fermer ses connexions proprement.
stop_grace_period: 30s
# Service de base de données, sans port publié sur l'hôte.
db:
# La référence d'image est, elle aussi, gérée dans .env.
image: "${MARIADB_IMAGE:?Définissez MARIADB_IMAGE dans .env}"
# Un arrêt manuel reste respecté après le redémarrage du démon ou du VPS.
restart: unless-stopped
# Initialise la base et l'utilisateur lors du tout premier démarrage seulement.
environment:
MARIADB_DATABASE: "${DB_NAME:?Définissez DB_NAME dans .env}"
MARIADB_USER: "${DB_USER:?Définissez DB_USER dans .env}"
MARIADB_PASSWORD_FILE: /run/secrets/db_password
MARIADB_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
# Les fichiers de données MariaDB résident dans un volume nommé.
volumes:
- mariadb_data:/var/lib/mysql
# La base reçoit les deux secrets, contrairement à l'application.
secrets:
- db_password
- db_root_password
# Ce script est fourni par l'image officielle MariaDB.
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
# La base n'appartient qu'au réseau privé interne.
networks:
- backend
# Même politique de rotation locale que pour l'application.
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# Une base peut avoir besoin de temps pour terminer ses écritures.
stop_grace_period: 1m
# Déclaration des stockages persistants gérés par Docker.
volumes:
wordpress_data:
mariadb_data:
# Déclaration des deux zones réseau de la stack.
networks:
frontend:
driver: bridge
backend:
driver: bridge
# Empêche ce réseau de fournir une connectivité externe directe.
internal: true
# Chaque secret local provient d'un fichier distinct sur l'hôte.
secrets:
db_password:
file: ./secrets/db_password.txt
db_root_password:
file: ./secrets/db_root_password.txt L'image officielle WordPress prend en charge la convention WORDPRESS_DB_PASSWORD_FILE. L'image officielle MariaDB accepte les variantes MARIADB_*_FILE et fournit healthcheck.sh. Ces comportements appartiennent aux images, pas à Compose lui-même. Il faut donc vérifier la documentation de chaque image avant de transposer ce modèle à une autre application.
Sources : image officielle WordPress, image officielle MariaDB, référence MariaDB de healthcheck.sh
depends_on avec condition: service_healthy empêche Compose de créer l'application avant que le contrôle de santé de la base réussisse. Cela améliore le démarrage initial, mais ne dispense pas l'application de savoir réessayer une connexion perdue en cours d'exploitation. Voir l'ordre de démarrage dans Compose.
Le fichier .env : configuration, pas coffre-fort
Compose lit automatiquement un fichier .env situé à côté de compose.yaml et l'utilise pour interpoler les expressions ${VARIABLE}.
# Nom stable du projet. Il influence notamment le nom réel des volumes et réseaux.
COMPOSE_PROJECT_NAME=wordpress-prod
# Le service reste local au VPS et sera publié par un reverse proxy.
APP_BIND_IP=127.0.0.1
APP_PORT=8080
# Valeurs non sensibles transmises aux deux services.
DB_NAME=wordpress
DB_USER=wordpress
# Sélecteurs lisibles pour préparer le premier pull.
# Avant la production, remplacez-les par les digests validés comme expliqué ci-dessous.
WORDPRESS_IMAGE=wordpress:apache
MARIADB_IMAGE=mariadb:lts Source : docs.docker.com, interpolation des variables Compose
Les tags d'image sont mutables : un éditeur peut faire pointer le même tag vers un contenu différent. Pour un déploiement strictement reproductible, validez l'image puis fixez sa référence avec un digest nom@sha256:.... Une mise à jour du digest devient alors une modification volontaire et révisable. Voir les bonnes pratiques Docker sur le verrouillage par digest.
Pour obtenir les références immuables correspondant aux images que vous venez de tester :
docker pull wordpress:apache
docker pull mariadb:lts
docker image inspect wordpress:apache --format '{{index .RepoDigests 0}}'
docker image inspect mariadb:lts --format '{{index .RepoDigests 0}}' Recopiez chaque résultat complet dans la variable correspondante de .env. Après ce verrouillage, docker compose pull ne changera pas silencieusement de contenu : la mise à jour passera par une modification explicite du digest.
Le digest verrouille l'image, pas l'état du volume. C'est particulièrement important dans cet exemple : l'image officielle WordPress indique que les mises à jour automatiques de WordPress peuvent modifier le contenu de /var/www/html après le déploiement. Une politique de mise à jour reproductible doit donc couvrir à la fois les images et le mécanisme de mise à jour propre à l'application.
Même si l'exemple ne place aucun mot de passe dans .env, ce fichier ne doit pas être committé. Dans beaucoup de projets, il finit par recevoir un jeton, une URL privée ou une valeur propre à la production. Un secret écrit directement dans compose.yaml ou .env, puis poussé dans Git, reste dans l'historique même après suppression dans le dernier commit.
Autre piège important : les variables MARIADB_DATABASE, MARIADB_USER et MARIADB_*_PASSWORD_FILE servent à initialiser un répertoire de données vide. L'image officielle précise qu'elles ne reconfigurent pas une base déjà présente. Remplacer le contenu d'un fichier secret ne change donc pas automatiquement le mot de passe enregistré dans MariaDB. Une rotation doit modifier le compte dans la base, mettre à jour le secret consommé par l'application, puis redémarrer ou recharger les composants selon leur documentation.
Créez un fichier .gitignore :
.env
secrets/
backups/ Vous pouvez versionner un .env.example contenant uniquement des noms de variables et des valeurs factices. Protégez le vrai fichier :
chmod 600 .env Attention également à docker compose config sans option : cette commande affiche la configuration résolue. Elle peut révéler des valeurs interpolées si vous avez placé des secrets dans des variables. docker compose config --quiet valide sans imprimer la configuration. Voir la référence de docker compose config.
Volumes nommés et bind mounts : ne pas les confondre
Un conteneur est remplaçable. Toute donnée écrite uniquement dans sa couche interne disparaît avec lui. La persistance doit être déclarée explicitement.
| Critère | Volume nommé | Bind mount |
|---|---|---|
| Source | Objet géré par Docker | Chemin explicite du VPS, par exemple /srv/app/config |
| Cas conseillé | Données générées par l'application ou la base | Fichier de configuration administré depuis l'hôte, certificat, contenu que l'hôte doit manipuler directement |
| Portabilité | Faiblement couplé à l'arborescence du VPS | Dépend du chemin, des permissions et parfois du contexte SELinux de l'hôte |
| Risque principal | Oublier qu'il existe hors du dossier de la stack | Monter le mauvais chemin, masquer le contenu déjà présent dans l'image ou donner un accès en écriture trop large |
| Sauvegarde | À exporter explicitement | À inclure explicitement dans la sauvegarde du chemin hôte |
Docker recommande les volumes pour les données persistantes produites par les conteneurs. Un volume survit à la suppression du conteneur qui l'utilisait. Un bind mount est préférable lorsqu'un administrateur ou un outil de l'hôte doit modifier directement un fichier.
Dans cette stack :
wordpress_dataconserve/var/www/html;mariadb_dataconserve/var/lib/mysql;./secrets/*.txtsont des fichiers de l'hôte montés séparément dans les services autorisés.
Où vit réellement un volume nommé ?
Le nom mariadb_data est le nom logique du modèle Compose. Avec COMPOSE_PROJECT_NAME=wordpress-prod, Docker crée généralement un nom tel que wordpress-prod_mariadb_data. Ne construisez pas vos scripts à partir de cette supposition. Compose applique des labels aux volumes, et docker volume inspect retourne leur point de montage réel :
docker volume ls \
--filter label=com.docker.compose.project=wordpress-prod
DB_VOLUME="$(docker volume ls \
--filter label=com.docker.compose.project=wordpress-prod \
--filter label=com.docker.compose.volume=mariadb_data \
--format '{{.Name}}')"
docker volume inspect "$DB_VOLUME" --format '{{.Mountpoint}}' Avec le pilote local et un démon Docker root classique, le chemin se trouve souvent sous /var/lib/docker/volumes/. Ce n'est pas une garantie : le mode rootless, un data-root personnalisé ou un pilote distant changent cet emplacement. La réponse de docker volume inspect fait foi. N'éditez pas directement les fichiers internes d'une base dans ce répertoire.
Les commandes qui détruisent les données
docker compose down retire les conteneurs et les réseaux de la stack, mais conserve les volumes nommés par défaut. L'option -v demande aussi leur suppression.
N'utilisez jamais
docker compose down -vcomme commande de mise à jour ou de dépannage. Vérifiez une sauvegarde restaurable avant toute suppression volontaire d'un volume.
De même, docker volume prune supprime les volumes considérés comme inutilisés. Un volume de production devient « inutilisé » dès que son conteneur est supprimé, même si ses données restent indispensables. Voir le comportement de docker compose down.
Publication des ports : la différence entre local et public
Ces deux lignes ne sont pas équivalentes :
ports:
- "8080:80" Sans adresse d'hôte, Docker publie le port sur toutes les adresses du VPS, en pratique 0.0.0.0 et, selon la configuration, IPv6. Le service peut alors devenir joignable depuis Internet si le routage et les règles réseau le permettent.
ports:
- "127.0.0.1:8080:80" Ici, le port n'écoute que sur la boucle locale IPv4. C'est le bon choix lorsque Nginx ou Apache tourne directement sur le VPS et relaie les requêtes vers http://127.0.0.1:8080. Docker documente explicitement que l'absence d'adresse publie sur toutes les adresses et qu'une liaison à 127.0.0.1 limite l'accès à l'hôte.
Source : docs.docker.com, publication des ports
Vérifiez le résultat, ne vous contentez pas de relire le YAML :
docker compose ps
ss -lntp | grep ':8080'
curl --fail --head http://127.0.0.1:8080 La base n'a pas de section ports. La directive expose n'est pas nécessaire pour que app joigne db sur le même réseau Compose.
Docker gère ses propres règles de pare-feu et un port publié peut contourner le chemin de filtrage attendu avec UFW. Ce point est détaillé dans Sécuriser un VPS Linux : la checklist complète et dans le guide d'installation Docker cité en prérequis. Les trois règles cohérentes sont : ne pas publier ce qui n'a pas à l'être, lier à 127.0.0.1 derrière un proxy local, puis vérifier l'écoute réelle.
Si le reverse proxy est lui-même un conteneur, 127.0.0.1 désigne ce conteneur, pas l'hôte ni l'application. Connectez alors le proxy et l'application à un réseau Docker partagé, sans publier le port applicatif sur Internet. Pour un proxy installé sur l'hôte, consultez Héberger plusieurs sites sur un VPS avec Nginx, puis Certbot : installer un SSL Let's Encrypt sur VPS.
always ou unless-stopped au redémarrage du VPS ?
Les deux politiques relancent un conteneur après une défaillance et lors du retour du démon Docker. La différence apparaît après un arrêt administratif :
alwaysrelance un conteneur arrêté manuellement lors du redémarrage du démon Docker ;unless-stoppedrespecte cet arrêt manuel, y compris après le redémarrage du démon ou du VPS.
Pour une stack administrée manuellement, unless-stopped évite de remettre en ligne au prochain reboot un service que l'on avait volontairement arrêté pour maintenance. always convient lorsqu'un service doit revenir systématiquement et que ce comportement est explicitement recherché. La politique ne remplace pas un contrôle de santé : elle réagit à l'arrêt du processus principal, pas à une application vivante mais bloquée.
Source : docs.docker.com, politiques de redémarrage
Valider et démarrer la stack
Commencez par vérifier la CLI :
docker compose version
docker info Puis validez et déployez depuis le répertoire de la stack :
cd /opt/stacks/wordpress-prod
docker compose config --quiet
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=100 --timestamps docker compose pull télécharge les images mais ne remplace pas les conteneurs en cours d'exécution. docker compose up -d compare ensuite l'état demandé à l'état existant, crée ce qui manque et recrée les services dont l'image ou la configuration a changé. Les volumes montés sont conservés pendant cette recréation. Voir le comportement de docker compose up.
Après le premier démarrage, contrôlez aussi l'exposition depuis une autre machine. Un test local réussi ne prouve pas que le port 8080 est inaccessible publiquement.
Mettre à jour sans perdre les données
Une mise à jour de production est une petite procédure de changement, pas une commande lancée à l'aveugle.
Avant la mise à jour
- Lisez les notes de version de l'application et de la base, notamment les migrations et les chemins de mise à niveau pris en charge.
- Vérifiez l'espace disque et les inodes avec
df -hetdf -i. - Réalisez une sauvegarde et testez régulièrement sa restauration.
- Notez les références d'images actuellement déployées avec
docker compose images. - Modifiez dans
.envles tags précis ou digests seulement après validation. - Exécutez
docker compose config --quiet.
Appliquer la mise à jour
cd /opt/stacks/wordpress-prod
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --since=10m --timestamps Ce qui peut être recréé :
- le conteneur
appsi sa référence d'image ou sa configuration change ; - le conteneur
dbsi sa référence d'image ou sa configuration change ; - les réseaux si leur définition change.
Ce qui est conservé :
- le contenu des volumes nommés
wordpress_dataetmariadb_data; - les fichiers du VPS utilisés comme bind mounts ou sources de secrets ;
- les images précédentes tant qu'elles n'ont pas été supprimées.
docker compose restart ne suffit pas après une modification de compose.yaml ou des variables : cette commande redémarre les conteneurs existants sans appliquer la nouvelle configuration. Utilisez docker compose up -d. Voir la référence de docker compose restart.
Une recréation peut provoquer une courte interruption. Compose sur un VPS unique ne promet pas une mise à jour sans coupure. Pour une application critique, prévoyez une stratégie adaptée à l'application, une seconde instance ou une plateforme d'orchestration.
Revenir en arrière
Pour un rollback applicatif, remettez dans .env la référence d'image précédente, puis relancez pull et up -d. Cette méthode n'annule pas une migration de schéma. Si la nouvelle version a modifié la base de façon incompatible, seul un plan de retour documenté par l'éditeur ou une restauration validée permet de revenir proprement. C'est la raison pour laquelle la sauvegarde précède la mise à jour.
Sauvegarder la stack, pas seulement son YAML
Copier compose.yaml ne sauvegarde aucune donnée contenue dans les volumes. À l'inverse, copier à chaud les fichiers internes de MariaDB ne garantit pas une base cohérente. Pour ce cas, on combine :
- un dump logique de MariaDB, produit par l'outil de la base ;
- une archive du volume applicatif pendant que l'application est arrêtée ;
- une copie de
compose.yaml,.envet de la liste des références d'images ; - une sauvegarde séparée et chiffrée des fichiers secrets ;
- une copie hors du VPS, avec rétention et tests de restauration.
MariaDB documente mariadb-dump dans son guide officiel de sauvegarde des conteneurs.
Exemple de sauvegarde cohérente
Exécutez ce bloc avec Bash depuis le répertoire de la stack. L'application est arrêtée pour empêcher les écritures pendant le dump et l'archive. La base reste active le temps du dump logique. Le trap redémarre l'application même si une commande échoue.
set -Eeuo pipefail
cd /opt/stacks/wordpress-prod
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
DEST="backups/$STAMP"
install -d -m 0700 "$DEST"
docker compose config --quiet
docker compose stop app
trap 'docker compose start app' EXIT
docker compose exec -T db sh -c '
exec mariadb-dump \
--user=root \
--password="$(cat /run/secrets/db_root_password)" \
--single-transaction \
--routines \
--triggers \
--events \
"$MARIADB_DATABASE"
' | gzip -9 > "$DEST/database.sql.gz"
docker compose run --rm --no-deps -T \
--entrypoint tar app \
-C /var/www/html -czf - . \
> "$DEST/wordpress-data.tar.gz"
cp compose.yaml .env "$DEST/"
docker compose images > "$DEST/images.txt"
gzip -t "$DEST/database.sql.gz"
tar -tzf "$DEST/wordpress-data.tar.gz" > /dev/null
(cd "$DEST" && sha256sum database.sql.gz wordpress-data.tar.gz compose.yaml .env images.txt > SHA256SUMS)
docker compose start app
trap - EXIT Ce script ne copie volontairement pas secrets/ dans la même archive. Exportez ces fichiers vers un coffre ou une sauvegarde chiffrée avec un contrôle d'accès distinct. Une sauvegarde présente uniquement sur le même VPS disparaît avec le disque, le compte ou l'incident qui détruit la production.
Le dump avec --single-transaction est adapté aux tables transactionnelles. Une application utilisant d'autres moteurs ou plusieurs systèmes de stockage exige une procédure de cohérence propre à l'éditeur. Pour de gros volumes et des objectifs de reprise stricts, étudiez aussi les outils de sauvegarde physique, la réplication et les instantanés coordonnés.
Si vous ne souhaitez pas scripter et superviser vous-même cette chaîne, l'option de sauvegardes automatiques proposée avec nos VPS Linux réalise une copie quotidienne dans un datacenter distinct, avec un historique rotatif et une restauration en un clic. Elle ne dispense pas de tester une restauration applicative, mais elle couvre le cas où le VPS lui-même est perdu.
Tester une restauration
Un fichier dont la restauration n'a jamais été testée n'est qu'une hypothèse de sauvegarde. Le test doit être réalisé dans un projet isolé, avec des volumes vides et les mêmes références d'images que la sauvegarde :
- copiez
compose.yaml,.envet les secrets de test sur un autre VPS ou dans un répertoire isolé ; - changez
COMPOSE_PROJECT_NAMEet le port hôte pour ne pas toucher à la production ; - créez le volume applicatif en lançant une commande ponctuelle, puis extrayez l'archive ;
- démarrez uniquement
db, attendez son étathealthy, puis importez le dump dans la base vide ; - démarrez
app, vérifiez les fonctions métier et consignez la durée réelle de reprise.
Exemple d'import dans une base de test déjà initialisée et vide :
gzip -dc backups/DATE/database.sql.gz | \
docker compose exec -T db sh -c '
exec mariadb \
--user=root \
--password="$(cat /run/secrets/db_root_password)" \
"$MARIADB_DATABASE"
' N'exécutez pas cet import sur la base de production existante. Une procédure de restauration complète doit préciser comment obtenir une base vide, quel temps d'arrêt est accepté et comment revenir à l'état précédent si la validation échoue.
Lire les logs et diagnostiquer une boucle de redémarrage
La première erreur est souvent de lancer down immédiatement. Cela supprime les conteneurs et fait perdre une partie des informations d'état utiles. Commencez par observer.
docker compose ps --all
docker compose logs --tail=200 --timestamps app
docker compose logs --tail=200 --timestamps db
docker compose logs --follow --since=10m app La commande logs permet de filtrer par service, de suivre le flux et de limiter l'historique avec --tail ou --since. Voir la référence de docker compose logs.
Inspectez ensuite l'état exact du conteneur :
CID="$(docker compose ps -q app)"
docker inspect "$CID" --format \
'status={{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}} restarts={{.RestartCount}} error={{.State.Error}}'
docker compose top
docker stats --no-stream
df -h
df -i
free -h
journalctl -u docker --since '30 minutes ago' Les causes fréquentes sont :
- secret absent, illisible ou monté sous un mauvais nom ;
- base encore indisponible, identifiants incohérents ou migration échouée ;
- port hôte déjà occupé ;
- volume monté au mauvais emplacement ou permissions incompatibles avec l'utilisateur du conteneur ;
- processus tué par manque de mémoire, visible avec
OOMKilled=true; - disque ou inodes saturés ;
- image incompatible avec l'architecture du VPS ;
- configuration interpolée différente de celle attendue.
Validez le modèle sans afficher les valeurs, puis consultez les variables attendues :
docker compose config --quiet
docker compose config --variables Si le service redémarre trop vite pour permettre exec, lancez un conteneur ponctuel sans ses dépendances et avec un shell, à condition que l'image en fournisse un :
docker compose run --rm --no-deps --entrypoint sh app Ce conteneur ponctuel monte les mêmes volumes et secrets déclarés pour le service. Évitez toute modification tant que la cause n'est pas comprise.
La rotation json-file déclarée dans le fichier limite l'espace consommé par les journaux locaux. Docker précise que max-size vaut -1 par défaut, donc sans limite de taille. Pour une production importante, expédiez également les logs vers un système externe avec une rétention adaptée. Voir le pilote de logs json-file.
Contrôles d'exploitation à conserver
À chaque modification
docker compose config --quiet
docker compose up -d
docker compose ps
docker compose logs --since=10m --timestamps Avant chaque mise à jour
- notes de version et chemin de migration lus ;
- espace disque contrôlé ;
- sauvegarde créée, exportée hors du VPS et restauration déjà testée ;
- images actuelles notées ;
- fenêtre d'interruption annoncée si nécessaire.
Après chaque redémarrage du VPS
docker compose ps
docker compose logs --since=30m --timestamps
ss -lntp Vérifiez que l'application est saine, que la base n'est pas publiée, que le port applicatif reste lié à 127.0.0.1 et que le reverse proxy sert le domaine en HTTPS.
Erreurs fréquentes à éviter
- commencer le fichier par
version: "3.8"; - utiliser le binaire historique
docker-compose; - appeler le fichier
docker-compose.ymldans un nouveau projet ; - écrire
8080:80en pensant que le service reste local ; - publier
3306:3306alors que seule l'application doit joindre la base ; - stocker les mots de passe dans
compose.yamlou dans le dépôt Git ; - utiliser un bind mount pour une base sans maîtriser propriétaire, permissions, sauvegarde et contexte de sécurité ;
- croire que supprimer un conteneur supprime ou sauvegarde son volume ;
- lancer
docker compose down -vpour « repartir proprement » ; - exécuter
docker compose restarten espérant appliquer une nouvelle configuration ; - mettre à jour une base sans lire son chemin de migration ni disposer d'un retour arrière ;
- sauvegarder uniquement sur le disque du VPS ;
- confondre un conteneur
runningavec une application réellement saine.
Questions fréquentes
Faut-il encore écrire version dans un fichier Compose ?
Non. La propriété de premier niveau version est documentée comme obsolète : elle est purement informative et produit un avertissement. Compose interprète le fichier avec la spécification actuelle, quelle que soit la valeur écrite.
Quelle différence entre docker compose et docker-compose ?
docker-compose avec un tiret est l'outil v1 en Python. docker compose en deux mots est le plugin Go, en v2 depuis 2020 et en v5 depuis 2025. Seule cette seconde forme doit être utilisée aujourd'hui.
docker compose down supprime-t-il mes données ?
Non par défaut : les volumes nommés sont conservés. C'est l'option -v qui les supprime, ainsi que docker volume prune sur un volume dont plus aucun conteneur n'existe.
Comment mettre à jour une stack sans perdre la base ?
Sauvegardez, changez la référence d'image dans .env, puis lancez docker compose pull suivi de docker compose up -d. Les conteneurs sont recréés, les volumes nommés sont conservés. docker compose restart ne suffit pas : il ne relit pas la configuration.
Quelle configuration VPS pour une stack Compose ?
Une application web et sa base tiennent sur 2 vCPU et 4 Go de RAM. Comptez davantage de mémoire dès que vous ajoutez un cache, un moteur de recherche ou plusieurs stacks sur la même machine, et surveillez surtout l'espace disque : les images, les volumes et les journaux s'accumulent plus vite que prévu.
Conclusion
Une stack Compose fiable repose moins sur la quantité de YAML que sur quelques invariants : un fichier compose.yaml conforme à la spécification actuelle, des images choisies et mises à jour volontairement, des volumes identifiés, des ports publiés au strict nécessaire, des secrets séparés du dépôt, une sauvegarde restaurable et un diagnostic fondé sur l'état réel des conteneurs.
Le cycle normal devient alors prévisible : valider, sauvegarder, tirer les images, appliquer avec docker compose up -d, contrôler la santé, puis conserver la possibilité de revenir en arrière. C'est cette discipline qui transforme un exemple Compose en socle d'exploitation pour les stacks suivantes.
