Introduction
Automatiser les flux de travail Docker Compose dans les pipelines CI/CD peut améliorer considérablement la cohérence des déploiements et réduire les erreurs manuelles. Pourtant, de nombreuses équipes peinent à intégrer en toute sécurité des applications multi-conteneurs dans des pipelines automatisés. Ce guide pratique présente un exemple complet : de l'inventaire des versions et de l'environnement, à la configuration sécurisée, la vérification, la gestion des échecs et une checklist opérationnelle. Vous apprendrez à construire un pipeline qui valide votre fichier Compose, construit des images avec des identifiants uniques, exécute des tests, promeut à travers les étapes et revient en arrière en toute sécurité en cas de problème. En adoptant ces pratiques, votre processus de déploiement devient reproductible, auditable et moins sujet aux erreurs.
Nous utiliserons une pile applicative web représentative composée d'un service web Node.js, d'une base de données PostgreSQL et d'un cache Redis, définie dans un fichier docker-compose.yml. Chaque commande est montrée telle qu'elle apparaîtrait dans un script de pipeline CI/CD, avec les sorties attendues et les cas d'échec expliqués.
Inventaire des versions et de l'environnement
Avant d'automatiser quoi que ce soit, documentez les versions exactes et la topologie que vous utiliserez. Cela réduit les surprises causées par les différences entre le développement local et les environnements CI/CD. Pour ce guide, nous supposons :
- Docker Engine 24.0 ou ultérieur
- Plugin Docker Compose v2.20 ou ultérieur
- Git pour le contrôle de source
- Une plateforme CI/CD qui prend en charge l'exécution de commandes shell (par exemple, GitHub Actions, GitLab CI, Jenkins)
- Un hôte cible (par exemple, une VM ou un serveur bare metal) avec accès SSH, ou un contexte Docker local pour les tests
Vérifiez vos versions locales avec :
docker --version
# Docker version 24.0.7, build afdd53b
docker compose version
# Docker Compose version v2.21.0
Si votre exécuteur CI utilise une version différente, incluez la sortie de docker compose version dans les journaux du pipeline pour chaque exécution. Figez les versions de Docker et Compose dans votre configuration CI pour éviter un comportement inattendu. Par exemple, dans GitHub Actions, vous pouvez installer des versions spécifiques :
- name: Install Docker Compose
run: |
sudo curl -L "https://github.com/docker/compose/releases/download/v2.21.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
Votre topologie applicative peut être un simple service web avec une base de données et un cache, définie dans un fichier docker-compose.yml comme celui-ci :
services:
web:
build: .
image: myapp:${TAG:-latest}
ports:
- "80:80"
depends_on:
- db
- redis
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- db-data:/var/lib/postgresql/data
redis:
image: redis:7
volumes:
db-data:
Assurez-vous que tous les services sont conteneurisés et que le fichier Compose fonctionne localement avant de passer au CI/CD. Exécutez docker compose up -d sur votre machine locale et testez l'application de bout en bout. Le premier pilote utile doit être étroit, mesurable et facile à inspecter localement avant le déploiement. Par exemple, commencez par un seul service qui exécute un contrôle de santé et renvoie une réponse prévisible. Une fois que cela fonctionne, ajoutez la base de données et le cache.
Chemin de configuration sécurisé
La sécurité dans le CI/CD pour Docker Compose vient de la réalisation de petits changements ciblés et de la validation de chaque étape. Évitez d'introduire une orchestration complexe au début. Adoptez plutôt un pipeline qui :
- Lint et valide le fichier Compose.
- Construit des images avec un identifiant unique (par exemple, le SHA du commit).
- Exécute des tests d'intégration dans un environnement Compose dédié.
- Déploie vers la préproduction, vérifie la santé, puis promeut vers la production.
- Conserve les images précédentes pour un retour en arrière instantané.
Utilisez des fichiers de surcharge spécifiques à l'environnement pour éviter de modifier le fichier Compose de base pour chaque environnement. Par exemple, votre docker-compose.yml de base peut définir le service web avec une étiquette par défaut et le port 80. Pour la production, vous surchargez le port et ajoutez des limites de ressources :
# docker-compose.yml de base
services:
web:
image: myapp:${TAG:-latest}
ports:
- "80:80"
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
# docker-compose.prod.yml (surcharge)
services:
web:
image: myapp:${TAG}
ports:
- "443:443"
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
Dans votre pipeline, définissez TAG sur le SHA du commit et exécutez :
docker compose -f docker-compose.yml -f docker-compose.prod.yml config
La sortie attendue est le YAML fusionné avec les variables substituées, sans erreurs. Testez la substitution de variables avec un essai à sec :
TAG=abc123 docker compose config --quiet
# Aucune sortie signifie succès
Si le fichier Compose est invalide, docker compose config sort avec un statut non nul et affiche une erreur comme :
services.web.ports.0 must be a string
Attrapez cela dans votre script CI et faites échouer la construction.
Exemple de pipeline CI/CD avec GitHub Actions
Voici un workflow GitHub Actions complet qui construit, teste et promeut une application Docker Compose. Il est annoté avec des explications.
name: Docker Compose CI/CD
on:
push:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker
run: |
docker --version
docker compose version
- name: Set image tag
run: echo "TAG=${GITHUB_SHA::8}" >> $GITHUB_ENV
- name: Validate Compose file
run: docker compose config --quiet
- name: Build images
run: docker compose build
- name: Run tests
run: docker compose run --rm web npm test
- name: Push images to registry
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker compose push
- name: Deploy to staging
run: |
ssh staging "TAG=$TAG docker compose -f docker-compose.yml -f docker-compose.staging.yml up -d"
- name: Verify staging
run: |
sleep 10
curl -fsS http://staging/health
- name: Promote to production
run: |
ssh production "TAG=$TAG docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d"
- name: Verify production
run: |
sleep 10
curl -fsS http://production/health
Ce workflow utilise SSH pour exécuter les commandes de déploiement sur les hôtes de préproduction et de production. Vous pouvez également utiliser des contextes Docker ou un démon Docker distant pour une approche plus rationalisée, mais SSH est simple et fonctionne sur la plupart des plateformes.
Vérification et diagnostics
La vérification automatisée est critique. Après le déploiement, exécutez des tests de fumée contre les services en cours d'exécution. Pour un service web, utilisez curl pour vérifier les points de terminaison de santé :
curl -fsS http://localhost/health
# {"status":"ok"}
Si le point de terminaison renvoie un code non-2xx ou échoue à se connecter, le pipeline doit marquer le déploiement comme échoué. Utilisez docker compose ps pour inspecter les états des services :
docker compose ps
# NAME COMMAND SERVICE STATUS PORTS
# myapp-web-1 "/docker-entrypoint...." web running 0.0.0.0:80->80/tcp
# myapp-db-1 "docker-entrypoint.s..." db running 5432/tcp
# myapp-redis-1 "docker-entrypoint.s..." redis running 6379/tcp
De plus, vérifiez les journaux pour les erreurs :
docker compose logs --tail=50 web
Pour les connexions à la base de données, vous pouvez exécuter une requête rapide depuis le réseau :
docker exec -it <container-name> psql -U postgres -c "SELECT 1;"
# ?column?
# ----------
# 1
Vous pouvez également exécuter un conteneur de test dédié qui exécute des tests d'intégration et sort avec un code. Par exemple :
docker compose run --rm web npm test
# > test
# > jest --passWithNoTests
# PASS
Utilisez le code de sortie pour faire échouer le pipeline si les tests ne passent pas. Dans votre script CI, enveloppez la commande de test avec une vérification :
if ! docker compose run --rm web npm test; then
echo "Tests failed"
exit 1
fi
Contrôles de santé intégrés
Ajoutez des contrôles de santé à vos services Compose pour rendre la vérification plus robuste. Pour le service web, utilisez un contrôle de santé qui frappe le point de terminaison /health :
services:
web:
image: myapp:${TAG}
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 30s
Ensuite, utilisez docker compose up -d --wait (disponible dans Compose v2.20+) pour bloquer jusqu'à ce que les services soient sains ou échouent :
docker compose up -d --wait
# Container myapp-web-1 Healthy
# Container myapp-db-1 Healthy
# Container myapp-redis-1 Healthy
Si un conteneur ne devient pas sain dans la fenêtre de tentatives, docker compose up --wait sort avec un code non nul.
Modes de défaillance et récupération
Même avec une planification minutieuse, des échecs surviennent. Les modes de défaillance courants incluent :
- Échecs de construction d'image dus à des dépendances manquantes ou des problèmes réseau
- Erreurs de syntaxe du fichier Compose
- Échecs de démarrage de service (par exemple, conflits de port, variables d'environnement manquantes)
- Échecs de contrôle de santé après le déploiement
- Erreurs de migration de base de données
Concevez votre pipeline pour échouer rapidement et laisser une trace claire. Par exemple, si une construction échoue, capturez le journal de construction et joignez-le à l'exécution CI. Dans GitHub Actions, vous pouvez utiliser la condition if: failure() pour téléverser les journaux comme artefacts :
- name: Capture build logs on failure
if: failure()
run: |
docker compose logs > compose-logs.txt
- name: Upload logs
if: failure()
uses: actions/upload-artifact@v3
with:
name: compose-logs
path: compose-logs.txt
Le retour en arrière est essentiel. Parce que vous étiquetez les images avec des identifiants uniques, revenir en arrière est aussi simple que de redéployer l'étiquette précédente. Stockez la dernière étiquette valide connue dans un fichier ou une variable d'environnement. Par exemple, dans un script de déploiement :
# Déployer la nouvelle étiquette
TAG=$NEW_TAG docker compose up -d
# Attendre la santé
sleep 10
if ! curl -fsS http://localhost/health; then
echo "Deployment failed, rolling back to $LAST_GOOD_TAG"
TAG=$LAST_GOOD_TAG docker compose up -d
fi
Si la nouvelle version introduit une migration de base de données qui ne peut pas être annulée automatiquement, vous pouvez avoir besoin d'une migration de retour distincte ou d'une restauration à partir de la sauvegarde. Documentez ces procédures et testez-les périodiquement. Dans votre base de code, gardez les scripts de migration versionnés et réversibles lorsque c'est possible. Pour une base de données PostgreSQL utilisant un outil comme node-pg-migrate, une migration de retour serait un fichier séparé qui annule les changements.
Une autre approche de récupération consiste à garder le conteneur précédent en cours d'exécution et à basculer le trafic uniquement après la vérification de santé. Cela peut être fait avec un équilibreur de charge ou en utilisant la mise à jour roulante intégrée de Docker avec des contrôles de santé. Pour des configurations Compose simples sur un seul hôte, vous pouvez utiliser la commande docker compose up -d --no-deps web pour recréer uniquement le conteneur web, en laissant les autres en cours d'exécution. Cependant, avec une étiquette unique, une nouvelle version échouée peut être annulée en exécutant TAG=$LAST_GOOD_TAG docker compose up -d.
Gestion des conflits de port
Les conflits de port se produisent lorsqu'un nouveau conteneur tente de se lier à un port déjà utilisé. Par exemple, si un ancien conteneur tourne toujours sur le port 80, le nouveau échoue :
Error response from daemon: driver failed programming external connectivity on endpoint myapp-web-1: Bind for 0.0.0.0:80 failed: port is already allocated
Évitez cela en utilisant un port hôte différent pour la nouvelle version, ou en arrêtant d'abord les anciens conteneurs. Dans une configuration Compose, vous pouvez déployer une nouvelle version à côté de l'ancienne (bleu-vert) en utilisant un nom de projet différent :
docker compose -p myapp-blue up -d
# Tester le bleu
curl -fsS http://localhost:8080/health # si le bleu est mappé sur 8080
# Basculer le trafic
# Arrêter le vert
TAG=$LAST_GOOD_TAG docker compose -p myapp-green down
Cela nécessite un équilibreur de charge ou un proxy inverse pour basculer le trafic, mais cela peut réduire les temps d'arrêt.
Pièges courants et comment les éviter
Lors de l'automatisation de Docker Compose dans le CI/CD, les équipes rencontrent souvent ces pièges. Les comprendre vous aide à concevoir un pipeline plus robuste.
1. Utiliser des étiquettes « latest » en production
Pourquoi cela arrive : Il est pratique d'utiliser latest et d'éviter de gérer les étiquettes.
Pourquoi c'est un problème : Vous ne pouvez pas revenir en arrière de manière fiable vers une version spécifique, et vous pourriez accidentellement déployer une construction non testée.
Comment l'éviter : Utilisez toujours des étiquettes uniques dérivées du SHA du commit ou du numéro de construction. Imposez cela en définissant TAG dans votre environnement CI et en l'utilisant dans votre fichier Compose via ${TAG}.
2. Surcharger incorrectement les fichiers Compose
Pourquoi cela arrive : Les équipes modifient le docker-compose.yml de base pour chaque environnement, conduisant à des divergences et des conflits de fusion.
Comment l'éviter : Utilisez des fichiers de surcharge spécifiques à l'environnement (docker-compose.prod.yml) et combinez-les avec les drapeaux -f. Ne modifiez jamais le fichier de base pour des paramètres spécifiques à l'environnement.
3. Ignorer les contrôles de santé dans le pipeline
Pourquoi cela arrive : Pour gagner du temps ou parce que le test de fumée est considéré comme suffisant.
Comment l'éviter : Définissez des contrôles de santé Docker pour chaque service et utilisez docker compose up -d --wait pour que le pipeline bloque jusqu'à ce que les services soient sains. Cela attrape les échecs de démarrage tôt.
4. Pas de retour en arrière automatisé
Pourquoi cela arrive : Le retour en arrière est considéré comme une opération manuelle, ou l'équipe suppose que les déploiements échouent rarement.
Comment l'éviter : Scénarisez le retour en arrière en utilisant la dernière étiquette valide connue. Stockez l'étiquette dans un fichier ou une variable d'environnement sur l'hôte cible, et assurez-vous que votre pipeline peut la redéployer automatiquement lorsque les contrôles de santé échouent.
5. Ignorer les migrations de base de données
Pourquoi cela arrive : Les équipes supposent que les changements d'application ne nécessitent pas de changements de schéma, ou elles exécutent les migrations manuellement.
Comment l'éviter : Incluez des étapes de migration dans votre pipeline, exécutez les migrations à l'intérieur d'un conteneur et versionnez vos scripts de migration. Pour Postgres, vous pourriez utiliser docker compose run --rm web npm run migrate. Gardez les procédures de sauvegarde et de retour en arrière documentées et testées.
Checklist opérationnelle
Utilisez cette checklist pour chaque déploiement. Chaque étape a un seul responsable (généralement l'ingénieur de release ou le développeur principal) et est revisitée à chaque déploiement, avec un audit mensuel pour assurer la conformité.
| Étape | Commande ou action | Résultat attendu | Responsable |
|---|---|---|---|
| Valider le fichier Compose | docker compose config --quiet | Aucune erreur | Ingénieur de release : Priya Shah |
| Construire l'image | docker compose build | Image construite, étiquetée | Ingénieur de release : Priya Shah |
| Exécuter les tests | docker compose run --rm web npm test | Code de sortie 0 | Responsable QA : Marcus Chen |
| Déployer vers la préproduction | TAG=staging docker compose up -d | Services en cours d'exécution | Ingénieur de release : Priya Shah |
| Vérifier la préproduction | curl -fsS http://staging/health | Renvoie 200 | Responsable QA : Marcus Chen |
| Déployer vers la production | TAG=$(git rev-parse HEAD) docker compose up -d | Services en cours d'exécution | Ingénieur de release : Priya Shah |
| Vérifier la production | curl -fsS http://prod/health | Renvoie 200 | Responsable des opérations : Sofia Alvarez |
| Surveiller les journaux | docker compose logs -f --tail=100 | Aucune erreur critique | Responsable des opérations : Sofia Alvarez |
Après le déploiement, gardez un enregistrement de l'étiquette de l'image et du SHA du commit. Cela facilite le retour en arrière rapide et l'audit. Révisez cette checklist mensuellement lors d'une réunion post-déploiement avec l'équipe d'ingénierie, et ajustez les étapes à mesure que le pipeline évolue.
Conclusion
Automatiser Docker Compose avec des pipelines CI/CD est réalisable avec une planification minutieuse et une adoption incrémentale. Commencez par inventorier votre environnement et documenter les versions. Ensuite, implémentez des pratiques de configuration sécurisées telles que l'utilisation de fichiers de surcharge et d'étiquettes d'image uniques. Intégrez des étapes de vérification dans votre pipeline et préparez-vous aux échecs avec des procédures de retour en arrière et des contrôles de santé. Utilisez la checklist opérationnelle pour assurer la cohérence et attribuer des responsabilités claires. En suivant ces pratiques, vous pouvez obtenir des déploiements fiables, reproductibles et auditables. Commencez par un petit pilote, mesurez le succès et étendez progressivement.