🧱 Docker compose
🎬 Introduction
La fonction Compose de Docker permet de définir et de gérer des stacks multi-conteneurs de manière simple et efficace.
Cela permet de décrire l'architecture d'une application complète, incluant plusieurs services, réseaux et volumes, dans un seul fichier YAML (docker-compose.yml) sans forcément déployer une solution plus complexe comme Kubernetes.
Cependant, pour des applications de production plus robustes, il est recommandé d'utiliser des orchestrateurs comme Kubernetes.
La documentation officielle de Docker Compose est disponible ici : Docker Compose documentation.
Mais on va voir ensemble les principales fonctionnalités.
🛂 Contrôle
On va déjà commencer par vérifier que docker compose est bien disponible et sa version.
Docker compose est disponible
⚠️ Vous devez avoir une version récente de Docker Compose pour être sûr de pouvoir réaliser les étapes du lab.
ℹ️ La version courante est la v2.39.2.
Docker compose n'est pas disponible
Rendez-vous sur la documentation officielle pour l'installer : Install Docker Compose.
🚀 C'est parti
Checker l'état de vos composes
A tout moment vous pouvez faire un docker compose ls -a pour voir les compose dépolyés sur votre docker.
🐣 Un premier fichier de définition
On va commencer simple avec un fichier docker-compose.yml qui va définir un seul service pour notre backend de l'application de pet shop.
Question
A la racine du workspace, créer un fichier docker-compose.yml qui :
- déploie un service nommé
backend - utilise l'image
registry.gitlab.com/yodamad-workshops/2026/workshop-docker/demo_app/backend:main - mappe le port
8080du conteneur vers le port8080de la machine hôte
Une fois que c'est prêt, exécutez la commande suivante pour démarrer le service :
Le service backend est démarré
La commande up doit afficher les logs suivants.
[+] Running 5/5
✔ backend Pulled 7.0s
✔ 41e05f4acfc3 Pull complete 0.5s
✔ 0a3d06e07e77 Pull complete 1.4s
✔ 85b631112103 Pull complete 0.9s
✔ e521be0460be Pull complete 2.7s
[+] Running 3/3
✔ Network compose_default Created 0.1s
✔ Container compose-backend-1 Created 0.2s
Attaching to backend-1
backend-1 | 2025/11/10 14:55:22 Server starting on port 8080
Via l'onglet Ports de votre workspace, vous pouvez forwarder le port 8008 pour accéder à l'url, puis cliquer sur l'icone "🌍" pour afficher l'url et compléter par /health, vous devez voir une réponse JSON indiquant que le service est opérationnel.
La solution est là... ⤵️
Arrêter le service
Faites un bon vieux Ctrl+C dans le terminal où le service tourne pour arrêter le service.
Maintenant que le service semble fonctionner, il faudrait vérifier qu'il fonctionne correctement sachant qu'il dispose d'un endpoint /health pour cela.
Vérification du service backend
Ajouter au fichier docker-compose.yml une configuration de healthcheck pour le service backend qui vérifie la santé du service en effectuant :
- une requête HTTP GET sur l'endpoint
/health - toutes les 30 secondes
- avec un délai d'attente de 10 secondes
- qui considère le service comme sain après 3 tentatives réussies
Une fois que c'est prêt, exécutez la commande suivante pour démarrer le service :
Le service backend est démarré et sain
- La commande
updoit afficher les logs suivants :
[+] Running 1/1
✔ Container compose-backend-1 Recreated 0.1s
Attaching to backend-1
backend-1 | 2025/11/10 15:31:54 Server starting on port 8080
- En faisant un
docker psdans un autre terminal, vous devez voir le statuthealthypour le conteneurworkspaces-backend-1.
Vérifier le statut de santé
Si le conteneur reste en état starting ou unhealthy, vous pouvez vérifier le statut de santé du conteneur en utilisant la commande suivante :
cela vous donnera des informations détaillées sur les tentatives de vérification de l'état de santé :
{
"Status": "unhealthy",
"FailingStreak": 3,
"Log": [
{
"Start": "2025-11-10T16:32:24.899214585+01:00",
"End": "2025-11-10T16:32:24.95917608+01:00",
"ExitCode": 1,
"Output": "wget: can't connect to remote host: Connection refused\n"
},
{
"Start": "2025-11-10T16:32:54.961842833+01:00",
"End": "2025-11-10T16:32:55.04589081+01:00",
"ExitCode": 1,
"Output": "wget: can't connect to remote host: Connection refused\n"
},
{
"Start": "2025-11-10T16:33:25.050034995+01:00",
"End": "2025-11-10T16:33:25.111111913+01:00",
"ExitCode": 1,
"Output": "wget: can't connect to remote host: Connection refused\n"
}
]
}
La solution est là... ⤵️
# Docker compose for a single container with healthcheck
services:
backend:
image: registry.gitlab.com/yodamad-workshops/2026/workshop-docker/demo_app/backend:main
ports:
- "8080:8080"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
Echec du healthcheck (optionnel)
Pour tester le fonctionnement du healthcheck, vous pouvez modifier temporairement l'endpoint dans la configuration du healthcheck pour qu'il pointe vers un endpoint inexistant, par exemple /invalid_health.
Après avoir redémarré le service avec docker compose up, vous devriez voir que le conteneur passe à l'état unhealthy après plusieurs tentatives échouées.
Maintenant que notre backend est opérationnel, on va complexifier un peu les choses en ajoutant le frontend.
Arrêter le service
Faites un bon vieux Ctrl+C dans le terminal où le service tourne pour arrêter le service.
🧱 Une stack plus évoluée
L'avantage de Compose est de pouvoir définir plusieurs services dans un même fichier.
Ajout du service frontend
Toujours dans le même fichier docker-compose.yml, ajouter un service frontend qui :
- utilise l'image
registry.gitlab.com/yodamad-workshops/2026/workshop-docker/demo_app/frontend:main - mappe le port
80du conteneur vers le port3000 - vérifie la santé du service en effectuant une requête HTTP GET sur l'URL
/toutes les 30 secondes, avec un délai d'attente de 10 secondes, qui considère le service comme sain après 3 tentatives réussies
Une fois que c'est prêt, exécutez la commande suivante pour démarrer le service :
Les services backend et frontend sont démarrés et sains
- La commande
updoit afficher les logs suivants :
[+] Running 9/9
✔ frontend Pulled 4.4s
✔ 2110832f091f Pull complete 0.7s
✔ 150eb38f717f Pull complete 0.5s
✔ 53f686cfec5f Pull complete 0.8s
✔ 530d41a4378d Pull complete 0.9s
✔ bfaa5a64b4c2 Pull complete 2.0s
✔ 98047492076b Pull complete 0.8s
✔ 9f2537b40bed Pull complete 0.7s
✔ 15662b74eb12 Pull complete 0.7s
[+] Running 3/4
⠸ Container workspaces-backend-1 Recreate0.3s 0.2s
✔ Container workspaces-frontend-1 [+] Running 3/4 Created0.3s
⠼ Container workspaces-backend-1 Recreate0.4s
✔ Container workspaces-frontend-1 [+] Running 4/4 Created0.3s
✔ Container workspaces-backend-1 Recreated0.5s
✔ Container workspaces-frontend-1 Created0.3s
Attaching to backend-1, frontend-1
- En faisant un
docker psdans un autre terminal, vous devez voir le statuthealthypour les conteneursworkspaces-backend-1etworkspaces-frontend-1.docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES b1322e797a59 registry.gitlab.com/yodamad-workshops/2026/workshop-docker/demo_app/frontend:main "/docker-entrypoint.…" 6 seconds ago Up 5 seconds (health: healthy) 0.0.0.0:3000->80/tcp, [::]:3000->80/tcp workspaces-frontend-1 d0e5a894bc12 registry.gitlab.com/yodamad-workshops/2026/workshop-docker/demo_app/backend:main "./server" 6 seconds ago Up 5 seconds (health: healthy) 0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp workspaces-backend-1
La solution est là... ⤵️
# Docker compose for 2 services
services:
# Backend service
backend:
image: registry.gitlab.com/yodamad-workshops/2026/workshop-docker/demo_app/backend:main
ports:
- "8080:8080"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
# Frontend service
frontend:
image: registry.gitlab.com/yodamad-workshops/2026/workshop-docker/demo_app/frontend:main
ports:
- "3000:80"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/"]
interval: 30s
timeout: 10s
retries: 3
Dans des cas plus réels, il est fréquent qu'un service dépende d'un autre service pour fonctionner correctement.
Nous allons donc ajouter une dépendance entre le frontend et le backend.
Lier les 2 services
Modifier la configuration du service frontend pour que :
- il dépende du service
backend - il attende que le service
backendsoithealthyavant de démarrer - il redémarre automatiquement si le service
backendredémarre
Une fois que c'est prêt, exécutez la commande suivante pour démarrer le service :
Les services sont liés correctement
- La commande
updoit afficher les logs suivants :
Attaching to backend-1, frontend-1
backend-1 | 2025/11/10 18:20:29 Server starting on port 8080
frontend-1 | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
- il y a un délai avant que le service
frontendne démarre qui correspond au temps nécessaire pour que le servicebackendpasse àHealthy.
La solution est là... ⤵️
# Docker compose for 2 services with a dependance
services:
# Backend service
backend:
image: registry.gitlab.com/yodamad-workshops/2026/workshop-docker/demo_app/backend:main
ports:
- "8080:8080"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
# Frontend service
frontend:
image: registry.gitlab.com/yodamad-workshops/2026/workshop-docker/demo_app/frontend:main
ports:
- "3000:80"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/"]
interval: 30s
timeout: 10s
retries: 3
depends_on:
backend:
condition: service_healthy
restart: true
Dans le cadre de notre politique d'écoresponsabilité, on souhaite contrôler les ressources utilisées par nos services.
Configurer les ressources
Modifier la configuration des services backend et frontend pour définir une limite de ressources :
- CPU à
0.5 - Mémoire à
256Mpour chaque service
Les ressources sont configurées
- Grâce à la commande
docker stats, vous pouvez vérifier que les limites de ressources sont bien appliquées aux conteneursworkspaces-backend-1etworkspaces-frontend-1.
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
69a4a542013b workspaces-frontend-1 0.00% 15.61MiB / 256MiB 6.10% 6.72kB / 356kB 23.8MB / 1.74MB 9
494289cdcacc workspaces-backend-1 0.00% 5.047MiB / 256MiB 1.97% 3.2kB / 940B 201MB / 0B 7
- Vérifier via un
docker inspectque la limite est appliquée sur le CPU.
Mais pourquoi 500_000_000 ?
Cela revient à la multiplication : 100_000 * 5_000 où :
100000est lecpu-period(par défaut, qui équivaut à 100ms)5000est lecpu-quota(le nombre de périodes CPU allouées, ici 0.5 CPU = 5000 * 0.1ms)
La solution est là... ⤵️
# Docker compose for 2 services with resources management
services:
# Backend service
backend:
image: registry.gitlab.com/yodamad-workshops/2026/workshop-docker/demo_app/backend:main
ports:
- "8080:8080"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
deploy:
resources:
limits:
cpus: "0.5"
memory: 256M
# Frontend service
frontend:
image: registry.gitlab.com/yodamad-workshops/2026/workshop-docker/demo_app/frontend:main
ports:
- "3000:80"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/"]
interval: 30s
timeout: 10s
retries: 3
depends_on:
backend:
condition: service_healthy
restart: true
deploy:
resources:
limits:
cpus: "0.5"
memory: 256M
Vérifier sans resources (optionnel)
Pour voir la différence, vous pouvez temporairement supprimer la configuration des ressources dans le fichier docker-compose.yml, redémarrer les services avec docker compose up, et vérifier à nouveau les ressources avec docker stats et docker inspect.
Arrêter le service
Faites un bon vieux Ctrl+C dans le terminal où le service tourne pour arrêter le service.
🧺 Ranger ses affaires
On voit dans nos services que l'on a une partie commune sur la définition des ressources. Docker Compose propose un mécanisme pour factoriser cette configuration répétée.
Factoriser la configuration des resources
Restructurer le fichier docker-compose.yml pour avoir une block de configuration réutilisable pour les ressources, et l'appliquer aux services backend et frontend. Changer la configuration pour vous assurez que c'est bien pris en compte par exemple en mettant la mémoire à 128M et le CPU à 0.25.
Une fois que c'est prêt, exécutez la commande suivante pour démarrer le service :
Les services se déploient correctement avec les limites sur les ressources
- Les 2 services sont démarrés :
docker ps - Les limites de ressources sont bien appliquées :
docker statsetdocker inspect
La solution est là... ⤵️
# Docker compose for 2 services using an extension
x-resources: &resources
resources:
limits:
cpus: '0.25'
memory: 128M
services:
# Backend service
backend:
image: registry.gitlab.com/yodamad-workshops/2026/workshop-docker/demo_app/backend:main
ports:
- "8080:8080"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
deploy:
<<: *resources
# Frontend service
frontend:
image: registry.gitlab.com/yodamad-workshops/2026/workshop-docker/demo_app/frontend:main
ports:
- "3000:80"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/"]
interval: 30s
timeout: 10s
retries: 3
depends_on:
backend:
condition: service_healthy
restart: true
deploy:
<<: *resources
Tant que l'on a que 2 services, c'est facile à gérer, mais dès que l'on en a plus, le fichier docker-compose.yml peut vite devenir illisible.
Docker Compose permet de diviser la configuration en plusieurs fichiers pour une meilleure organisation.
⚠️ Les extensions ne sont accessibles que localement au fichier dans lequel elles sont définies... Il va donc falloir trouver une solution pour réutiliser cette configuration pour qu'elle soit réutilisable dans plusieurs services définis dans des fichiers différents.
Diviser le fichier de configuration
-
Dans un nouveau dossier
mon_appli:- Diviser le fichier existant en 2 pour avoir un fichier par service.
- Ajouter un 3e fichier
base.ymlqui reprend la configuration des ressources - Référencer
base.ymldans les 2 autres et utiliser le mot cléextenddans les services ...
-
Mettre à jour le fichier
docker-compose.ymlprincipal pour inclure les fichiers de configuration des servicesbackendetfrontend.
Une fois que c'est prêt, exécutez la commande suivante pour démarrer le service :
Le compose déploie bien les 2 services comme avant
La solution est là... ⤵️
include:
- mon_appli/docker-compose.backend.yml
- mon_appli/docker-compose.frontend.yml
x-resources: &resources
resources:
limits:
cpus: '0.5'
memory: 256M
services:
base-service:
deploy:
<<: *resources
services:
# Frontend service
frontend:
extends:
file: base.yml
service: base-service
image: registry.gitlab.com/yodamad-workshops/2026/workshop-docker/demo_app/frontend:main
ports:
- "3000:80"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/"]
interval: 30s
timeout: 10s
retries: 3
depends_on:
backend:
condition: service_healthy
restart: true
services:
# Backend service
backend:
extends:
file: base.yml
service: base-service
image: registry.gitlab.com/yodamad-workshops/2026/workshop-docker/demo_app/backend:main
ports:
- "8080:8080"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
🎭 Adapter son déploiement
Il arrive parfois que l'on ait besoin de services complémentaires dans des environnements de dev, par exemple. Imaginons ici que l'on souhaite, en plus de nos 2 services, en déployer un autre mais uniquement quand on est en dev. On aimerait un service qui expose la documentation de l'API de notre backend, exporté au format OpenAPI. Mais on ne veut pas déployer ce service à chaque fois.
Déployer la doc uniquement si demandé
- Créer dans
mon_appliun nouveau fichierdocker-compose.openapi.ymlqui- déploie l'image
registry.gitlab.com/yodamad-workshops/2026/workshop-docker/demo_app/backend:openapi - expose le port
9000lié au port8000du conteneur - uniquement pour un profil
dev
- déploie l'image
- Modifier le
docker-compose.ymlprincipal pour prendre en compte ce nouveau fichier
Dans un premier temps,
Une fois que c'est prêt, exécutez la commande suivante pour démarrer le service :
Le service openapi n'est pas déployé
Modifiez maintenant la commande pour que le service soit déployé
Le service openapi est déployé et accessible
-
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ef3468967d6c registry.gitlab.com/yodamad-workshops/2026/workshop-docker/demo_app/backend:openapi "python -m http.serv…" 2 minutes ago Up 2 minutes (healthy) 0.0.0.0:9000->8000/tcp, [::]:9000->8000/tcp compose-openapi-1 99ee7b0efca8 registry.gitlab.com/yodamad-workshops/2026/workshop-docker/demo_app/frontend:main "/docker-entrypoint.…" 4 minutes ago Up 2 minutes (healthy) 0.0.0.0:3000->80/tcp, [::]:3000->80/tcp compose-frontend-1 31eda5abd9df registry.gitlab.com/yodamad-workshops/2026/workshop-docker/demo_app/backend:main "./server" 4 minutes ago Up 2 minutes (healthy) 0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp compose-backend-1 -
Via l'onglet
Portsde votre workspace, vous pouvez forwarder le port9000pour accéder à l'url, puis cliquer sur l'icone "🌍" pour afficher l'url, on a bien le contrat OpenAPI disponible
La solution est là... ⤵️
services:
openapi:
image: registry.gitlab.com/yodamad-workshops/2026/workshop-docker/demo_app/backend:openapi
ports:
- "9000:8000"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8000/"]
interval: 30s
timeout: 10s
retries: 1
profiles:
- dev
Autre cas de figure, comment gérer des environnements différents (dev, prod, test, ...) ? On peut imaginer que l'on souhaite une configuration de ressources différente entre dev et prod. Pour cela, Compose propose un système de merge.
Configurer des environnements différents
- Dans le répertoire
mon_appli, créer un fichierprod.ymlqui:- surcharge la configuration des ressources pour les services
backendetfrontendavec :- CPU à
1 - Mémoire à
512M
- CPU à
- force l'utilisation de ces nouvelles ressources sur nos 2 services
- surcharge la configuration des ressources pour les services
- Adapter la commande de déploiement pour prendre en compte cette nouvelle configuration.
Les services sont déployés avec la configuration de ressources de production
- Les 2 services sont démarrés :
docker ps - Les limites de ressources sont bien appliquées :
docker statsetdocker inspect
La solution est là... ⤵️
🧑🏻💻 Et le dev dans tout ca ?!
Jusqu'à présent, on a déployé des services basés sur des images pré-construites et stockées dans un registre distant. Mais en phase de développement, on pourrait vouloir reconstruire/redéployer à la volée notre application à chaque modification de code source.
Pourquoi faire ? Cela peut être pratique si l'on ne veut pas avoir à installer les middlewares ou autre localement sur sa machine (par exemple quand on est sous Windows 😇 #gentilTroll).
Pour cela, Compose propose un mécanisme de watch qui permet de surveiller les changements dans le code source et de reconstruire/redéployer automatiquement les services concernés.
Pour tester cela, on a préparé un petit projet de test dev_api.
Configurer Compose avec la configuration watch
Dans le répertoire dev_api :
* Créer un fichier docker-compose.dev.yml qui :
* Déploie un service api basé sur le Dockerfile présent dans le projet
* Mappe le port 8080 du conteneur vers le port 5000 de la machine hôte
* Configure le service pour qu'il surveille les changements dans le répertoire local ./ et rebuild automatiquement le service en cas de modification du code source.
* Déployer le service
Le service api est déployé et fonctionne
- La commande
updoit afficher les logs suivants :
[+] Running 3/3
✔ api Building 0.0s
✔ 5f70bf18a086 Pull complete 0.5s
✔ 2b4f6b8f6c8d Pull complete 0.7s
✔ e7d92cdc71fe Pull complete 0.9s
✔ 1b930d010525 Pull complete 1.2s
✔ 2c4ed5fa8f3e Pull complete 1.4s
[+] Running 2/2
✔ Network compose_default Created 0.1s
✔ Container compose-api-1 Created 0.2s
Attaching to api-1
api-1 | Starting development server at http://localhost:8080
- Via l'onglet
Portsde votre workspace, vous pouvez forwarder le port5000pour accéder à l'url, puis cliquer sur l'icone "🌍" pour afficher l'url et compléter par/api/hello, vous devez voir le messageHello, Developer!.
La solution est là... ⤵️
Maintenant, on va modifier l'application pour valider que le watch fonctionne correctement. Même si le code est en Go, pas de panique, on va juste modifier un message texte ligne 26 du fichier main.go :
Une fois le fichier sauvegardé, vous devez voir dans les logs de docker compose que le service est en train de se reconstruire automatiquement.
Le service api se reconstruit automatiquement
- Dans les logs de
docker compose, vous devez voir un message indiquant que le service est en train de se reconstruire :
⦿ Rebuilding service(s) ["api"] after changes were detected...
[...]
api-1 has been recreated
api-1 exited with code 2
api-1 | 2025/11/14 15:30:01 Server starting on port 8080...
- Après la reconstruction, en rafraîchissant la page
/api/hello, vous devez voir le nouveau messageHello World !.