# Cómo montar Laravel en Docker: Guía práctica y comandos esenciales

# Cómo montar Laravel en Docker

Si alguna vez has intentado mover un proyecto Laravel de tu entorno local a producción y te has encontrado con el clásico problema de *"en mi máquina sí funciona"*, Docker es la solución que estabas buscando.

En este artículo, te voy a explicar cómo estructurar tu proyecto Laravel con Docker de forma profesional, cómo resolver los problemas más comunes (como el temido `Connection refused`) y te dejaré una lista interactiva (Cheat Sheet) de los comandos indispensables para tu día a día.

* * *

## Estructura del Proyecto

Antes de entrar en detalle, así es como se organiza nuestro proyecto:

```plaintext
proyecto/
├── .dockerignore        ← Excluye archivos innecesarios del build
├── .env                 ← Única fuente de credenciales (¡NO commitear!)
├── .env.example         ← Plantilla documentada (SÍ commitear)
├── docker-compose.yml   ← Orquestador de todos los servicios
├── dockerfiles/
│   ├── entrypoint.sh    ← Script de arranque automático
│   ├── nginx.dockerfile
│   └── php.dockerfile
├── nginx/
│   └── default.conf     ← Configuración del servidor web
└── src/
    └── ...              ← Tu proyecto Laravel
```

> \*\* Detalle importante:\*\* El fichero `.dockerignore` evita que carpetas como `vendor/`, `node_modules/` o archivos `.env` se envíen al contexto de build de Docker, acelerando la construcción y evitando filtrar información sensible.

* * *

## La Arquitectura: ¿Qué necesitamos?

Para ejecutar Laravel en Docker de forma robusta, dividimos nuestra aplicación en **4 contenedores (servicios)** principales:

1.  **Servidor Web (Nginx):** El encargado de recibir las peticiones HTTP y pasarlas a PHP. Configurado con cabeceras de seguridad y `server_tokens off`.
    
2.  **Aplicación (PHP-FPM):** Donde reside el código fuente de Laravel y se ejecuta la lógica. Corre con un usuario no-root (`laravel`).
    
3.  **Base de Datos (MySQL):** El almacenamiento persistente con un **healthcheck** que garantiza que esté lista antes de que PHP arranque.
    
4.  **Gestor de BD (phpMyAdmin) \[Solo desarrollo\]:** Administración visual de la base de datos, activada solo con el perfil `dev`.
    

Todo esto lo orquestamos a través de un archivo maestro llamado `docker-compose.yml`.

* * *

## Variables de Entorno: Una Única Fuente de Verdad

Una de las claves de seguridad más importantes es **nunca hardcodear credenciales** directamente en los ficheros de Docker. En su lugar, usamos un fichero `.env` centralizado en la raíz del proyecto.

### 1\. Crea tu `.env` a partir de la plantilla

```bash
cp .env.example .env
```

### 2\. Edita las credenciales en `.env`

```ini
# .env (raíz del proyecto — NO commitear)
APP_DEBUG=false
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=tu_contraseña_segura
MYSQL_ROOT_PASSWORD=otra_contraseña_segura
```

### 3\. El `docker-compose.yml` las referencia con `${VARIABLE}`

```yaml
services:
  php:
    build:
      context: .
      dockerfile: dockerfiles/php.dockerfile
    restart: unless-stopped
    environment:
      - DB_CONNECTION=mysql
      - DB_HOST=mysql          # Usamos el nombre del servicio Docker, ¡no 127.0.0.1!
      - DB_PORT=3306
      - DB_DATABASE=${DB_DATABASE:-laravel}
      - DB_USERNAME=${DB_USERNAME:-laravel}
      - DB_PASSWORD=${DB_PASSWORD}
      - APP_DEBUG=${APP_DEBUG:-false}
    depends_on:
      mysql:
        condition: service_healthy  # Espera a que MySQL esté REALMENTE listo
```

