## Introduction

Docker Compose simplifie la gestion d'applications multi-conteneurs, mais sa facilité d'utilisation masque des pièges de configuration qui peuvent entraîner des pertes de données, des interruptions de service ou des vulnérabilités de sécurité. Cet article aborde les erreurs courantes de configuration Docker Compose, en proposant des exemples pratiques et des solutions concrètes pour les développeurs, les ingénieurs DevOps et les équipes techniques de startups.

Nous nous concentrons sur des étapes observables et vérifiables : vérifier l'état actuel avant d'apporter des modifications, utiliser des valeurs par défaut raisonnables, éviter les secrets codés en dur et savoir comment revenir en arrière. Chaque section comprend des extraits de commandes concrets, les sorties attendues et les procédures de récupération.

## Inventaire des versions et de l'environnement

Avant de modifier toute configuration Compose, comprenez votre environnement. Différentes versions de Docker Compose ont des capacités et une syntaxe différentes. Exécutez :

```bash
docker compose version
```

Exemple de sortie :

```
Docker Compose version v2.27.1
```

Pour Compose v1 (basé sur Python), utilisez `docker-compose version`. Notez la différence : la v2 est écrite en Go et est un plugin pour la CLI Docker, tandis que la v1 est un binaire autonome. De nombreux exemples en ligne font référence à la v1 ; si vous utilisez la v2, les commandes peuvent nécessiter `docker compose` (avec un espace) au lieu de `docker-compose`.

Vérifiez la version du moteur Docker :

```bash
docker version --format '{{.Server.Version}}'
```

Celle-ci doit être compatible avec votre version de Compose.

Inventoriez les conteneurs en cours d'exécution et leur état :

```bash
docker compose ps
```

Cela affiche les services du projet, leur état et les ports exposés. Pour une inspection plus approfondie, utilisez `docker inspect` sur un conteneur spécifique pour voir les montages, les réseaux et les variables d'environnement (bien que les variables d'environnement puissent être sensibles à moins d'être masquées).

### Séparer l'observation de l'intervention

Prenez l'habitude d'enregistrer l'état actuel avant de modifier quoi que ce soit. Par exemple, sauvegardez la configuration actuelle :

```bash
docker compose config > compose-backup-$(date +%Y%m%d).yml
```

Cela vous permet de comparer avec la nouvelle configuration et de revenir en arrière si nécessaire.

### Vérification de l'emplacement de stockage des données

Une erreur fréquente est de supposer que les données persistent lors des recréations de conteneurs sans configurer correctement les volumes. Dans Compose, vous pouvez monter un volume nommé ou un montage lié (bind mount). Un volume nommé est géré par Docker et généralement stocké sous `/var/lib/docker/volumes/`, tandis qu'un montage lié mappe directement un chemin hôte.

Exemple d'extrait Compose avec un volume nommé :

```yaml
services:
  db:
    image: postgres:16
    volumes:
      - db_data:/var/lib/postgresql/data
volumes:
  db_data:
```

Avec un montage lié :

```yaml
volumes:
  - ./data:/var/lib/postgresql/data
```

Pour vérifier la persistance des données, effectuez un test de redémarrage : arrêtez et supprimez le conteneur, puis relancez-le :

```bash
docker compose down
docker compose up -d
```

Vérifiez ensuite si les données sont toujours accessibles. Si les données disparaissent, le service a probablement écrit dans le système de fichiers éphémère du conteneur au lieu du volume prévu.

## Chemin de configuration sûr

Lorsque vous modifiez des fichiers Compose, suivez une approche systématique pour éviter d'introduire des erreurs.

### Valider la configuration avant de l'appliquer

Validez toujours votre fichier Compose avec :

```bash
docker compose config
```

Cela vérifie la syntaxe et résout les variables, affichant la configuration finale. En cas d'erreurs, la commande se termine avec un code non nul et affiche des messages. Par exemple, si vous avez une indentation YAML invalide :

