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-dev→ migrations →cache: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
mainsi 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:warmupen prod pour ne pas payer la première requête. - Workers :
messenger:stop-workerspour 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
| Cible | Pour qui |
|---|---|
| Platform.sh / Upsun | PaaS Symfony-natif (les créateurs sont proches de Symfony) : migrations, workers, services gérés — le plus simple. |
| VPS + Docker | contrôle total, coût maîtrisé ; on gère soi-même (Docker Compose, reverse proxy, sauvegardes). |
| Kubernetes | grande é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
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.