Skip to Content

Chapitre 15.4 — Déploiement & CI/CD

Partie 15 : Expert — Chapitre 4/9 Version de référence : Symfony 7.4 / FrankenPHP / GitHub Actions.

⏱️ TL;DR

  • Une image Docker de prod multi-étapes : étape build (composer install --no-dev, asset-map:compile) → étape runtime légère (FrankenPHP).
  • Séquence de déploiement : récupérer le code → composer install --no-devmigrationscache:clear/warmup → compiler les assets → redémarrer les workers → recharger le serveur.
  • Zéro-downtime : des migrations rétrocompatibles, un déploiement rolling, des health checks.
  • Le CD (déploiement continu) : un job GitHub Actions déploie sur main si la CI passe.
  • Cibles : Platform.sh/Upsun (PaaS Symfony-natif), VPS + Docker, Kubernetes.
  • Pour un dev Next.js : c’est votre pipeline de déploiement (build image → push → deploy), avec les étapes spécifiques Symfony (migrations, cache, workers).

🎯 Objectifs

  • Construire une image Docker de production.
  • Ordonner correctement les étapes de déploiement.
  • Viser un déploiement sans coupure.
  • Automatiser le déploiement (CD).

1. L’image Docker de production (multi-étapes)

Une image multi-étapes sépare la construction (outils de build) du runtime (image finale légère) :

# Dockerfile # --- étape build --- FROM dunglas/frankenphp:1-php8.4 AS builder RUN install-php-extensions pdo_pgsql intl opcache redis WORKDIR /app COPY composer.* ./ RUN composer install --no-dev --no-scripts --prefer-dist --no-progress COPY . . RUN composer dump-autoload --optimize --classmap-authoritative \ && php bin/console asset-map:compile # --- étape runtime --- FROM dunglas/frankenphp:1-php8.4 RUN install-php-extensions pdo_pgsql intl opcache redis WORKDIR /app COPY --from=builder /app /app ENV APP_ENV=prod APP_DEBUG=0 # FrankenPHP sert public/ (worker mode configurable)

Points clés : --no-dev (pas de dépendances de dev en prod), autoloader optimisé, assets compilés (chapitre 7.3), APP_ENV=prod.

2. La séquence de déploiement

L’ordre des étapes compte. Sur chaque nouvelle version :

  • Migrations : appliquées avant que le nouveau code ne serve (mais compatibles avec l’ancien pendant la transition — voir §3).
  • Cache : cache:warmup en prod pour ne pas payer la première requête.
  • Workers : messenger:stop-workers pour qu’ils rechargent le nouveau code (Partie 12.2).

⚠️ Piège migrations : lancer une migration cassante (renommer/supprimer une colonne encore utilisée par l’ancien code) pendant un déploiement rolling casse les requêtes de l’ancienne version encore active. Règle : les migrations doivent être rétrocompatibles — on procède en deux temps (expand/contract) : d’abord ajouter (nouvelle colonne, sans retirer l’ancienne), déployer le code qui l’utilise, puis un déploiement ultérieur retire l’ancienne. Jamais « ajouter et casser » dans le même coup.

3. Vers le zéro-downtime

Un déploiement sans coupure repose sur :

  • des migrations rétrocompatibles (pattern expand/contract ci-dessus) ;
  • un déploiement rolling (on remplace les instances progressivement, en gardant des instances saines) ;
  • des health checks (/health, chapitre 15.5) : l’orchestrateur n’envoie du trafic à une nouvelle instance que lorsqu’elle répond OK ;
  • la possibilité de rollback (revenir à la version précédente si la nouvelle échoue).

4. Le CD : déployer automatiquement

On étend la CI (Partie 14.7) avec un job de déploiement qui ne s’exécute que sur main, après que tous les checks passent :

# .github/workflows/deploy.yml (extrait) jobs: deploy: needs: [quality] # ne déploie que si la CI (tests/PHPStan/CS) a réussi if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Build & push image run: | docker build -t registry.example.com/booklab:${{ github.sha }} . docker push registry.example.com/booklab:${{ github.sha }} - name: Deploy run: | # ssh / kubectl / platform CLI selon la cible ./deploy.sh ${{ github.sha }}

