Si tu travailles avec Docker sur une stack Laravel, WordPress, Symfony ou toute autre application PHP, tu as probablement déjà rencontré ce genre d’erreurs :
Permission denied
Failed to open stream
Unable to create cache directory
file_put_contents(): failed to open stream
403 Forbidden
Dans la majorité des cas, le problème ne vient pas de Laravel, PHP ou Nginx. Il vient de la gestion des permissions Linux entre l’hôte et Docker.
Et malheureusement, beaucoup de développeurs utilisent la solution rapide :
chmod -R 777 storage
Ça fonctionne… jusqu’au jour où ça casse, ou pire : jusqu’au jour où ça ouvre une faille de sécurité.
Comprendre les permissions Linux
Chaque fichier Linux possède :
- un propriétaire (UID)
- un groupe (GID)
- des permissions
-rw-r--r-- 1 1000 1000 file.txt
Ici :
- UID 1000 = propriétaire
- GID 1000 = groupe
Docker ne raisonne pas par nom d’utilisateur. Il raisonne uniquement par UID/GID.
Le piège classique avec Docker
Sur l’hôte :
michael (UID 1000)
Dans le container PHP :
www-data (UID 33)
Si Laravel écrit un fichier dans un bind mount :
storage/app/public/avatar.png
Le fichier sera créé avec :
33:33
Ton hôte peut ne pas avoir accès.
Et inversement :
Si tu crées le dossier depuis ton Mac/Linux :
1000:1000
PHP peut ne pas pouvoir écrire dedans.
Pourquoi chmod 777 est une mauvaise idée
chmod -R 777 storage
Ça donne :
- lecture à tout le monde
- écriture à tout le monde
- exécution à tout le monde
Donc :
- Nginx peut écrire
- PHP peut écrire
- n’importe quel process peut écrire
- un exploit potentiel aussi
Exemple concret :
Si un upload mal filtré passe :
shell.php
Avec un dossier writable globalement, le risque augmente fortement.
En production, 777 doit rester exceptionnel.
Le problème réel dans notre architecture Laravel + Nginx
Notre architecture ressemblait à ceci :
Host
├── src/
├── storage/
├── docker-compose.yml
Containers:
├── app (PHP-FPM)
└── nginx
Cas réel :
- Laravel génère une image optimisée
- stockée dans storage/app/public/generated
- Nginx doit la servir directement
Problèmes rencontrés :
- fichier créé par UID 33
- hôte en UID 1000
- Nginx parfois autre UID
- 403 sur certains fichiers
- symlink cassé visuellement mais existant
Le problème n’était pas Laravel. Le problème était la couche filesystem.
Solution 1 : aligner les UID/GID
Très propre pour le dev.
services:
app:
user: "1000:1000"
Résultat :
- Docker écrit avec le même user que l’hôte
- moins de conflits
Limites :
- moins portable
- pas idéal multi-serveurs
- plus fragile en prod
Solution 2 : utiliser des groupes partagés
Bonne approche Linux classique.
chgrp -R www-data storage
chmod -R 775 storage
On garde :
- owner = écriture
- group = écriture
- others = lecture uniquement
Beaucoup plus propre que 777.
Solution 3 (notre meilleure architecture) : volumes managés
Celle qu’on a retenue.
services:
app:
volumes:
- ./src:/var/www
- laravel_storage:/var/www/storage
nginx:
volumes:
- ./src/public:/var/www/public:ro
- laravel_storage:/var/www/storage:ro
Pourquoi c’est mieux :
- Docker gère les ownerships
- pas de conflit hôte/container
- Laravel écrit librement
- Nginx lit les mêmes fichiers
- moins de chmod manuels
Quand utiliser quoi ?
| Dossier | Type conseillé |
|---|---|
| Code source | Bind mount |
| .env | Bind mount |
| config/ | Bind mount |
| storage/ | Managed volume |
| bootstrap/cache | Managed volume |
| uploads utilisateurs | Managed volume |
| logs | Managed volume |
Conclusion
Si tu rencontres des problèmes de permissions avec Docker :
ne commence pas par chmod 777.
Pose-toi d’abord ces questions :
- Qui crée le fichier ?
- Quel UID/GID utilise ce process ?
- Qui doit lire ce fichier ?
- Est-ce que ce dossier devrait vraiment être un bind mount ?
Dans notre expérience :
code source = bind mount
fichiers dynamiques = volumes managés
nginx + php = partage du même volume
C’est aujourd’hui l’architecture la plus stable, la plus propre et la plus prévisible que nous utilisons.