backend / php-laravel
Déploiement et bonnes pratiques en production
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=0change 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/upet à 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 OPcache | Effet | Contexte recommandé |
|---|---|---|
validate_timestamps=1 | Vérifie les fichiers à chaque requête (plus lent) | Développement |
validate_timestamps=0 | Ignore 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 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"]; 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# 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
// 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=0maximise les perfs mais exige un reload OPcache explicite à chaque déploiement.php artisan down/upavec migration entre les deux minimise la fenêtre d'indisponibilité.queue:restartest indispensable après un déploiement : les workers existants tournent sur l'ancien code chargé en mémoire.- Un endpoint
/healthvérifiant DB et cache permet à l'orchestrateur de détecter une instance défaillante.
Exercices pratiques
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.