Le principe : rien ne se déploie si la qualité n’est pas verte. C’est la promesse de la CI/CD — livrer souvent, en sécurité.

5. Les cibles de déploiement

CiblePour qui
Platform.sh / UpsunPaaS Symfony-natif (les créateurs sont proches de Symfony) : migrations, workers, services gérés — le plus simple.
VPS + Dockercontrôle total, coût maîtrisé ; on gère soi-même (Docker Compose, reverse proxy, sauvegardes).
Kubernetesgrande échelle, orchestration avancée ; complexe, pour des équipes ops.
Serverless PHP (Bref, sur AWS Lambda)pics de charge, pay-per-use ; contraintes du serverless.

Pour BookLab et la plupart des projets freelance, Platform.sh/Upsun ou un VPS + Docker (FrankenPHP) couvrent l’essentiel.

💡 Pour un dev Next.js : vous déployez Next sur Vercel/Docker ; côté Symfony, Platform.sh est l’équivalent « PaaS qui connaît le framework » (migrations, workers gérés). Sur une architecture BookLab (API Symfony + front Next.js), vous auriez deux déploiements : le front sur Vercel/edge, l’API Symfony sur Platform.sh/VPS — reliés par l’API (Partie 11) et le CORS/proxy (11.6). Savoir déployer les deux est, encore une fois, ce qui vous rend autonome sur toute la chaîne.

✏️ Exercices

Exercice 1. Dans quel ordre placer : cache:warmup, migrations:migrate, composer install --no-dev, messenger:stop-workers ? Pourquoi ?

✅ Solution

  1. composer install --no-dev (dépendances de prod). 2. migrations:migrate (schéma à jour, rétrocompatible). 3. cache:warmup (config compilée, éviter le coût de la 1re requête). 4. messenger:stop-workers (les workers rechargent le nouveau code). Logique : d’abord le code et ses dépendances, puis le schéma, puis le cache prêt, enfin les workers relancés — chaque étape suppose la précédente.

Exercice 2. Pourquoi une migration doit-elle être rétrocompatible lors d’un déploiement rolling ?

✅ Solution

Parce que pendant un déploiement rolling, l’ancienne et la nouvelle version du code tournent en même temps un court instant. Une migration cassante (supprimer/renommer une colonne utilisée par l’ancien code) ferait échouer les requêtes de l’ancienne version encore active. On procède en expand/contract : d’abord ajouter sans casser, déployer le code, puis (déploiement suivant) retirer l’ancien. Jamais « ajouter et casser » d’un coup.

Exercice 3. Quel garde-fou empêche de déployer du code qui ne passe pas les tests ?

✅ Solution

Le job de déploiement dépend de la CI (needs: [quality]) et ne s’exécute que si elle a réussi (tests, PHPStan, CS Fixer, audit). Combiné à la protection de branche (Partie 14.7), rien n’est mergé ni déployé tant que la qualité n’est pas verte. C’est le cœur de la CI/CD : livrer souvent, mais en sécurité.

🧠 Quiz de révision

1. Pourquoi une image Docker multi-étapes ?

Pour séparer le build (outils, composer install, compilation des assets) du runtime (image finale légère, sans outils de build).

2. Citez trois étapes de déploiement Symfony.

Au choix : composer install --no-dev, migrations:migrate, cache:warmup, asset-map:compile, messenger:stop-workers, recharger le serveur.

3. Comment viser le zéro-downtime ?

Migrations rétrocompatibles (expand/contract), déploiement rolling, health checks, possibilité de rollback.

4. Quand le job de déploiement s’exécute-t-il ?

Sur main, après que la CI (tests/analyse/style) a réussi (needs) — jamais si la qualité est rouge.

5. Quelle cible pour un déploiement Symfony simple ?

Platform.sh/Upsun (PaaS Symfony-natif) ou un VPS + Docker (FrankenPHP).


Prochain chapitre : 15.5 — Observabilité — logs, Sentry, OpenTelemetry, health checks.