Article
Déployer une application Laravel sur Google Cloud Run : les pièges et leurs solutions
, 8 min de lecture

Pour déployer Laravel sur Google Cloud Run, il faut un conteneur qui réunit PHP-FPM et un serveur web à l'écoute du port fourni par Cloud Run, des assets frontend compilés avant l'image, une configuration portée par le service plutôt que par le fichier de CI, et un Laravel qui sait qu'il tourne derrière un proxy HTTPS. L'essentiel du travail tient à quelques pièges propres à Laravel : voici ceux rencontrés lors de la mise en production de Driveway Morocco, et leurs corrections.
Driveway Morocco est la plateforme de location de voitures de ShipFastDev (étude de cas) : Laravel 13, une vitrine Inertia/React, un back-office Filament, des cautions Stripe, et des plugins (flotte, réservation, paiement) installés comme paquets Composer locaux. Elle est en ligne sur Cloud Run depuis août 2026, avec une base Postgres managée chez Supabase et un déploiement GitHub Actions à chaque push. Les pièges ci-dessous viennent de l'historique Git de ce déploiement.
Ce que Cloud Run attend de votre conteneur
Lisez le contrat d'exécution des conteneurs avant d'écrire le Dockerfile. Quatre règles comptent pour Laravel :
- Le port. Le conteneur doit écouter sur
0.0.0.0, sur le port indiqué par la variablePORT(8080 par défaut). - Pas de TLS dans le conteneur. Cloud Run termine le HTTPS et transmet les requêtes en HTTP simple. Si Laravel n'en est pas informé, il génère des liens en
http://. - Le disque, c'est de la mémoire. Écrire sur le système de fichiers consomme la RAM de l'instance, et tout disparaît à son arrêt. Sessions, fichiers envoyés et logs vont en base, dans un stockage objet ou sur la sortie standard.
- Démarrage et arrêt. Une instance doit écouter dans les 4 minutes suivant son lancement. À l'arrêt, elle reçoit
SIGTERM, puisSIGKILL10 secondes plus tard.
Driveway tourne dans une seule image : PHP 8.4 FPM, Nginx et Supervisor. Nginx écoute sur le port, sert les assets compilés et passe les requêtes PHP à FPM.
Piège n° 1 : les assets frontend absents
Avec gcloud run deploy --source ., le dossier est envoyé puis construit par Cloud Build à partir de votre Dockerfile s'il existe. Ce qui part est filtré par .gcloudignore, et le fichier par défaut reprend votre .gitignore. Vite compile dans public/build, ignoré par Git : les premiers déploiements via la CI ont donc produit une image sans CSS ni JavaScript.
Compiler dans l'image n'a pas suffi : le build régénérait manifest.json avec de nouveaux hashs, sans les fichiers JS correspondants. La solution retenue compile les assets dans le job de CI, avant le déploiement, et le Dockerfile se contente de copier public/build. Sans Node ni npm, l'image a aussi perdu environ 200 Mo.
Piège n° 2 : votre vendor/ local écrase le bon
Le Dockerfile lance composer install, puis copie les sources. Comme vendor/ ne figurait pas dans .gcloudignore, le COPY . remplaçait les dépendances fraîchement installées par le vendor/ du poste de développement, et les routes du back-office Filament cassaient. Excluez vendor/ et node_modules/ de l'envoi.
Piège n° 3 : dépôts Composer de type path et liens symboliques
Les plugins de Driveway vivent dans un dossier plugins/ et sont installés comme dépôts Composer de type « path ». Composer les relie par défaut par des liens symboliques. Pendant le build Docker, la copie a transformé ces liens en vrais dossiers, et l'autoloader ne trouvait plus les classes des plugins. Les pages fiche véhicule et paiement, qui dépendaient des routes des plugins, s'affichaient vides.
Corrections retenues : une étape de build qui ajoute les namespaces PSR-4 des plugins à la table d'autoload de Composer, un contrôleur de flotte porté par le cœur de l'application, et des routes de secours pour le tunnel de réservation. Sur un nouveau projet, essayez d'abord l'option "symlink": false : selon la documentation de Composer, forcer la copie « peut être utile lors d'un déploiement ».
Piège n° 4 : un cache de couches qui casse le build
Docker recommande de copier les fichiers de dépendances, de les installer, puis de copier les sources. Un commit a appliqué cet ordre (copie de composer.json et de plugins/, installation, puis le reste) en estimant qu'un déploiement typique passerait d'environ 8 minutes à environ 2. Il a été annulé le jour même : le nouvel ordre cassait les builds de CI, car avec des dépôts « path », la couche de dépendances dépend aussi du code de vos plugins. Obtenez d'abord une image qui fonctionne, optimisez ensuite.
Piège n° 5 : des liens http:// derrière le proxy HTTPS
PHP-FPM recevait du HTTP simple, donc le helper url() de Laravel générait des URL en http:// pour les assets et les appels d'API. La page étant servie en HTTPS, la Content Security Policy bloquait ces requêtes : navigation, connexion, recherche, filtres et réservation ne répondaient plus, sans aucune erreur côté serveur.
La correction consiste à forcer les URL en HTTPS : APP_URL et ASSET_URL pointent vers le domaine public, le script de démarrage force le HTTPS, et URL::forceScheme('https') est appelé en production. La documentation de Laravel décrit la voie standard : activer le middleware TrustProxies pour que Laravel lise le protocole transmis par le proxy, avec * quand on ne connaît pas les adresses du load balancer.
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
$middleware->trustProxies(at: '*');
})
Dans les deux cas, vérifiez dans un navigateur, console ouverte.
Piège n° 6 : des secrets dans le fichier de CI
Le premier workflow passait les variables d'environnement à l'étape de déploiement, mot de passe de la base et clé de l'application compris. Elles ont été retirées dans la journée. Cloud Run reporte la configuration d'une révision sur les suivantes tant qu'on ne la modifie pas explicitement : la CI n'a qu'à livrer la nouvelle image. Attention : --set-env-vars supprime toute variable absente de la liste, contrairement à --update-env-vars. Google déconseille par ailleurs les variables d'environnement pour les secrets comme les identifiants de base ou les clés d'API : il recommande Secret Manager.
Côté authentification, la CI utilisait d'abord une clé de compte de service stockée en secret GitHub. En septembre 2026, elle est passée à la fédération d'identité de charge de travail (Workload Identity Federation) avec l'action de déploiement de Google. La documentation de google-github-actions/auth la recommande parce qu'elle évite d'exporter un identifiant de longue durée ; une clé n'expire jamais et doit être traitée comme un mot de passe. Le workflow doit avoir la permission id-token: write.
Piège n° 7 : démarrage à froid et mémoire
Le script de démarrage de Driveway vide les caches de configuration et de routes pour que le gestionnaire de plugins lise en base les plugins activés, et lance php artisan package:discover pour que Laravel enregistre les paquets installés. Gardez-le court : l'instance n'a que 4 minutes pour écouter.
L'autre limite est la mémoire. Elle est de 512 Mio par instance par défaut, une instance qui la dépasse est arrêtée, et les fichiers écrits sur disque comptent dedans. Le service de Driveway est passé à 1 Gio pour gagner en stabilité.
Piège n° 8 : une fonction d'administration qui fait tomber tout le site
En septembre 2026, un plugin enregistrait dans sa méthode boot() un composant Filament/Livewire réservé au back-office. Sur Cloud Run, cet appel levait une exception au démarrage de l'application, et toutes les pages tombaient, vitrine comprise. Les pages d'erreur appelaient ensuite route('home'), absente de la liste des routes après l'échec : React se démontait et le visiteur voyait un écran blanc.
Corrections : un try/catch autour de l'enregistrement réservé à l'administration, et des pages d'erreur qui se rabattent sur des chemins en dur. Le code de boot() s'exécute à chaque requête ; une page d'erreur ne doit dépendre de rien qui puisse échouer.
Files d'attente et tâches planifiées
La check-list de production du projet prévoit un worker de file d'attente et le scheduler de Laravel. Un audit avait montré que, sans worker, e-mails, notifications push et expiration des cautions ne s'exécutaient jamais en local, sans alerte. Sur Cloud Run, il faut le prévoir explicitement : avec la facturation à la requête (le mode par défaut), le contrat indique que le CPU est alloué pendant le traitement des requêtes, donc un queue:work dans le conteneur web n'est pas un worker fiable. Les options documentées : la facturation à l'instance, qui garde le CPU alloué en continu, ou un job Cloud Run déclenché par Cloud Scheduler pour les commandes périodiques.
La check-list
- Nginx et PHP-FPM dans une image, à l'écoute de
$PORT. - Assets compilés en CI, pas de Node dans l'image d'exécution.
vendor/etnode_modules/exclus de l'envoi.- Autoload testé dans l'image construite.
- URL en HTTPS vérifiées dans un vrai navigateur.
- Secrets dans Secret Manager, CI sur Workload Identity Federation.
- Script de démarrage court, mémoire dimensionnée.
- Code de démarrage protégé, pages d'erreur qui s'affichent toujours.
- Une solution pour les files d'attente et le cron.
- Des tests navigateur avant le déploiement : voir les tests de bout en bout avec Playwright en CI.
La plupart de ces problèmes ne sont apparus que dans le conteneur déployé. Des mises en production petites et fréquentes ont permis d'en trouver vite la cause (voir des livraisons chaque semaine avec un développeur à distance). Sur la recherche du même projet : recherche Laravel avec Meilisearch et repli sur la base de données.
Questions fréquentes
Laravel peut-il tourner sur Cloud Run sans modifier le code ?
Presque. Il faut un conteneur à l'écoute de $PORT, des URL générées en HTTPS, une configuration lue depuis l'environnement ou Secret Manager, et rien de persistant sur le disque local.
Faut-il Nginx dans le conteneur ?
Cloud Run transmet des requêtes HTTP au conteneur, et PHP-FPM parle FastCGI, pas HTTP : il faut donc un serveur web devant. Driveway Morocco utilise Nginx et PHP-FPM, pilotés par Supervisor.
Comment faire tourner les files d'attente et le cron de Laravel sur Cloud Run ?
Pas en tâche de fond dans le conteneur web avec la facturation à la requête. Utilisez la facturation à l'instance pour un worker, ou des jobs Cloud Run déclenchés par Cloud Scheduler pour les commandes planifiées.
Pourquoi mon application Laravel sur Cloud Run charge-t-elle ses assets en http:// ?
Cloud Run envoie du HTTP simple au conteneur, et Laravel croit la requête non sécurisée. Activez TrustProxies ou forcez le HTTPS en production, et mettez votre domaine en https:// dans APP_URL.
Sources
- Container runtime contract, Google Cloud, 2026-09
- Deploy services from source code, Google Cloud, 2026-09
- gcloud topic gcloudignore, Google Cloud, 2026-10
- Configure environment variables for services, Google Cloud, 2026-09
- Configure memory limits for services, Google Cloud, 2026-09
- Execute jobs on a schedule, Google Cloud, 2026-09
- google-github-actions/auth, Google (GitHub), 2026-10
- HTTP Requests: Configuring Trusted Proxies, documentation Laravel 13.x, 2026-10
- Repositories: Path, documentation Composer, 2026-10
- Optimize cache usage in builds, Docker Docs, 2026-10

