FlowingDev

Docker Compose, expliqué : la télécommande universelle pour vos conteneurs

Découvrez comment Docker Compose utilise un fichier YAML pour gérer des applications multi-conteneurs, transformant des commandes `docker run` complexes en un plan unique et partageable.

Essayer l'outil: Docker Run vers Compose

En une phrase

Docker Compose est un outil qui vous permet de définir et de lancer des applications multi-conteneurs en utilisant un seul fichier YAML lisible par un humain, au lieu d'un tas de commandes docker run longues et lourdingues.

Le problème qu'il résout

Au commencement, il y avait docker run. Et c'était... pas mal. Pour lancer un seul conteneur, ça va. Vous tapez votre commande, peut-être avec un mappage de port et un volume, et boum, vous avez un service qui tourne.

Mais que se passe-t-il quand votre application n'est pas qu'un seul service ? Et si vous avez un frontend web, une API backend, une base de données PostgreSQL et un cache Redis ? Soudain, vous n'exécutez plus une seule commande. Vous jonglez avec quatre d'entre elles.

Vous devez créer manuellement un réseau pour qu'ils puissent communiquer entre eux. Vous devez vous assurer de ne pas accidentellement les mapper tous sur le même port de l'hôte. Vous devez vous souvenir de la commande docker run exacte et très longue pour chaque service, avec toutes ses variables d'environnement et ses montages de volumes. Si vous voulez partager cette configuration avec un nouveau coéquipier, vous écrivez un README.md géant qui est essentiellement une prière pour qu'il copie-colle tout correctement. C'est la recette parfaite pour le chaos, les fautes de frappe et le dilemme classique du « ben, ça marche sur ma machine ! ».

C'est ce chaos que Docker Compose, né d'un projet nommé Fig que Docker a racheté en 2014, a été conçu pour dompter. Il s'agit de passer de la gestion de conteneurs individuels comme des « animaux de compagnie » à l'orchestration de tout un « troupeau » d'applications. Au lieu de donner des commandes impératives (lance ça, puis lance ça, lie-les comme ci...), vous écrivez un seul fichier déclaratif (voici à quoi ressemble toute ma stack applicative) et laissez Compose gérer les détails fastidieux pour en faire une réalité.

Comment ça marche sous le capot

Fondamentalement, Docker Compose est un traducteur. Il traduit un fichier YAML descriptif en une série d'appels à l'API du moteur Docker. C'est le « montre, ne raconte pas » de la gestion de conteneurs.

Des flags docker run aux clés YAML

Disons que vous voulez lancer une base de données PostgreSQL. La commande docker run pourrait ressembler à une ligne de soupe à l'alphabet sortie d'un film de hackers :

docker run --name my-postgres-db \
  -e POSTGRES_PASSWORD=supersecret \
  -p 5432:5432 \
  -v pgdata:/var/lib/postgresql/data \
  --restart unless-stopped \
  -d postgres:14

Cette commande a un nom, une variable d'environnement pour le mot de passe, un mappage de port, un volume nommé pour la persistance des données, une politique de redémarrage, et elle s'exécute en arrière-plan (-d).

Maintenant, voyons l'équivalent Docker Compose dans un fichier docker-compose.yml :

version: '3.8' # Spécifie la version du fichier Compose

services:
  db: # C'est un nom sympa pour notre service
    image: postgres:14
    container_name: my-postgres-db
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: supersecret
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata: # On déclare le volume nommé ici

Vous voyez le parallèle ? Les flags de la ligne de commande sont devenus des clés YAML propres et organisées. Ce n'est pas seulement plus joli ; c'est une définition de votre service qui peut être versionnée, partagée et beaucoup moins sujette aux erreurs.

