Guide Technique — DevOps

Pipeline CI/CD
Du commit au serveur

Automatiser les tests, le build Docker et le déploiement zero-downtime avec GitHub Actions — du push Git à la mise en production en quelques minutes.

AuteurSOL-IT Solutions Informatiques
Version1.0 — 2026
NiveauIntermédiaire
Stack couverteGitHub Actions · Docker · VPS · Kubernetes

Table des matières

  1. Vue d'ensemble et philosophie CI/CD Principes, flux, environnements
  2. Structure du projet et prérequis Arborescence, secrets GitHub
  3. Pipeline CI — Tests et build Lint, tests unitaires, build Docker
  4. Déploiement sur VPS (zero-downtime) SSH, Docker Compose, health check
  5. Déploiement sur Kubernetes kubectl, rollout, rollback
  6. Gestion des environnements dev, staging, production
  7. Notifications et observabilité Slack, email, status badges
  8. Checklist de mise en place Points de contrôle finaux
Chapitre 1

Vue d'ensemble et philosophie CI/CD

Qu'est-ce que CI/CD ?

CI (Intégration Continue) : chaque push de code déclenche automatiquement les tests et le build, garantissant que le code intégré ne casse rien.

CD (Déploiement Continu) : si le CI passe, le code est automatiquement déployé en production (ou staging), sans intervention manuelle.

Le flux complet

💻
git push
Developer
🔍
Lint + Tests
CI Runner
🐳
Build Docker
Registry
🚀
Deploy
VPS / K8s
Health Check
Monitoring
🔔
Notification
Slack / Email

Les trois environnements

  • Development — branche develop, déployé automatiquement sur un serveur de test
  • Staging — branche staging, miroir de la production pour validation finale
  • Production — branche main, déploiement après validation manuelle (approval gate)
💡 Règle d'or : Le pipeline doit échouer rapidement (fast fail). Si les tests ne passent pas, le déploiement n'a pas lieu, peu importe l'urgence.
Chapitre 2

Structure du projet et prérequis

Arborescence recommandée

# Structure de votre dépôt
mon-projet/
├── .github/
│   └── workflows/
│       ├── ci.yml          # Tests + build (sur tous les pushes)
│       ├── deploy-staging.yml   # Deploy staging (branche staging)
│       └── deploy-prod.yml      # Deploy production (branche main)
├── docker/
│   ├── Dockerfile
│   └── nginx.conf
├── docker-compose.yml          # Composition locale
├── docker-compose.prod.yml     # Composition production
└── src/

Secrets GitHub à configurer

Dans votre dépôt GitHub → Settings → Secrets and variables → Actions :

PROD_SSH_HOST PROD_SSH_USER PROD_SSH_KEY REGISTRY_TOKEN SLACK_WEBHOOK_URL APP_ENV_PROD

Générer et configurer la clé SSH de déploiement

# Sur votre machine locale : générer une clé dédiée au CI
ssh-keygen -t ed25519 -C "github-actions-deploy" -f ~/.ssh/deploy_key -N ""

# Copier la clé publique sur le VPS
ssh-copy-id -i ~/.ssh/deploy_key.pub deployer@votre-vps.com

# Ajouter la clé privée dans GitHub Secrets
# PROD_SSH_KEY = contenu de ~/.ssh/deploy_key (clé PRIVÉE)
cat ~/.ssh/deploy_key
⚠️ La clé privée SSH va dans les Secrets GitHub (chiffrés). Ne jamais la committer dans le code.
Chapitre 3

Pipeline CI — Tests et build

.github/workflows/ci.yml

 CI — Tests & Build


  push:
    branches: ['**']   # Sur toutes les branches
  pull_request:
    branches: [main, staging]


  test:
    name: Tests & Lint
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_DB: test_db
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: secret
        options: >-
          --health-cmd pg_isready
          --health-interval 5s
          --health-timeout 3s
          --health-retries 5
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup PHP 8.3
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'
          extensions: pdo_pgsql, redis, bcmath
          coverage: xdebug

      - name: Install Composer dependencies
        run: composer install --no-interaction --prefer-dist --optimize-autoloader

      - name: Prepare Laravel environment
        run: |
          cp .env.testing .env
          php artisan key:generate
          php artisan migrate --force

      - name: Run PHPUnit tests
        run: php artisan test --parallel --coverage-clover coverage.xml

      - name: PHP CS Fixer (Lint)
        run: vendor/bin/php-cs-fixer fix --dry-run --diff

  build:
    name: Build & Push Docker image
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging'
    steps:
      - uses: actions/checkout@v4

      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: |
            ghcr.io/${{ github.repository }}:${{ github.sha }}
            ghcr.io/${{ github.repository }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max
💡 Cache Docker layerscache-from/cache-to: type=gha utilise le cache GitHub Actions pour accélérer les builds répétés de 60 à 80%.
Chapitre 4

Déploiement sur VPS — Zero-downtime

docker-compose.prod.yml


  app:
    image: ghcr.io/monorg/mon-app:${IMAGE_TAG}
    restart: unless-stopped
    env_file: .env.production
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 30s
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`app.example.com`)"
      - "traefik.http.routers.app.tls.certresolver=letsencrypt"

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]
      interval: 5s


  pgdata:

