Error de permisos en Laravel con Docker (Linux)

1. Descripción del problema

Tras instalar Laravel en un entorno Linux con Docker y eliminar el index.php de pruebas, al acceder a http://localhost:8080 aparece un error de permisos denegados. Aunque los contenedores están levantados y la conexión a base de datos funcionaba con el index.php previo, al crear el volumen y montar el proyecto Laravel en src/ el servidor ya no puede leer o escribir en los archivos.

Contexto típico:

  • Volumen bind: ./src en el host montado como /var/www/html dentro del contenedor.
  • Servicios habituales: php (php-fpm) y nginx.
  • El contenedor php necesita leer y escribir en /var/www/html, y nginx leerlo para servir.

Causa más común: Desajuste de propietarios y permisos entre el host y el contenedor. Dentro del contenedor, los procesos suelen correr como www-data:www-data. En el host, los archivos pertenecen a tu usuario. Esto rompe la escritura/lectura cuando se monta el volumen.


2. Objetivo

  • Hacer que el usuario interno del contenedor (www-data) tenga permisos correctos sobre /var/www/html.
  • Mantener la edición fluida en el host con tu usuario.
  • Evitar errores 403/500 por permisos y problemas de cache/logs de Laravel.

3. Requisitos previos

  • Docker y Docker Compose funcionando.
  • Estructura del proyecto similar a:

    .
    ├─ docker-compose.yml
    ├─ src/        # código Laravel montado al contenedor en /var/www/html
    └─ ...
    
  • Conocer el nombre del contenedor PHP (por ejemplo laravel_app-php-1). Puedes listar con:

    docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"
    
  • Si no conoces puedes utilizar docker compose y el nombre del servicio:

    docker compose exec php bash
    

4. Solución paso a paso

Paso 1. Ajustar permisos dentro del contenedor PHP

  1. Entrar en el contenedor PHP:

    docker exec -it laravel_app-php-1 bash
    
    o bien:

    docker-compose exec php bash
    
  2. Asignar propietario y grupo www-data a la webroot:

    chown -R www-data:www-data /var/www/html
    
  3. Establecer permisos seguros de lectura/ejecución:

    chmod -R 755 /var/www/html
    

Resultado esperado

Resultado esperado: localhost:8080 vuelve a servir la aplicación, porque www-data puede leer el código. Si usas Laravel, recuerda que storage/ y bootstrap/cache requieren escritura; este ajuste se verá en el Paso 3.

Paso 2. Permitir edición desde el host sin errores de permisos

Tras el Paso 1 puedes encontrar que tu usuario en el host no puede editar algunos archivos en src/. Para que tanto tu usuario como el usuario www-data del contenedor trabajen sin pisarse:

  1. Añade tu usuario al grupo www-data en el host:

    sudo usermod -aG www-data TU_USUARIO
    

    por eejmplo si tu usuario es: profesor:

    sudo usermod -aG www-data profesor
    
  2. Reinicia la sesión para aplicar el cambio de grupos: cierra sesión y vuelve a entrar, o reinicia la máquina. Verifica:

    groups TU_USUARIO
    # Debería aparecer 'www-data'
    
  3. En el host, ajusta propietario y grupo de src/:

    sudo chown -R TU_USUARIO:www-data src
    
  4. Permisos de colaboración (lectura y escritura para usuario y grupo):

    sudo chmod -R 775 src
    

Permisos locales

Con esto, tu usuario edita archivos en src/ sin fricciones y el contenedor (grupo www-data) también tiene permisos de escritura cuando lo necesite.


Paso 3. Directorios que Laravel necesita con escritura

Laravel requiere escritura en:

  • storage/
  • bootstrap/cache/

Si no existen o no tienen permisos adecuados, alinea permisos explícitamente:

Dentro del host:

sudo chown -R TU_USUARIO:www-data src/storage src/bootstrap/cache
sudo chmod -R 775 src/storage src/bootstrap/cache

Dentro del contenedor PHP (opcional, por coherencia):

primero entra en el contenedor de PHP:

docker exec -it laravel_app-php-1 bash
o bien:

docker compose exec php bash

Para modificar los permisos en el contenedor ejecuta:

chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache
chmod -R 775 /var/www/html/storage /var/www/html/bootstrap/cache

5. Comprobaciones rápidas

  • Navegador: http://localhost:8080 carga la página de inicio de Laravel.
  • Logs:

    • Nginx: revisa el contenedor de nginx si hay 403/502.
    • PHP/Laravel: storage/logs/laravel.log.
  • Cache de Laravel:

    docker exec -it laravel_app-php-1 bash -lc 'php artisan optimize:clear'
    

    o bien:

    docker compose exec php bash -lc 'php artisan optimize:clear'
    

6. Notas y buenas prácticas

  • Evita chmod -R 777. Usa 775 para colaboración usuario-grupo y restringe a lo necesario.
  • Si los contenedores definen un user: con UID/GID específico, alinea el UID/GID de www-data con el de tu usuario del host o usa variables de entorno para mapearlos.
  • Mantén el volumen solo para el código. Para datos persistentes de la app (cache, sesiones, logs) considera volúmenes gestionados por Docker.
  • Si cambiaste nombres de servicios, ajusta el nombre del contenedor en los comandos docker exec.
  • Cada vez que añadas tu usuario a un grupo, reinicia sesión para que surta efecto.

7. Conclusión

Esta es la solución encontrada cuando una vez instalados los contenedores Docker en Linux, al montar el código fuente Laravel desde el host, el servidor web no podía leer los archivos por problemas de permisos.

Otra solución posible es ajustar los UID/GID de los usuarios dentro del contenedor para que coincidan con los del host, pero esto puede complicar la configuración y mantenimiento ya que sería necesario modificar el Dockerfile o usar variables de entorno. Est solución la dejo para el futuro, ya que con los cursos en marcha no creo que sea buena idea modificar los fichjeros de los contenedores.

8. Resumen

  1. Dentro del contenedor PHP:
docker exec -it laravel_app-php-1 bash
chown -R www-data:www-data /var/www/html
chmod -R 755 /var/www/html
  1. En el host:
sudo usermod -aG www-data TU_USUARIO
# cerrar sesión y volver a entrar
groups TU_USUARIO
sudo chown -R TU_USUARIO:www-data src
sudo chmod -R 775 src
  1. Directorios clave de Laravel:
sudo chown -R TU_USUARIO:www-data src/storage src/bootstrap/cache
sudo chmod -R 775 src/storage src/bootstrap/cache
docker exec -it laravel_app-php-1 bash -lc 'php artisan optimize:clear'