Flag docker run Clé docker-compose.yml Ce que ça fait
postgres:14 (l'image) image: postgres:14 Spécifie l'image de conteneur à utiliser.
--name my-postgres-db container_name: my-postgres-db Donne un nom spécifique au conteneur.
-p 5432:5432 ports: ["5432:5432"] Mappe un port de l'hôte vers le conteneur.
-v pgdata:/var/lib/... volumes: [pgdata:/var/lib/...] Monte un volume pour les données persistantes.
-e VAR=value environment: { VAR: value } Définit des variables d'environnement dans le conteneur.
--restart unless-stopped restart: unless-stopped Définit la politique de redémarrage du conteneur.

La magie du réseau

C'est là que Compose brille vraiment. Si vous ajoutiez un service d'application web à ce même fichier YAML, comme ceci :

services:
  db:
    # ... (notre config postgres d'en haut)
  
  api:
    image: my-cool-api:latest
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgresql://user:supersecret@db:5432/mydatabase
    depends_on:
      - db

Quand vous lancez docker-compose up, Compose fait un truc génial : il crée un réseau virtuel dédié pour votre application. Les conteneurs db et api sont tous deux attachés à ce réseau.

Maintenant, le service api peut se connecter à la base de données en utilisant le nom d'hôte db — le même nom que nous avons donné au service dans le fichier YAML ! Fini la recherche d'adresses IP ou les fragiles flags --link. Compose gère toute la résolution DNS au sein de son réseau privé. C'est comme ça que votre conteneur d'application web peut murmurer des mots doux (ou, vous savez, des requêtes SQL) à votre conteneur de base de données de manière sûre et fiable.

Le cycle de vie de Compose

La commande docker-compose est votre panneau de contrôle pour toute la stack applicative.

  • docker-compose up : C'est la commande principale. Elle lit votre docker-compose.yml, vérifie l'état de votre système et fait tout ce qui est nécessaire pour qu'il corresponde au fichier. Elle va créer les réseaux, construire les images (si vous avez spécifié une instruction build), créer les volumes et démarrer tous les conteneurs de vos services. Si vous la relancez, elle vérifiera intelligemment les changements et ne recréera que les conteneurs qui doivent être mis à jour.
  • docker-compose down : Le sympathique opposé de up. Il arrête et supprime tous les conteneurs, et par défaut, supprime aussi le réseau qu'il a créé. C'est le bouton « nettoie mon bazar ». Pour une ardoise vraiment propre, docker-compose down -v supprime aussi les volumes nommés.
  • docker-compose ps : Vous montre le statut de tous les conteneurs gérés par votre fichier Compose.
  • docker-compose logs -f : Affiche en continu les logs de tous vos services dans un seul flux pratique, avec un code couleur par service. Indispensable pour le débogage.

Histoires vécues

La saga du « Ça marche sur ma machine »

Devon vient de finir une nouvelle fonctionnalité pour le tableau de bord analytique de son entreprise, qui utilise un backend Node.js et une base de données MongoDB. Ça marche parfaitement sur son portable. Il pousse le code et dit à une nouvelle recrue, Sam, de le récupérer et de le lancer. Le README de Devon contient deux énormes commandes docker run. Sam, étant humain, fait une faute de frappe dans le chemin du volume de la base de données. L'application démarre, mais ne peut rien sauvegarder. Sam passe trois heures à déboguer le code de l'application, pensant que le bug est là, tandis que Devon insiste sur le fait que tout va bien. Finalement, ils partagent leur écran et repèrent la faute de frappe. La frustration est à son comble.

La leçon : S'ils avaient utilisé un docker-compose.yml, l'intégration aurait consisté en une seule commande docker-compose up. Tout l'environnement — ports, volumes, variables d'environnement — aurait été identique, éliminant toute une catégorie d'erreurs de configuration et transformant une demi-journée de débogage en un démarrage de 30 secondes.

Le cauchemar du pipeline CI/CD

Une équipe DevOps construisait un pipeline d'intégration continue pour tester leur application microservices. L'application avait un serveur web, une passerelle API, un service utilisateur et un cache Redis. Leur Jenkinsfile était un script shell horrible, avec quatre appels docker run distincts et un tas de commandes sleep 30 en espérant que la base de données serait prête avant que l'API n'essaie de s'y connecter. Les tests étaient foireux ; parfois ils passaient, parfois ils échouaient parce que le sleep n'était pas assez long.

La leçon : Ils ont réécrit le pipeline pour utiliser Docker Compose. Le Jenkinsfile a été simplifié à docker-compose -f docker-compose.ci.yml up -d. Ils ont utilisé depends_on avec condition: service_healthy pour s'assurer que l'API ne démarrait qu'après que la base de données ait passé son propre health check. Les tests foireux ont disparu du jour au lendemain. Compose a fourni un moyen fiable et déclaratif de définir et de lancer l'environnement de test exact nécessaire, à chaque fois.

Le Sidecar oublié

Une développeuse, Maria, a déployé son application Python principale dans un conteneur Docker. Quelques semaines plus tard, son manager lui a demandé d'ajouter un suivi de performance détaillé. La meilleure façon était de lancer un exportateur Prometheus en tant que « sidecar » — un second conteneur qui vit à côté de l'application principale, partageant son réseau pour récupérer des métriques. Maria l'a mis en place avec une autre commande docker run. Mais maintenant, elle devait se souvenir de démarrer, d'arrêter et de redémarrer deux conteneurs à chaque fois qu'elle faisait une modification. Un jour, elle a redémarré l'application mais a oublié le sidecar, et ils ont perdu une journée entière de métriques.

La leçon : Maria a refactorisé sa configuration en un docker-compose.yml. Elle a défini son application Python comme un service et l'exportateur Prometheus comme un second. Désormais, docker-compose up les faisait vivre tous les deux, et docker-compose down les mettait à la retraite ensemble. Ils étaient gérés comme une seule unité logique, exactement comme il se doit. Compose rend les patterns comme les sidecars triviaux à gérer.

Erreurs et pièges courants

  • Penser que depends_on attend que le service soit prêt. Par défaut, depends_on attend seulement que le conteneur de la dépendance démarre, pas que l'application à l'intérieur soit complètement initialisée et prête à accepter des connexions. Votre API pourrait démarrer avant que votre base de données ne soit prête à être interrogée, provoquant un crash. La vraie solution est d'utiliser l'instruction healthcheck dans la définition de votre service de base de données, puis d'utiliser la syntaxe longue de depends_on avec condition: service_healthy.
  • Commiter des secrets dans Git. C'est incroyablement tentant de coder en dur POSTGRES_PASSWORD: my-secret-password directement dans le docker-compose.yml. Ne le faites pas. Quiconque a accès à votre dépôt a maintenant le mot de passe de votre base de données. La bonne manière est d'utiliser un fichier .env (que vous ajoutez à votre .gitignore) et de référencer des variables comme ${POSTGRES_PASSWORD} dans votre fichier Compose.
  • Confondre les 'bind mounts' et les volumes nommés. Un chemin commençant par . ou / (ex: ./my-app:/app) est un 'bind mount', qui mappe un répertoire de votre machine hôte. C'est génial pour le développement car les modifications du code sont répercutées instantanément. Un chemin commençant juste par un nom (ex: db-data:/var/lib/mysql) est un volume nommé géré par Docker. C'est mieux pour la persistance des données comme une base de données, car c'est indépendant de la plateforme et géré par le moteur Docker. Utiliser le mauvais type peut entraîner des problèmes de permission ou des données qui ne sont pas aussi portables que vous le pensez.
  • L'utiliser pour de la production à grande échelle. Docker Compose est fantastique pour le développement, les tests, et même de petits déploiements en production sur un seul hôte. Mais ce n'est PAS un substitut à un orchestrateur de conteneurs complet comme Kubernetes ou Docker Swarm. Il n'a aucun concept intégré de clusters multi-nœuds, d'auto-réparation, de répartition de charge (load balancing) entre les nœuds, ou de mises à jour progressives (rolling updates) sophistiquées. Connaissez ses limites.

Pourquoi vous devriez vous y intéresser

Vous devriez penser à Docker Compose dès que votre application implique plusieurs éléments mobiles.

  • Vous développez une application web avec une base de données séparée ? Utilisez Compose.
  • Vous devez lancer votre application, un cache, et un worker en arrière-plan ? Utilisez Compose.
  • Vous voulez donner à vos coéquipiers un moyen de lancer tout l'environnement de développement en une seule commande ? Utilisez Compose.
  • Vous mettez en place un pipeline CI qui doit monter un environnement temporaire pour les tests ? Utilisez Compose.

C'est l'outil standard de l'industrie pour le développement local basé sur des conteneurs. L'apprendre est l'étape naturelle après docker run, et c'est la porte d'entrée pour comprendre le monde plus complexe de l'orchestration d'applications. Il fait le pont entre le lancement d'un unique conteneur et l'exécution d'un système distribué complet.

Pour aller plus loin

  • Docker Docs: Compose file version 3 reference - La spécification officielle et la source de vérité pour toutes les clés et la syntaxe disponibles.
  • Docker Docs: Overview of Docker Compose - Une introduction de haut niveau par les créateurs eux-mêmes.
  • YAML Spec - Comme tous les fichiers docker-compose.yml sont en YAML, comprendre la syntaxe (clés-valeurs, listes, ancres) peut être très utile.
  • Awesome Docker - Une liste de ressources Docker triées sur le volet, dont beaucoup incluent des tutoriels et des exemples de fichiers docker-compose.yml pour diverses stacks.

Théorie bouclée. Place à la pratique — 100 % dans ton navigateur.

Essayer l'outil: Docker Run vers Compose