Skip to main content

Command Palette

Search for a command to run...

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

Aprende a dockerizar tu aplicación Laravel paso a paso, solucionar problemas comunes de conexión y gestionar tus contenedores

Published
8 min read

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:

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

cp .env.example .env

2. Edita las credenciales en .env

# .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}

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:

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:

#!/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:

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:

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!

4 views