> \*\* ¿Por qué no usamos `src/.env`?\*\* Docker inyecta estas variables de entorno en el contenedor, sobrescribiendo las del `.env` de Laravel. Así se separa la configuración del código y se facilitan los despliegues automatizados en herramientas como **Coolify** o AWS.

* * *

## Healthchecks: Arranque Inteligente

Uno de los problemas más comunes es que PHP intente conectar a MySQL cuando la base de datos aún no está lista. Para evitarlo, configuramos un **healthcheck** en MySQL:

```yaml
mysql:
  image: mysql:8.0.32
  restart: unless-stopped
  healthcheck:
    test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
    interval: 5s
    timeout: 3s
    retries: 10
    start_period: 10s
```

Con `depends_on: mysql: condition: service_healthy`, Docker Compose esperará a que MySQL responda al `ping` antes de arrancar los contenedores de PHP y Nginx. ¡Adiós a los `Connection refused` en el arranque!

* * *

## 🛠️ Automatizando Migraciones y Seeders

El archivo `entrypoint.sh` se ejecuta cada vez que arranca el contenedor PHP. Automatiza las migraciones y ejecuta los seeders **de forma inteligente**:

```bash
#!/bin/sh
set -e

cd /var/www/html

echo "Database is ready. Running migrations..."
php artisan migrate --force

# Solo ejecutar seeders si existen ficheros Y no se han ejecutado antes
if [ ! -f /var/www/html/storage/.seeded ]; then
  SEEDER_COUNT=$(find /var/www/html/database/seeders -name '*.php' 2>/dev/null | wc -l)
  if [ "$SEEDER_COUNT" -gt 0 ]; then
    echo "Running seeders ($SEEDER_COUNT found)..."
    php artisan db:seed --force
    touch /var/www/html/storage/.seeded
  else
    echo "No seeders found, skipping."
  fi
else
  echo "Seeders already executed, skipping."
fi

exec "$@"
```

**¿Qué hace de especial este script?**

*   `set -e`: Si cualquier comando falla, el contenedor se detiene inmediatamente (fail-fast).
    
*   **Seeders condicionales:** Primero comprueba que existan ficheros `.php` en `database/seeders`. Si los hay y no se han ejecutado antes, se ejecutan y se crea un fichero marcador `.seeded`. En reinicios posteriores, se omite automáticamente.
    

> \*\* Importante:\*\* Cualquier cambio en `entrypoint.sh` o en el `php.dockerfile` requiere que **reconstruyas la imagen** con el flag `--build`. ¡Si solo reinicias el contenedor usando *restart*, no se aplicarán los cambios!

* * *

## Seguridad en Nginx

Nuestra configuración de Nginx incluye cabeceras de seguridad esenciales:

```nginx
server {
    listen 80;
    server_name .localhost;
    root /var/www/html/public;

    # Ocultar versión de Nginx
    server_tokens off;

    # Cabeceras de seguridad
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Límites
    client_max_body_size 10M;
    fastcgi_read_timeout 60;

    # Bloquear acceso a ficheros ocultos
    location ~ /\.(?!well-known) {
        deny all;
    }
}
```

Estas cabeceras protegen contra ataques como clickjacking (`X-Frame-Options`), MIME-sniffing (`X-Content-Type-Options`) y ataques XSS (`X-XSS-Protection`).

* * *

## phpMyAdmin: Solo en Desarrollo

phpMyAdmin se gestiona con un **perfil de Docker Compose**, lo que significa que solo se levanta cuando lo necesitas explícitamente:

```yaml
phpmyadmin:
  image: phpmyadmin/phpmyadmin:latest
  profiles: ["dev"]    # Solo se activa con --profile dev
  depends_on:
    mysql:
      condition: service_healthy
  ports:
    - "127.0.0.1:8081:80"
```

En producción, phpMyAdmin no se despliega, eliminando una superficie de ataque innecesaria.

* * *

## Cheat Sheet: Los Comandos que Usamos Todo el Tiempo

