Retour au cours

backend / php-laravel

Déploiement et bonnes pratiques en production

Leçon 231 exercice

Explication

Ce que vous allez apprendre

  • Construire une image Docker de production multi-stage pour une application Laravel
  • Comprendre le rôle d'OPcache et pourquoi validate_timestamps=0 change les habitudes de déploiement
  • Mettre en place un pipeline CI/CD qui teste avant de déployer
  • Déployer sans interrompre le service grâce à artisan down/up et à la migration entre les deux
  • Exposer un endpoint de santé (/health) pour qu'un orchestrateur détecte une instance défaillante

Dans quel contexte ?

Une application Laravel qui tournait parfaitement sur la machine d'un développeur doit désormais être déployée sur plusieurs serveurs en production, avec des mises à jour régulières qui ne doivent jamais interrompre le service pour les utilisateurs déjà connectés. Cette dernière leçon rassemble tout ce qui a été vu dans le cours pour aboutir à un déploiement professionnel, robuste et observable.

D'abord, une image Docker pensée pour la production, pas pour le développement

Une image multi-stage sépare la construction des assets front (Node.js, nécessaire uniquement à la compilation) de l'image finale PHP-FPM, qui n'a besoin ni de Node ni des outils de build. composer install --no-dev --optimize-autoloader exclut les dépendances de développement et optimise l'autoloader — reprenant directement ce qui a été vu en leçon 6 sur Composer, mais appliqué spécifiquement au contexte de production.

Prérequis

Cette dernière leçon suppose l'ensemble du cours acquis : elle assemble Composer (leçon 6), le cache (leçon 21) et les migrations (leçon 12) dans un contexte de déploiement réel.

Ensuite, un réglage de performance qui change une habitude de travail

OPcache compile le bytecode PHP une seule fois et le garde en mémoire, évitant de recompiler le code à chaque requête — un gain de performance considérable en production. Mais opcache.validate_timestamps=0 désactive la vérification automatique des changements de fichiers : PHP continuera de servir l'ANCIEN code tant qu'un rechargement explicite n'a pas eu lieu.

Réglage OPcacheEffetContexte recommandé
validate_timestamps=1Vérifie les fichiers à chaque requête (plus lent)Développement
validate_timestamps=0Ignore les changements de fichiers (plus rapide)Production, avec reload au déploiement

Piège fréquent

Activer validate_timestamps=0 sans adapter le processus de déploiement fait qu'un déploiement semble "ne rien changer" : l'ancien code continue de tourner jusqu'au prochain redémarrage du service PHP-FPM. Le déploiement doit systématiquement inclure ce redémarrage.

Il reste à automatiser les vérifications avant tout déploiement