.github/workflows/deploy-prod.yml

 Deploy — Production


  push:
    branches: [main]


  deploy:
    name: Deploy to VPS (zero-downtime)
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://app.example.com
    steps:
      - uses: actions/checkout@v4

      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.PROD_SSH_HOST }}
          username: ${{ secrets.PROD_SSH_USER }}
          key: ${{ secrets.PROD_SSH_KEY }}
          script: |
            # Pull la nouvelle image
            docker pull ghcr.io/${{ github.repository }}:${{ github.sha }}

            # Mise à jour zero-downtime avec Docker Compose
            export IMAGE_TAG=${{ github.sha }}
            docker compose -f /opt/mon-app/docker-compose.prod.yml up -d --no-deps app

            # Attendre le health check
            echo "Waiting for health check..."
            timeout 60 bash -c 'until docker inspect --format="{{.State.Health.Status}}" mon-app-app-1 | grep -q healthy; do sleep 3; done'

            # Migrations Laravel
            docker compose -f /opt/mon-app/docker-compose.prod.yml exec -T app php artisan migrate --force

            # Nettoyage des anciennes images
            docker image prune -f

            echo "✅ Deployment successful: ${{ github.sha }}"
Zero-downtime garanti : Docker Compose recrée uniquement le container app, la base de données et les volumes restent intacts. Le health check confirme que le nouveau container répond avant de considérer le déploiement réussi.

Rollback en un seul commit

# En cas de problème, revenir au commit précédent
git revert HEAD --no-edit
git push origin main
# Le pipeline se relance automatiquement avec la version précédente
Chapitre 5

Déploiement sur Kubernetes

Workflow complet GitHub Actions → K8s

 Deploy — Kubernetes


  push:
    branches: [main]


  deploy-k8s:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure kubectl
        uses: azure/k8s-set-context@v3
        with:
          kubeconfig: ${{ secrets.KUBECONFIG }}

      - name: Inject image tag into manifests
        run: sed -i "s|IMAGE_TAG|${{ github.sha }}|g" k8s/deployment.yaml

      - name: Apply manifests
        run: kubectl apply -f k8s/ -n production

      - name: Wait for rollout
        run: kubectl rollout status deployment/mon-app -n production --timeout=120s

      - name: Run post-deploy migrations
        run: |
          kubectl exec deployment/mon-app -n production -- php artisan migrate --force

      - name: Rollback on failure
        if: failure()
        run: kubectl rollout undo deployment/mon-app -n production
💡 L'étape Rollback on failure s'exécute automatiquement si une étape précédente échoue, grâce à if: failure().
Chapitre 6

Gestion des environnements

Stratégie de branches recommandée

# Gitflow simplifié pour CI/CD

main        →  Production  (protégée, PR obligatoire, approval gate)
staging     →  Staging     (déploiement automatique)
develop     →  Dev         (déploiement automatique)
feature/*   →  CI uniquement (tests + build, pas de déploiement)

Variables d'environnement par env

# .env.staging
APP_ENV=staging
APP_URL=https://staging.example.com
DB_DATABASE=app_staging
LOG_LEVEL=debug
CACHE_DRIVER=redis
---
# .env.production
APP_ENV=production
APP_URL=https://app.example.com
APP_DEBUG=false
LOG_LEVEL=error
CACHE_DRIVER=redis

Approval gate pour la production

Dans GitHub → Settings → Environments → production : activer Required reviewers. Le déploiement en production ne peut se lancer qu'après approbation manuelle d'un responsable.

⚠️ Ne jamais déployer directement en production depuis une branche feature. Toujours passer par staging d'abord.
Chapitre 7

Notifications et observabilité

Notification Slack sur succès/échec

# Ajouter en fin de workflow
      - name: Notify Slack — Success
        if: success()
        uses: slackapi/slack-github-action@v1
        with:
          channel-id: '#deployments'
          slack-message: |
            ✅ *${{ github.repository }}* déployé en production
            Commit: `${{ github.sha }}` par @${{ github.actor }}
            ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
        env:
          SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}

      - name: Notify Slack — Failure
        if: failure()
        uses: slackapi/slack-github-action@v1
        with:
          channel-id: '#deployments'
          slack-message: |
            🚨 *ÉCHEC DÉPLOIEMENT* — ${{ github.repository }}
            Commit: `${{ github.sha }}` — rollback automatique déclenché
        env:
          SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}

Badge de statut dans le README

# Ajouter dans README.md
![CI](https://github.com/monorg/mon-repo/actions/workflows/ci.yml/badge.svg)
![Deploy](https://github.com/monorg/mon-repo/actions/workflows/deploy-prod.yml/badge.svg)
Chapitre 8

Checklist de mise en place

Avant d'activer le pipeline

  • Secrets GitHub configurés : SSH key, tokens registry, variables d'env production
  • Branche main protégée : PR obligatoire, CI doit passer avant le merge
  • Fichier .env.testing présent et fonctionnel pour les tests en CI
  • Route /health implémentée dans l'application (retourne 200 si OK)
  • Health check configuré dans docker-compose.prod.yml
  • Approval gate activé sur l'environnement production GitHub
  • Rollback testé manuellement : kubectl rollout undo ou revert Git
  • Notification Slack ou email configurée sur échec
  • Logs centralisés et accessibles post-déploiement
  • Pipeline testé sur une branche feature avant de l'activer sur main

Commandes de vérification post-déploiement

# Vérifier l'état du container
docker compose -f docker-compose.prod.yml ps

# Logs en temps réel
docker compose -f docker-compose.prod.yml logs -f app --tail=50

# Health check manuel
curl -f https://app.example.com/health && echo "✅ OK" || echo "❌ FAIL"

# Vérifier la version déployée
curl https://app.example.com/health | jq '.version'