```bash
docker compose config
```

Sortie :

```
services.db.environment.MYSQL_ROOT_PASSWORD contient un type invalide, il devrait être une chaîne ou un objet
```

Remarque : `docker compose config` ne garantit pas que les services fonctionneront correctement ; il vérifie uniquement la syntaxe et le schéma.

### Utiliser correctement les variables d'environnement

Au lieu de coder en dur les secrets dans le fichier Compose, utilisez la substitution de variables d'environnement. Dans `docker-compose.yml` :

```yaml
services:
  app:
    image: myapp:latest
    environment:
      DB_PASSWORD: ${DB_PASSWORD}
```

Spécifiez ensuite la valeur dans un fichier `.env` dans le même répertoire (non versionné) :

```
DB_PASSWORD=supersecret
```

Ou définissez-la dans le shell :

```bash
export DB_PASSWORD=supersecret
```

Lorsque vous exécutez `docker compose config`, la variable sera substituée. Pour éviter d'exposer les secrets, utilisez `docker compose config --no-interpolate` pour voir le fichier brut sans substitution.

Une erreur courante est d'utiliser `$$` (double dollar) pour échapper la substitution de variable lorsque vous souhaitez passer la chaîne littérale. Par exemple, si vous voulez passer un `$DB_PASSWORD` littéral au conteneur, écrivez `$$DB_PASSWORD` dans le fichier Compose.

### Limiter la portée : utiliser les profils et les fichiers de surcharge

Utilisez les profils Compose pour démarrer optionnellement des services. Par exemple :

```yaml
services:
  app:
    image: myapp:latest
  debug-tools:
    image: busybox
    profiles: ["debug"]
```

Avec les profils, `docker compose up` démarre uniquement `app`, tandis que `docker compose --profile debug up` démarre les deux. Cela empêche le démarrage accidentel de services de débogage en production.

Une autre technique consiste à utiliser plusieurs fichiers Compose, comme `compose.yaml` et `compose.override.yaml`. Le fichier de surcharge peut spécifier des paramètres spécifiques au développement. Par défaut, si les deux fichiers existent, Compose les fusionne automatiquement ; vous pouvez afficher la configuration fusionnée avec :

```bash
docker compose config
```

Si vous utilisez des noms de fichiers personnalisés, spécifiez-les avec les options `-f`. Cela vous permet de garder la configuration de base stable et d'ajouter des modifications locales sans toucher au fichier principal.

### Apporter des modifications petites et réversibles

Modifiez un paramètre à la fois et testez immédiatement. Par exemple, si vous devez augmenter la limite de mémoire d'un service, modifiez le fichier Compose :

```yaml
services:
  app:
    deploy:
      resources:
        limits:
          memory: 512M
```

Appliquez :

```bash
docker compose up -d
```

Vérifiez ensuite si le service est sain et si l'utilisation de la mémoire est dans les limites. Si des problèmes surviennent, annulez facilement la modification en défaisant la modification et en exécutant à nouveau `docker compose up -d`.

## Vérification et diagnostics

Après avoir appliqué les modifications, vérifiez que le système se comporte comme prévu.

### Vérifier l'état des services

Utilisez `docker compose ps` pour voir si les conteneurs sont en cours d'exécution :

```bash
docker compose ps
```

Exemple de sortie :

```
NAME                COMMAND                  SERVICE             STATUS              PORTS
myapp_app_1         "python app.py"          app                 running             0.0.0.0:8000->8000/tcp
myapp_db_1          "docker-entrypoint.s…"   db                  running             5432/tcp
```

Si un conteneur est `exited` ou `restarting`, examinez les journaux.

### Lire efficacement les journaux

Utilisez `docker compose logs` pour afficher les journaux :

```bash
docker compose logs app
```

Suivez les journaux en temps réel :

```bash
docker compose logs -f app
```

Si vous avez besoin de voir uniquement les lignes récentes :

```bash
docker compose logs --tail=100 app
```