A continuación tienes una lista de los comandos más habituales al trabajar con contenedores, y los que te salvarán la vida en tu día a día (¡comprobado en este mismo blog!):

### Comandos de Ciclo de Vida

Levantar tu proyecto o apagarlo:

*   `docker compose --profile dev up -d`: Levanta todos los contenedores **incluyendo phpMyAdmin** (modo desarrollo).
    
*   `docker compose up -d`: Levanta los contenedores **sin phpMyAdmin** (modo producción).
    
*   `docker compose up -d --build`: Levanta los contenedores forzando la reconstrucción de las imágenes. **Úsalo si cambias un** `Dockerfile` **o copias un nuevo** `entrypoint.sh`.
    
*   `docker compose --profile dev up -d --build`: Igual que el anterior, pero incluyendo phpMyAdmin.
    
*   `docker compose down`: Detiene y destruye los contenedores y redes, pero conserva los volúmenes (tus bases de datos).
    
*   `docker compose down -v`: Detiene y destruye todos los contenedores, redes y destruye los volúmenes (tus bases de datos).
    
*   `docker network inspect <nombre_red>`: Útil si Docker te dice que una red sigue en uso y no te permite eliminar un entorno temporal.
    

### Ejecutando código de Laravel dentro de Docker

Para usar `php artisan` y `composer`, **no** lo ejecutes en la terminal base de tu máquina (Windows/Mac), ya que intentará usar el PHP instalado localmente y probablemente fallará (por falta de dependencias u otras versiones de PHP). Ejecútalo **dentro** del contenedor:

*   `docker compose exec php php artisan migrate`: Ejecuta las migraciones manualmente.
    
*   `docker compose exec php php artisan db:seed`: Ejecuta los seeders para poblar datos de prueba o roles base.
    
*   `docker compose exec php php artisan config:clear`: Limpia la caché de configuración. **¡Obligatorio si tocas tu** `.env` **local!**
    
*   `docker compose exec --user root php composer dump-autoload`: Regenera el autoload de Composer como super-usuario. Útil si pasas código creado en Windows a Linux y hay errores de permisos ("Operation not permitted").
    

### Debugging y Logs

Cuando algo falla, como los **errores HTTP 500**, o un host `laravel.localhost` que devuelve **"Connection Refused"**, la consola de comandos es tu mejor aliado para rastrear el fallo:

*   `docker compose logs php`: Muestra lo que está pasando en el contenedor de PHP. Excelente para ver si el Entrypoint ejecutó las migraciones o si los seeders se saltaron correctamente.
    
*   `docker compose logs -f server`: Sigue en tiempo real los logs de acceso (y errores `502 Bad Gateway`) del servidor Nginx.
    
*   `docker compose ps`: Comprueba qué contenedores están encendidos en ese momento. Con healthchecks, verás el estado `healthy` en MySQL.
    
*   `docker compose exec php ps aux`: Verifica qué procesos internos (por ejemplo: `php-fpm`) se están ejecutando dentro del contenedor en ese preciso instante.
    

* * *

## Reflexión Final

Dockerizar Laravel implica una curva de aprendizaje mínima al principio, pero una vez configuras tus volúmenes adecuadamente y entiendes cómo funciona su red interna, el flujo de trabajo es impecable.

Evitas conflictos de versiones (¡adiós a fallos por ejecutar Laravel 8 en PHP 8.3 local!), aislas tus bases de datos para no "manchar" tu sistema operativo y, lo mejor de todo, logras que el comportamiento local sea **exactamente igual al del servidor de producción**.

Con las mejoras de seguridad (variables externalizadas, cabeceras HTTP, perfiles para desarrollo) y fiabilidad (healthchecks, seeders condicionales), tu stack está preparado para un entorno profesional real.

¿Y tú, ya usas Docker en tus proyectos de Laravel o sigues usando XAMPP/Laragon? ¡Compárteme tu experiencia en los comentarios!