Un pipeline CI (GitHub Actions dans l'exemple) exécute la suite de tests (leçon 20) et une analyse statique (phpstan --level=8) sur chaque changement, AVANT que le déploiement ne soit même déclenché. Ça transforme la confiance dans le code d'un "on espère que ça marche" à un "c'est vérifié automatiquement avant chaque mise en production".

Maintenant, déployer sans interrompre brutalement le service

php artisan down --render="maintenance" affiche une page de maintenance pendant que la migration s'exécute, puis php artisan up rouvre l'accès. Une étape facile à oublier mais critique : php artisan queue:restart, sans lequel les workers de queue (vus en leçon 18) continueraient à traiter les jobs avec l'ancien code, potentiellement incompatible avec le nouveau schéma de base de données.

Astuce

Un endpoint /health qui vérifie activement la connexion à la base de données et au cache (pas seulement "le serveur répond") permet à un orchestrateur comme Kubernetes de détecter une instance réellement défaillante et de la retirer automatiquement de la rotation, avant même qu'un utilisateur ne rencontre une erreur.

Ce cours a parcouru le chemin complet, des variables PHP les plus basiques jusqu'à un déploiement Laravel professionnel en production. La suite logique, une fois ces bases solides acquises, consiste à pratiquer sur un vrai projet personnel : c'est en construisant quelque chose de bout en bout que ces concepts s'ancrent durablement.

Commandes & code

Déploiement et bonnes pratiques en production

dockerfile
# Dockerfile multi-stage : build des assets front, puis image PHP-FPM allégée
FROM node:20-alpine AS assets
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY resources resources
COPY vite.config.js ./
RUN npm run build

FROM php:8.3-fpm-alpine AS app
RUN apk add --no-cache postgresql-dev \
    && docker-php-ext-install pdo pdo_pgsql opcache

# OPcache : compile le bytecode PHP une fois, énorme gain de perf en prod
COPY docker/opcache.ini /usr/local/etc/php/conf.d/opcache.ini

WORKDIR /var/www
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-interaction --no-scripts

COPY . .
COPY --from=assets /app/public/build ./public/build

RUN php artisan config:cache && php artisan route:cache && php artisan view:cache
RUN chown -R www-data:www-data storage bootstrap/cache

USER www-data
CMD ["php-fpm"]
ini
; docker/opcache.ini -- configuration critique en production
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0    ; désactivé en prod : nécessite un reload explicite au déploiement
opcache.jit=tracing
opcache.jit_buffer_size=100M
yaml
# Pipeline CI/CD simplifié (GitHub Actions)
name: deploy
on:
  push:
    branches: [main]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: composer install --no-interaction
      - run: cp .env.example .env && php artisan key:generate
      - run: php artisan test --parallel
      - run: ./vendor/bin/phpstan analyse --level=8   # analyse statique stricte

  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - run: |
          # Déploiement zero-downtime : nouveau build, migration, puis bascule symlink
          php artisan down --render="maintenance" --retry=60
          php artisan migrate --force
          php artisan optimize
          php artisan queue:restart   # les workers rechargent le nouveau code au prochain job
          php artisan up
php
<?php
// Observabilité : logs structurés et contexte de requête (config/logging.php)
return [
    'channels' => [
        'stack' => ['driver' => 'stack', 'channels' => ['daily', 'sentry']],
        'daily' => ['driver' => 'daily', 'path' => storage_path('logs/laravel.log'), 'days' => 14],
    ],
];

// Contexte enrichi automatiquement sur chaque log (traçabilité en production)
\Log::withContext([
    'request_id' => request()->header('X-Request-Id') ?? \Str::uuid(),
    'user_id' => auth()->id(),
]);

// Health check pour l'orchestrateur (Kubernetes, load balancer)
Route::get('/health', function () {
    try {
        DB::connection()->getPdo();
        Cache::store()->has('healthcheck');
        return response()->json(['status' => 'ok'], 200);
    } catch (\Throwable $e) {
        return response()->json(['status' => 'error'], 503);
    }
});

Résumé

  • opcache.validate_timestamps=0 maximise les perfs mais exige un reload OPcache explicite à chaque déploiement.
  • php artisan down/up avec migration entre les deux minimise la fenêtre d'indisponibilité.
  • queue:restart est indispensable après un déploiement : les workers existants tournent sur l'ancien code chargé en mémoire.
  • Un endpoint /health vérifiant DB et cache permet à l'orchestrateur de détecter une instance défaillante.

Exercices pratiques

1 disponible
1

Mission : un deploiement qui semble ne rien changer

Objectif : Diagnostiquer un deploiement OPcache mal termine et completer un pipeline de deploiement zero-downtime incomplet.

Contexte

Une correction urgente vient d'etre deployee sur le serveur de production, qui tourne avec opcache.validate_timestamps=0. Vingt minutes plus tard, les utilisateurs signalent toujours le meme bug corrige dans le code source, visible sur le serveur via git log. En parallele, tu remarques que le script de deploiement CI/CD execute php artisan down, php artisan migrate --force, puis php artisan up, mais ne contient PAS l'etape queue:restart presente dans l'exemple de la lecon, alors que l'application traite plusieurs jobs de facturation par minute via des workers Supervisor.

Résoudre l’exercice →