Les indicateurs d'échec courants dans les journaux incluent les exceptions, les erreurs de connexion refusée ou les messages de boucle de crash. Par exemple, si la base de données est injoignable, vous pourriez voir :

```
ERROR: could not connect to database: connection refused
```

### Inspecter les détails du conteneur

Utilisez `docker inspect` pour obtenir des informations détaillées sur un conteneur, y compris son état de santé, ses montages et son environnement :

```bash
docker inspect myapp_app_1
```

Pour extraire des champs spécifiques :

```bash
docker inspect --format '{{.State.Health.Status}}' myapp_app_1
```

Si l'état de santé est `unhealthy`, examinez la configuration du healthcheck.

### Définir des healthchecks

Ajoutez des healthchecks à vos services pour détecter automatiquement les défaillances. Exemple pour une application web :

```yaml
services:
  web:
    image: nginx:alpine
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
```

Avec les healthchecks, `docker compose ps` affiche l'état de santé, et vous pouvez utiliser `docker compose up --wait` pour bloquer jusqu'à ce que les services soient sains.

### Déboguer à l'intérieur du conteneur

Utilisez `docker compose exec` pour exécuter des commandes dans un conteneur en cours d'exécution :

```bash
docker compose exec app sh
```

Cela vous donne un shell dans le conteneur pour vérifier les fichiers, les processus ou tester la connectivité. Pour des commandes ponctuelles :

```bash
docker compose exec app env
```

Remarque : `exec` nécessite que le conteneur soit en cours d'exécution ; si le conteneur se termine immédiatement, vous devrez peut-être remplacer le point d'entrée :

```bash
docker compose run --entrypoint sh app
```

## Modes de défaillance et récupération

Même avec une planification minutieuse, des problèmes surviennent. Voici des scénarios de défaillance typiques et comment récupérer.

### Le conteneur se termine immédiatement

Si un conteneur démarre et se termine avec le code 0 ou non nul, vérifiez les journaux :

```bash
docker compose logs <service>
```

Souvent, la commande est mal configurée. Dans Compose, l'option `command` remplace le CMD par défaut. Par exemple, si vous avez :

```yaml
services:
  app:
    image: alpine
    command: echo "hello"
```

Le conteneur exécutera `echo` et se terminera. Pour le maintenir en vie, vous pourriez avoir besoin d'un processus de longue durée. Récupération : ajustez la `command` pour un processus de premier plan.

### Conflits de ports

Si un service ne parvient pas à se lier à un port, vous verrez une erreur comme :

```
Error starting userland proxy: listen tcp4 0.0.0.0:80: bind: address already in use
```

Identifiez quel processus utilise le port sur l'hôte :

```bash
sudo lsof -i :80
```

Ou sur Linux :

```bash
sudo ss -tulpn | grep :80
```

Ensuite, arrêtez le processus en conflit ou modifiez le port hôte dans le fichier Compose :

```yaml
ports:
  - "8080:80"
```

### Erreurs de montage de volume

Si un chemin de montage lié n'existe pas sur l'hôte, Docker peut le créer en tant que répertoire avec la propriété root, ce qui entraîne des problèmes de permissions à l'intérieur du conteneur. Par exemple :

```yaml
volumes:
  - ./logs:/app/logs
```

Si `./logs` n'existe pas, Docker le crée, mais l'utilisateur du conteneur peut ne pas avoir les permissions d'écriture. Récupération : créez le répertoire avec des permissions appropriées avant de démarrer :

```bash
mkdir -p logs && chmod 777 logs
```

Ou ajustez l'utilisateur du conteneur pour qu'il corresponde.

### Variable d'environnement non substituée

Si vous référencez une variable dans le fichier Compose mais qu'elle n'est pas définie, `docker compose config` émettra un avertissement ou une erreur. Par exemple :

```yaml
environment:
  - DATABASE_URL=${DATABASE_URL}
```

