Docker Compose pour les Applications Multi-Conteneurs
Bienvenue à cette leçon fondamentale du cours "Maîtriser Docker et Kubernetes : Déploiement et Scalabilité d'Applications Modernes". Aujourd'hui, nous allons explorer Docker Compose, un outil indispensable pour gérer des applications complexes composées de multiples conteneurs. Alors que Docker vous permet de gérer des conteneurs individuels, Docker Compose excelle dans l'orchestration de groupes de conteneurs qui fonctionnent ensemble pour former une application complète.
Introduction : Au-delà du Conteneur Unique
Dans le monde du développement moderne, les applications sont rarement monolithiques. Elles sont souvent décomposées en microservices, ou du moins en composants distincts qui communiquent entre eux : un serveur web, une base de données, un cache, une file d'attente de messages, etc. Gérer chacun de ces services comme un conteneur Docker séparé peut devenir fastidieux. Vous devriez démarrer chaque conteneur individuellement, gérer leurs réseaux, leurs volumes, et leurs dépendances. C'est là que Docker Compose entre en jeu.
Docker Compose est un outil qui simplifie la définition et l'exécution d'applications Docker multi-conteneurs. Avec un simple fichier YAML, vous pouvez configurer l'ensemble des services de votre application, spécifier leurs dépendances, leurs ports, leurs volumes et leurs réseaux, puis les démarrer tous avec une seule commande. C'est la solution idéale pour le développement local, les tests automatisés et les environnements de staging simples.
Qu'est-ce que Docker Compose ?
Docker Compose est un outil développé par Docker pour définir et exécuter des applications Docker multi-conteneurs. Il utilise un fichier de configuration au format YAML pour spécifier tous les services qui composent votre application. Chaque service est un conteneur qui peut être démarré, arrêté, lié à d'autres services, etc.
En substance, Docker Compose vous permet de :
- Définir votre application en un seul fichier (généralement
docker-compose.yml). - Isoler les environnements pour chaque service.
- Simplifier le démarrage et l'arrêt de l'ensemble de l'application.
- Faciliter la collaboration en assurant que tous les développeurs travaillent avec le même environnement configuré.
Pourquoi utiliser Docker Compose ?
L'utilisation de Docker Compose apporte de nombreux avantages, en particulier pour les développeurs et les équipes travaillant sur des applications distribuées :
- Environnements Cohérents et Reproductibles : Fini le "ça marche sur ma machine !". Docker Compose garantit que chaque membre de l'équipe utilise exactement le même environnement de développement que celui défini dans le fichier
docker-compose.yml. C'est un pas géant vers la reproductibilité. - Démarrage Facilité : Au lieu de multiples commandes
docker run, une simple commandedocker-compose upsuffit pour démarrer l'intégralité de votre application multi-services, y compris la création des réseaux et des volumes nécessaires. - Gestion Simplifiée des Dépendances : Vous pouvez spécifier l'ordre de démarrage des services et les relations entre eux (par exemple, le service web dépend de la base de données).
- Isolation des Services : Chaque service est encapsulé dans son propre conteneur, ce qui garantit l'isolation des dépendances logicielles et réduit les conflits.
- Configuration Centralisée : Toutes les configurations (ports, volumes, variables d'environnement, réseaux) sont définies dans un seul fichier facile à lire et à versionner.
- Idéal pour le Développement et le Staging : Bien que ce ne soit pas un orchestrateur de production à grande échelle comme Kubernetes, Docker Compose est parfait pour simuler un environnement de production sur une machine locale ou un serveur de staging.
Concepts Clés de Docker Compose
Pour comprendre Docker Compose, il est essentiel de maîtriser quelques concepts fondamentaux qui sont définis dans le fichier docker-compose.yml.
Services
Un service dans Docker Compose est un conteneur (ou un groupe de conteneurs d'une même image) qui exécute une partie de votre application. Chaque service est défini avec une image Docker spécifique (ou un Dockerfile à construire), des ports exposés, des volumes montés, des variables d'environnement, etc.
- Exemple : Votre application web, votre base de données, un cache Redis sont tous des services distincts.
Réseaux
Docker Compose crée par défaut un réseau par défaut pour votre application, permettant à tous les services de communiquer entre eux via leurs noms de service. Par exemple, si vous avez un service nommé db, votre service web peut se connecter à la base de données en utilisant l'hôte db.
Vous pouvez aussi définir des réseaux personnalisés pour organiser la communication de manière plus granulaire ou pour connecter des services entre différentes applications Compose.
- Avantage : Simplifie la découverte de service (pas besoin de connaître les adresses IP).
Volumes
Les volumes sont utilisés pour persister les données générées et utilisées par les conteneurs. Les données stockées dans des volumes survivent à l'arrêt, au redémarrage ou à la suppression des conteneurs.
-
Types de volumes :
- Volumes nommés (Named Volumes) : Gérés par Docker, ils sont le moyen préféré pour persister les données.
- Montages de bind (Bind Mounts) : Permettent de mapper un chemin du système de fichiers de l'hôte directement dans le conteneur. Idéal pour le développement, car les modifications de code sur l'hôte sont immédiatement reflétées dans le conteneur.
-
Exemple : Persister les données de votre base de données, ou monter votre code source dans le conteneur de votre application web pour un rechargement à chaud.
Le Fichier docker-compose.yml
Le cœur de Docker Compose est son fichier de configuration, docker-compose.yml (ou docker-compose.yaml). Ce fichier est écrit en YAML et définit la structure de votre application multi-conteneurs.
Voici la structure de base des sections les plus courantes :
version: '3.8' # La version de la syntaxe Docker Compose. Toujours utiliser la dernière stable.
services: # La section où vous définissez tous vos conteneurs/services.
<nom_du_service_1>:
image: <nom_image>:<tag> # Ou 'build: .' pour construire à partir d'un Dockerfile local.
ports: # Mapper les ports de l'hôte vers le conteneur.
- "8000:80" # Hôte:Conteneur
volumes: # Monter des volumes ou des bind mounts.
- ./app:/usr/src/app # Hôte:Conteneur (bind mount)
- db_data:/var/lib/postgresql/data # Volume nommé:Conteneur
environment: # Variables d'environnement passées au conteneur.
- DATABASE_URL=postgres://user:password@db:5432/mydb
depends_on: # Définir les dépendances de démarrage (le service 'db' doit démarrer avant celui-ci).
- <nom_du_service_dependant>
networks: # Attacher le service à des réseaux spécifiques.
- <nom_du_reseau>
<nom_du_service_2>:
# ... autres configurations
volumes: # Section pour définir les volumes nommés.
db_data:
networks: # Section pour définir les réseaux personnalisés.
app_network:
driver: bridge
Explications des Directives Clés :
version: Indique la version du fichier Compose que vous utilisez. La version3.8(ou la plus récente) est recommandée pour bénéficier des dernières fonctionnalités.services: C'est ici que vous définissez chaque composant de votre application. Chaque clé sousservicesest le nom d'un service (ex:web,db,redis).imageoubuild:image: Spécifie l'image Docker à utiliser pour le service (ex:nginx:latest,postgres:13). Compose tentera de la télécharger si elle n'est pas déjà présente.build: Indique à Compose de construire une image à partir d'unDockerfilespécifié.build: .signifie que leDockerfilese trouve dans le répertoire courant. Vous pouvez aussi spécifiercontext: ./path/to/diretdockerfile: Dockerfile.prod.
ports: Mappe les ports du conteneur aux ports de la machine hôte."<port_hôte>:<port_conteneur>". Le port hôte peut être omis ("80") pour un port dynamique.volumes: Monte des volumes persistants ou des bind mounts../app:/usr/src/app: Un bind mount. Le répertoire./appde votre machine hôte est monté dans/usr/src/appdu conteneur. Utile pour le code source.db_data:/var/lib/postgresql/data: Un volume nommé.db_dataest le nom du volume défini dans la sectionvolumesplus bas.
environment: Définit des variables d'environnement passées au conteneur. Utile pour les identifiants de base de données, les clés API, etc.depends_on: Spécifie les dépendances entre les services. Par exemple, le servicewebdépend du servicedb. Docker Compose démarreradbavantweb. Attention :depends_ongarantit uniquement que le conteneur du service dépendant est démarré, pas que l'application à l'intérieur de ce conteneur est entièrement prête et opérationnelle (par exemple, une base de données peut prendre du temps à initialiser). Pour une véritable "attente de service", vous devrez utiliser des scripts de santé ou des outils commewait-for-it.shà l'intérieur de votre conteneur.networks: Connecte un service à un ou plusieurs réseaux définis dans la sectionnetworks.
volumes: Définit les volumes nommés qui peuvent être réutilisés par différents services. Docker gérera le stockage de ces volumes.networks: Définit les réseaux personnalisés. Par défaut, Docker Compose crée un réseaubridgepour l'application. Vous pouvez en définir d'autres si nécessaire.
Exemple Pratique : Application Web avec Base de Données
Déployons une application web simple avec une base de données PostgreSQL en utilisant Docker Compose. Notre application sera une petite API Flask en Python.
Scénario :
- Service
web: Une application Flask (Python) qui interagit avec la base de données. - Service
db: Une base de données PostgreSQL.
Structure du projet :
.
├── docker-compose.yml
├── web/
│ ├── Dockerfile
│ ├── app.py
│ └── requirements.txt
└── .env
1. Fichiers de l'Application Web (web/)
web/Dockerfile : Pour construire l'image de notre application Flask.
# web/Dockerfile
FROM python:3.9-slim-buster
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
web/requirements.txt : Dépendances Python.
# web/requirements.txt
Flask
psycopg2-binary
web/app.py : L'application Flask simple.
# web/app.py
import os
from flask import Flask
import psycopg2
app = Flask(__name__)
# Récupérer les variables d'environnement
DB_HOST = os.getenv('DB_HOST', 'db') # 'db' est le nom du service PostgreSQL dans docker-compose.yml
DB_NAME = os.getenv('DB_NAME', 'mydatabase')
DB_USER = os.getenv('DB_USER', 'myuser')
DB_PASSWORD = os.getenv('DB_PASSWORD', 'mypassword')
def get_db_connection():
conn = None
try:
conn = psycopg2.connect(
host=DB_HOST,
database=DB_NAME,
user=DB_USER,
password=DB_PASSWORD
)
return conn
except Exception as e:
print(f"Erreur de connexion à la base de données : {e}")
return None
@app.route('/')
def hello():
return "Bonjour depuis l'application Flask !"
@app.route('/test-db')
def test_db():
conn = get_db_connection()
if conn:
cursor = conn.cursor()
cursor.execute('SELECT 1;')
result = cursor.fetchone()
conn.close()
return f"Connexion à la base de données réussie : {result}"
else:
return "Échec de la connexion à la base de données."
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
2. Variables d'Environnement (.env)
.env : Pour définir les variables d'environnement sensibles ou spécifiques à l'environnement.
# .env
POSTGRES_DB=mydatabase
POSTGRES_USER=myuser
POSTGRES_PASSWORD=mypassword
Docker Compose lira automatiquement ce fichier .env si il est présent dans le même répertoire que docker-compose.yml.
3. Le Fichier docker-compose.yml
C'est le fichier clé qui orchestre nos deux services.
# docker-compose.yml
version: '3.8'
services:
web:
build: ./web # Construire l'image à partir du Dockerfile dans le répertoire './web'
ports:
- "5000:5000" # Mapper le port 5000 de l'hôte au port 5000 du conteneur web
volumes:
- ./web:/app # Monter le répertoire local 'web' dans '/app' du conteneur pour le développement
environment:
# Passer les variables d'environnement de .env au service web
DB_HOST: db # Le nom du service de la base de données est 'db'
DB_NAME: ${POSTGRES_DB}
DB_USER: ${POSTGRES_USER}
DB_PASSWORD: ${POSTGRES_PASSWORD}
depends_on:
- db # S'assurer que le service 'db' est démarré avant 'web'
db:
image: postgres:13 # Utiliser l'image officielle PostgreSQL version 13
environment:
# Utiliser les variables d'environnement du fichier .env
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data # Persister les données de la base de données dans un volume nommé
volumes:
db_data: # Définition du volume nommé 'db_data'
Explication Détaillée du docker-compose.yml :
version: '3.8': Nous utilisons la version 3.8 de la spécification Compose.services::web::build: ./web: Compose va construire une image Docker pour ce service en utilisant leDockerfilesitué dans le sous-répertoireweb.ports: - "5000:5000": Le port 5000 de notre machine hôte est mappé au port 5000 du conteneurweb. Ainsi, vous pourrez accéder à l'application viahttp://localhost:5000.volumes: - ./web:/app: C'est un bind mount. Le répertoirewebde notre projet local est monté dans/appà l'intérieur du conteneur. Cela signifie que si vous modifiezapp.pysur votre machine, la modification sera instantanément visible dans le conteneur (si votre application est configurée pour recharger à chaud, comme Flask en mode debug).environment:: Définit les variables d'environnement que l'application Flask utilisera pour se connecter à la base de données. Remarquez queDB_HOSTestdb, qui est le nom du service de base de données dans notre fichier Compose. Compose gère la résolution DNS des noms de service au sein de son réseau interne. Les autres variables (DB_NAME,DB_USER,DB_PASSWORD) sont lues à partir du fichier.envgrâce à la syntaxe${VARIABLE_NAME}.depends_on: - db: Indique à Compose de démarrer le servicedbavant le serviceweb.
db::image: postgres:13: Utilise l'image officielle de PostgreSQL version 13 depuis Docker Hub. Compose la téléchargera si elle n'est pas déjà présente.environment:: Définit les variables d'environnement spécifiques à PostgreSQL pour initialiser la base de données. Celles-ci sont également lues depuis le fichier.env.volumes: - db_data:/var/lib/postgresql/data: Monte le volume nommédb_datadans le chemin par défaut où PostgreSQL stocke ses données. Cela assure que les données de votre base de données persistent même si le conteneurdbest supprimé et recréé.
volumes::db_data:: Déclare un volume nommé appelédb_data. Docker créera et gérera ce volume pour nous, garantissant la persistance des données.
Commandes Docker Compose Essentielles
Maintenant que vous avez un fichier docker-compose.yml, voyons comment interagir avec votre application.
Pour exécuter ces commandes, assurez-vous d'être dans le même répertoire que votre fichier docker-compose.yml.
-
docker-compose up- Description : Construit (si nécessaire), crée, démarre et attache les conteneurs définis dans votre
docker-compose.yml. C'est la commande la plus utilisée pour lancer votre application. - Variantes :
docker-compose up: Démarre les services en arrière-plan (mode détaché).docker-compose up --build: Force la reconstruction des images des services avant de les démarrer. Utile après des modifications duDockerfile.docker-compose up -d: Démarre les services en mode détaché (en arrière-plan), ce qui libère votre terminal.
# Démarre les services en mode détaché docker-compose up -d - Description : Construit (si nécessaire), crée, démarre et attache les conteneurs définis dans votre
-
docker-compose ps- Description : Liste tous les services en cours d'exécution de votre application Compose, avec leur statut et les ports mappés.
docker-compose ps -
docker-compose logs [service_name]- Description : Affiche les logs des services. Vous pouvez spécifier un nom de service pour voir les logs d'un conteneur spécifique.
- Variantes :
docker-compose logs: Affiche les logs de tous les services.docker-compose logs -f [service_name]: Suit les logs en temps réel (commetail -f).
# Affiche les logs du service web docker-compose logs web # Suit les logs de tous les services en temps réel docker-compose logs -f -
docker-compose exec [service_name] [command]- Description : Exécute une commande à l'intérieur d'un conteneur de service en cours d'exécution. Utile pour le débogage ou pour interagir avec un service.
# Exécute un shell bash dans le conteneur web docker-compose exec web bash # Connecte au terminal PostgreSQL via psql docker-compose exec db psql -U myuser mydatabase -
docker-compose stop [service_name]- Description : Arrête les conteneurs d'un ou de tous les services sans les supprimer. Les conteneurs peuvent être redémarrés plus tard avec
docker-compose start.
# Arrête le service web docker-compose stop web - Description : Arrête les conteneurs d'un ou de tous les services sans les supprimer. Les conteneurs peuvent être redémarrés plus tard avec
-
docker-compose start [service_name]- Description : Démarre les conteneurs précédemment arrêtés.
# Démarre le service web docker-compose start web -
docker-compose down- Description : Arrête et supprime les conteneurs, les réseaux et les volumes par défaut créés par
docker-compose up. - Variantes :
docker-compose down: Supprime les conteneurs et les réseaux.docker-compose down --volumes(ou-v): Supprime également les volumes nommés (ceux déclarés dans la sectionvolumes:du fichier Compose). Attention : Cela supprimera toutes les données persistantes, utilisez avec prudence.
# Arrête et supprime les services, les réseaux. Conserve les volumes nommés. docker-compose down # Arrête et supprime TOUT, y compris les volumes (attention aux données !) docker-compose down -v - Description : Arrête et supprime les conteneurs, les réseaux et les volumes par défaut créés par
-
docker-compose build [service_name]- Description : Reconstruit les images pour les services qui ont une directive
build. Utile si vous avez modifié unDockerfileou les fichiers de contexte de construction.
# Reconstruit l'image du service web docker-compose build web - Description : Reconstruit les images pour les services qui ont une directive
Bonnes Pratiques et Astuces
- Utilisez les versions de services spécifiques : Toujours spécifier la version exacte des images (ex:
postgres:13au lieu depostgres:latest) pour assurer la reproductibilité. - Fichiers
.envpour les secrets : N'incorporez jamais d'informations sensibles (mots de passe, clés API) directement dansdocker-compose.yml. Utilisez un fichier.envou des variables d'environnement du système d'exploitation. Docker Compose chargera automatiquement les variables d'un fichier.envsitué dans le même répertoire que ledocker-compose.yml. - Volumes nommés pour la persistance : Privilégiez les volumes nommés (
volumes: db_data:) plutôt que les bind mounts pour les données de production/staging, car ils sont gérés par Docker et plus performants. - Bind mounts pour le développement : Les bind mounts (
volumes: ./src:/app) sont excellents en développement car ils reflètent immédiatement les changements de code de l'hôte dans le conteneur. - Environnements multiples avec
extendsou plusieurs fichiers :- Utilisez plusieurs fichiers
docker-compose.yml(ex:docker-compose.ymlpour la base,docker-compose.override.ymlpour les spécificités de développement) et la commandedocker-compose -f file1.yml -f file2.yml up. Docker Compose fusionne automatiquementdocker-compose.override.yml. - Utilisez la directive
extendspour réutiliser des configurations de services.
- Utilisez plusieurs fichiers
- Santé des conteneurs : Utilisez
healthcheckdans vos définitions de service pour vérifier que l'application à l'intérieur du conteneur est réellement prête, pas seulement que le conteneur est démarré. Cela aidedepends_on(même si ce dernier ne l'attend pas explicitement).
Limitations et Quand Passer à l'Orchestration (Kubernetes)
Bien que Docker Compose soit un outil puissant et suffisant pour de nombreux cas d'utilisation, il a ses limites :
- Orchestration sur un seul hôte : Docker Compose est conçu pour gérer des applications multi-conteneurs sur une seule machine ou un seul nœud Docker. Il ne fournit pas de fonctionnalités natives pour la distribution de services sur plusieurs serveurs, la haute disponibilité ou la gestion automatique de la charge.
- Pas de haute disponibilité automatique : Si un conteneur tombe, Compose ne le redémarrera pas automatiquement sur un autre nœud.
- Mise à l'échelle limitée : Bien que vous puissiez augmenter le nombre d'instances d'un service avec
docker-compose up --scale service=N, cela se fait sur un seul hôte et ne gère pas la répartition de charge avancée ou la détection de défaillance. - Pas de rolling updates : La mise à jour de votre application implique souvent un temps d'arrêt.
Pour les applications en production nécessitant une scalabilité horizontale, une haute disponibilité, des mises à jour sans interruption (rolling updates), une résilience et une gestion automatisée de l'infrastructure, vous devrez vous tourner vers des orchestrateurs de conteneurs plus avancés comme Docker Swarm ou, plus communément, Kubernetes.
Kubernetes est le standard de l'industrie pour l'orchestration de conteneurs à grande échelle et sera le sujet de nos prochaines leçons. Docker Compose sert souvent de tremplin, permettant de prototyper et de tester des architectures de microservices avant de les déployer dans un cluster Kubernetes.
Conclusion
Docker Compose est un outil essentiel pour tout développeur travaillant avec Docker. Il transforme la tâche complexe de la gestion d'applications multi-conteneurs en une expérience simple et reproductible grâce à un fichier docker-compose.yml intuitif.
Nous avons appris :
- Ce qu'est Docker Compose et pourquoi il est crucial pour les applications modernes.
- Les concepts fondamentaux comme les services, les réseaux et les volumes.
- La structure détaillée d'un fichier
docker-compose.ymlet ses directives clés. - Un exemple pratique complet avec une application web Flask et une base de données PostgreSQL.
- Les commandes Docker Compose les plus utilisées pour gérer votre application.
- Les bonnes pratiques et les limitations de l'outil, soulignant son rôle en développement et le besoin d'orchestrateurs comme Kubernetes pour la production à grande échelle.
Maîtriser Docker Compose vous donnera une base solide pour développer et tester des architectures distribuées, et vous préparera parfaitement aux défis de l'orchestration avancée avec Kubernetes.