Si `DATABASE_URL` n'est pas définie, le conteneur reçoit une chaîne vide. Pour éviter cela, définissez une valeur par défaut dans le fichier Compose :

```yaml
environment:
  - DATABASE_URL=${DATABASE_URL:-postgres://localhost:5432}
```

Vérifiez ensuite avec `docker compose config`.

### Stratégie de récupération : retour en arrière

Soyez toujours en mesure de revenir à un état connu et fonctionnel. Cela signifie :

- Gardez un contrôle de version pour vos fichiers Compose (par exemple, Git).
- Avant d'apporter des modifications, créez une sauvegarde de la configuration actuelle et, le cas échéant, des données.
- Utilisez `docker compose config` pour sauvegarder la configuration entièrement résolue pour référence future.
- Si une modification casse l'application, récupérez la version précédente et appliquez-la : `git checkout -- docker-compose.yml` puis `docker compose up -d`.

Pour les données, si vous utilisez des volumes nommés, envisagez des instantanés ou des sauvegardes. Par exemple, pour sauvegarder une base de données PostgreSQL dans un volume, vous pourriez exécuter :

```bash
docker compose exec db pg_dump -U postgres mydb > backup.sql
```

Puis pour restaurer :

```bash
cat backup.sql | docker compose exec -T db psql -U postgres mydb
```

## Pièges courants et comment les éviter

Cette section regroupe les erreurs fréquentes, pourquoi elles se produisent et comment les prévenir ou les récupérer.

### Piège 1 : Utiliser le tag `latest` pour les images

Utiliser `image: myapp:latest` peut entraîner un comportement imprévisible car `latest` peut changer de manière inattendue. Cela rend le retour en arrière difficile et nuit à la reproductibilité.

**Comment éviter :** Épinglez des versions d'image spécifiques, par exemple `image: myapp:1.2.3`. Utilisez un digest pour une immuabilité encore plus forte : `myapp@sha256:...`.

### Piège 2 : Coder en dur les secrets dans les fichiers Compose

Stocker des mots de passe ou des clés API directement dans `docker-compose.yml` est un risque de sécurité, surtout si le fichier est versionné dans un dépôt.

**Comment éviter :** Utilisez des variables d'environnement ou des secrets Docker. Pour le développement local, utilisez un fichier `.env` qui est gitignoré. Pour la production, utilisez un gestionnaire de secrets.

### Piège 3 : Mal comprendre les types de volumes

Utiliser des montages liés quand vous vouliez des volumes nommés (ou vice versa) peut entraîner une perte de données ou des problèmes de permissions. Les montages liés sont spécifiques à l'hôte ; les volumes nommés sont portables et gérés par Docker.

**Comment éviter :** Définissez clairement le type et le but du volume. Utilisez des volumes nommés pour les données d'application persistantes, des montages liés pour le développement où vous avez besoin de la synchronisation du code en direct.

### Piège 4 : Ne pas définir de politiques de redémarrage

Par défaut, les conteneurs ne redémarrent pas automatiquement sauf si vous spécifiez une politique de redémarrage. Si un conteneur plante, il reste arrêté.

**Comment éviter :** Définissez `restart: unless-stopped` ou `restart: always` pour les services de production. Exemple :

```yaml
services:
  app:
    restart: unless-stopped
```

### Piège 5 : Ignorer les limites de ressources

Sans limites, un conteneur peut consommer toutes les ressources de l'hôte, affectant les autres services.

**Comment éviter :** Définissez des limites de mémoire et de CPU en utilisant la clé `deploy` (pour Swarm) ou `mem_limit`, `cpus` (pour non-Swarm). Exemple :

```yaml
services:
  app:
    mem_limit: 512m
    cpus: 0.5
```

### Piège 6 : Mal configurer les réseaux

Les services peuvent ne pas communiquer parce qu'ils sont sur des réseaux différents ou que les alias de réseau sont incorrects.

**Comment éviter :** Définissez explicitement les réseaux dans le fichier Compose et attachez les services aux réseaux appropriés. Utilisez `docker compose exec` pour tester la connectivité avec `ping` ou `curl`.

### Piège 7 : Ne pas sauvegarder les données

Les données dans les conteneurs ou les volumes peuvent être perdues si vous supprimez des volumes par inadvertance.

**Comment éviter :** Sauvegardez régulièrement les volumes en utilisant `docker run --rm -v volume_name:/data -v $(pwd):/backup alpine tar czf /backup/backup.tar.gz -C /data .`

### Piège 8 : Négliger les limitations de `depends_on`

`depends_on` contrôle uniquement l'ordre de démarrage ; il n'attend pas que la dépendance soit prête (par exemple, que la base de données accepte les connexions).

**Comment éviter :** Utilisez des healthchecks et `depends_on` avec `condition: service_healthy` (Compose v2.1+). Exemple :

```yaml
services:
  web:
    depends_on:
      db:
        condition: service_healthy
  db:
    image: postgres
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
```

## Liste de contrôle opérationnelle

Utilisez cette liste avant et après avoir apporté des modifications de configuration Compose. Attribuez un responsable (par exemple, ingénieur DevOps) et une fréquence de révision.

| Étape | Action | Commande/Détail | Responsable | Révision |
|------|--------|----------------|-------|--------|
| 1 | Enregistrer la version actuelle de Compose | `docker compose version` | Responsable DevOps | À chaque modification |
| 2 | Sauvegarder la configuration actuelle | `docker compose config > backup.yml` | Responsable DevOps | À chaque modification |
| 3 | Valider la nouvelle configuration | `docker compose config` | Développeur | À chaque modification |
| 4 | Appliquer en mode simulation (si disponible) | `docker compose up --no-start` | Développeur | À chaque modification |
| 5 | Appliquer les modifications | `docker compose up -d` | Responsable DevOps | À chaque modification |
| 6 | Vérifier l'état des services | `docker compose ps` | Développeur | Immédiatement |
| 7 | Vérifier les journaux pour les erreurs | `docker compose logs --tail=50` | Développeur | Immédiatement |
| 8 | Tester la fonctionnalité de l'application | Tests manuels ou automatisés | Ingénieur QA | Après chaque déploiement |
| 9 | Vérifier la persistance des données | `docker compose down && docker compose up -d` puis vérifier les données | Responsable DevOps | Mensuellement |
| 10 | Examiner la sécurité (secrets, permissions) | `docker compose config --no-interpolate` pour vérifier les secrets | Agent de sécurité | Trimestriellement |

Chaque élément doit avoir un responsable nommé. Par exemple, le responsable DevOps (par exemple, « Priya Shah, responsable de l'ingénierie ») est responsable des sauvegardes de configuration et de l'application des modifications. Les intervalles de révision peuvent être ajustés en fonction du risque, mais les paramètres critiques doivent être examinés après chaque modification.

## Conclusion

Les erreurs de configuration Docker Compose peuvent entraîner des temps d'arrêt et des pertes de données, mais elles sont évitables avec des pratiques systématiques. En validant les configurations, en gérant correctement les volumes et les variables d'environnement, et en ayant un plan de retour en arrière, vous pouvez réduire considérablement les risques.

Observez toujours avant de changer, apportez des modifications petites et réversibles, et vérifiez le résultat avec des commandes concrètes. Tenez une liste de contrôle et attribuez des responsabilités pour garantir la responsabilisation. Pour approfondir, explorez la documentation officielle de Docker sur la référence des fichiers Compose, les healthchecks et la gestion des secrets.

Prochaine étape : choisissez une amélioration à faible risque de cet article, comme l'ajout de healthchecks à un service, et implémentez-la dans un environnement de développement. Enregistrez l'état actuel, appliquez la modification et vérifiez le comportement attendu. Documentez tout problème pour référence future.

En adoptant ces pratiques, vous construirez un environnement conteneurisé plus résilient.