# Configuración de desarrollo de Laravel con Docker Compose


Esta guía muestra cómo configurar un entorno de **desarrollo** para una aplicación Laravel utilizando Docker y Docker Compose. Se basa **sobre** la imagen de producción para PHP-FPM y luego añade características orientadas al desarrollador (como Xdebug) para facilitar la depuración. Al basar el contenedor de desarrollo en una imagen de producción conocida, mantienes ambos entornos estrechamente alineados.

Esta configuración incluye los servicios de PHP-FPM, Nginx y PostgreSQL (aunque puedes cambiar fácilmente PostgreSQL por otra base de datos, como MySQL o MariaDB). Todo se ejecuta en contenedores, por lo que puedes desarrollar de forma aislada sin alterar tu sistema host.

> [!NOTE]
> Para experimentar con una configuración lista para ejecutar, descarga el repositorio [Laravel Docker Examples](https://github.com/dockersamples/laravel-docker-examples). Contiene configuraciones preconfiguradas tanto para desarrollo como para producción.

## Estructura del proyecto

```plaintext
my-laravel-app/
├── app/
├── bootstrap/
├── config/
├── database/
├── public/
├── docker/
│   ├── common/
│   │   └── php-fpm/
│   │       └── Dockerfile
│   ├── development/
│   │   ├── php-fpm/
│   │   │   └── entrypoint.sh
│   │   ├── workspace/
│   │   │   └── Dockerfile
│   │   └── nginx
│   │       ├── Dockerfile
│   │       └── nginx.conf
│   └── production/
├── compose.dev.yaml
├── compose.prod.yaml
├── .dockerignore
├── .env
├── vendor/
├── ...
```

Esta estructura representa un proyecto Laravel típico, con las configuraciones de Docker almacenadas en un directorio unificado `docker`. Encontrarás **dos** archivos de Compose: `compose.dev.yaml` (para desarrollo) y `compose.prod.yaml` (para producción), para mantener tus entornos separados y fáciles de gestionar.

El entorno incluye un servicio `workspace`, un contenedor sidecar para tareas como compilar recursos del front-end, ejecutar comandos de Artisan y otras herramientas de CLI que tu proyecto pueda requerir. Aunque este contenedor adicional pueda parecer inusual, es un patrón común en soluciones como **Laravel Sail** y **Laradock**. También incluye **Xdebug** para ayudar en la depuración.

## Crear un Dockerfile para PHP-FPM

Este Dockerfile **extiende** la imagen de producción instalando Xdebug y ajustando los permisos de usuario para facilitar el desarrollo local. De esta manera, tu entorno de desarrollo se mantiene consistente con el de producción, al mismo tiempo que ofrece funciones adicionales de depuración y un montaje de archivos mejorado.

```dockerfile
# Compila una capa solo para desarrollo sobre la imagen de producción
FROM production AS development

# Usa ARGs para definir variables de entorno pasadas desde el comando docker build o Docker Compose.
ARG XDEBUG_ENABLED=true
ARG XDEBUG_MODE=develop,coverage,debug,profile
ARG XDEBUG_HOST=host.docker.internal
ARG XDEBUG_IDE_KEY=DOCKER
ARG XDEBUG_LOG=/dev/stdout
ARG XDEBUG_LOG_LEVEL=0

USER root

# Configura Xdebug si está habilitado
RUN if [ "${XDEBUG_ENABLED}" = "true" ]; then \
    pecl install xdebug && \
    docker-php-ext-enable xdebug && \
    echo "xdebug.mode=${XDEBUG_MODE}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
    echo "xdebug.idekey=${XDEBUG_IDE_KEY}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
    echo "xdebug.log=${XDEBUG_LOG}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
    echo "xdebug.log_level=${XDEBUG_LOG_LEVEL}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
    echo "xdebug.client_host=${XDEBUG_HOST}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini ; \
    echo "xdebug.start_with_request=yes" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini ; \
fi

# Añade ARGs para sincronizar permisos
ARG UID=1000
ARG GID=1000

# Crea un nuevo usuario con el UID y GID especificados, reutilizando un grupo existente si el GID ya existe
RUN if getent group ${GID}; then \
      group_name=$(getent group ${GID} | cut -d: -f1); \
      useradd -m -u ${UID} -g ${GID} -s /bin/bash www; \
    else \
      groupadd -g ${GID} www && \
      useradd -m -u ${UID} -g www -s /bin/bash www; \
      group_name=www; \
    fi

# Actualiza dinámicamente php-fpm para usar el nuevo usuario y grupo
RUN sed -i "s/user = www-data/user = www/g" /usr/local/etc/php-fpm.d/www.conf && \
    sed -i "s/group = www-data/group = $group_name/g" /usr/local/etc/php-fpm.d/www.conf


# Establece el directorio de trabajo
WORKDIR /var/www

# Copia el script de punto de entrada (entrypoint)
COPY ./docker/development/php-fpm/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh

# Cambia de nuevo al usuario sin privilegios para ejecutar la aplicación
USER www-data

# Cambia el comando predeterminado para ejecutar el script de punto de entrada
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

# Expone el puerto 9000 e inicia el servidor php-fpm
EXPOSE 9000
CMD ["php-fpm"]
```

## Crear un Dockerfile para Workspace

Un contenedor de workspace proporciona una terminal dedicada para la compilación de recursos, comandos de Artisan/Composer y otras tareas de CLI. Este enfoque sigue patrones de Laravel Sail y Laradock, consolidando todas las herramientas de desarrollo en un solo contenedor para mayor comodidad.

```dockerfile
# docker/development/workspace/Dockerfile
# Usa la imagen oficial de PHP CLI como base
FROM php:8.5-cli

# Establece variables de entorno para el ID de usuario y grupo
ARG UID=1000
ARG GID=1000
ARG NODE_VERSION=22.0.0

# Instala dependencias del sistema y bibliotecas de compilación
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    unzip \
    libpq-dev \
    libonig-dev \
    libssl-dev \
    libxml2-dev \
    libcurl4-openssl-dev \
    libicu-dev \
    libzip-dev \
    && docker-php-ext-install -j$(nproc) \
    pdo_mysql \
    pdo_pgsql \
    pgsql \
    intl \
    zip \
    bcmath \
    soap \
    && pecl install redis \
    && docker-php-ext-enable redis \
    && curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
    && apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Usa ARG to definir variables de entorno pasadas desde el comando docker build o Docker Compose.
ARG XDEBUG_ENABLED
ARG XDEBUG_MODE
ARG XDEBUG_HOST
ARG XDEBUG_IDE_KEY
ARG XDEBUG_LOG
ARG XDEBUG_LOG_LEVEL

# Configura Xdebug si está habilitado
RUN if [ "${XDEBUG_ENABLED}" = "true" ]; then \
    pecl install xdebug && \
    docker-php-ext-enable xdebug && \
    echo "xdebug.mode=${XDEBUG_MODE}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
    echo "xdebug.idekey=${XDEBUG_IDE_KEY}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
    echo "xdebug.log=${XDEBUG_LOG}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
    echo "xdebug.log_level=${XDEBUG_LOG_LEVEL}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \
    echo "xdebug.client_host=${XDEBUG_HOST}" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini ; \
    echo "xdebug.start_with_request=yes" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini ; \
fi

# Si el grupo ya existe, úsalo; de lo contrario, crea el grupo 'www'
RUN if getent group ${GID}; then \
      useradd -m -u ${UID} -g ${GID} -s /bin/bash www; \
    else \
      groupadd -g ${GID} www && \
      useradd -m -u ${UID} -g www -s /bin/bash www; \
    fi && \
    usermod -aG sudo www && \
    echo 'www ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

# Cambia al usuario no raíz para instalar NVM y Node.js
USER www

# Instala NVM (Node Version Manager) como el usuario www
RUN export NVM_DIR="$HOME/.nvm" && \
    curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash && \
    [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" && \
    nvm install ${NODE_VERSION} && \
    nvm alias default ${NODE_VERSION} && \
    nvm use default

# Asegúrate de que NVM esté disponible para todas las terminales futuras
RUN echo 'export NVM_DIR="$HOME/.nvm"' >> /home/www/.bashrc && \
    echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> /home/www/.bashrc && \
    echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"' >> /home/www/.bashrc

# Establece el directorio de trabajo
WORKDIR /var/www

# Invalida el punto de entrada para evitar el entrypoint predeterminado de php
ENTRYPOINT []

# Comando predeterminado para mantener el contenedor en ejecución
CMD ["bash"]
```

> [!NOTE]
> Si prefieres un enfoque de **un servicio por contenedor**, simplemente omite el contenedor workspace y ejecuta contenedores separados para cada tarea. Por ejemplo, podrías usar un contenedor `php-cli` dedicado para tus scripts PHP y un contenedor `node` para manejar la compilación de recursos.

## Crear una configuración de Docker Compose para desarrollo

Aquí está el archivo `compose.yaml` para configurar el entorno de desarrollo:

```yaml
services:
  web:
    image: nginx:latest # Usa la imagen predeterminada de Nginx con configuración personalizada.
    volumes:
      # Monta el código de la aplicación para actualizaciones en vivo
      - ./:/var/www
      # Monta el archivo de configuración de Nginx
      - ./docker/development/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    ports:
      # Asocia el puerto 80 dentro del contenedor con el puerto de la máquina host
      - "80:80"
    environment:
      - NGINX_HOST=localhost
    networks:
      - laravel-development
    depends_on:
      php-fpm:
        condition: service_started # Espera a que php-fpm se inicie

  php-fpm:
    # Para el servicio php-fpm, usaremos nuestro Dockerfile PHP-FPM común con el objetivo de desarrollo (target)
    build:
      context: .
      dockerfile: ./docker/common/php-fpm/Dockerfile
      target: development
      args:
        UID: ${UID:-1000}
        GID: ${GID:-1000}
        XDEBUG_ENABLED: ${XDEBUG_ENABLED:-true}
        XDEBUG_MODE: develop,coverage,debug,profile
        XDEBUG_HOST: ${XDEBUG_HOST:-host.docker.internal}
        XDEBUG_IDE_KEY: ${XDEBUG_IDE_KEY:-DOCKER}
        XDEBUG_LOG: /dev/stdout
        XDEBUG_LOG_LEVEL: 0
    env_file:
      # Carga las variables de entorno de la aplicación Laravel
      - .env
    user: "${UID:-1000}:${GID:-1000}"
    volumes:
      # Monta el código de la aplicación para actualizaciones en vivo
      - ./:/var/www
    networks:
      - laravel-development
    depends_on:
      postgres:
        condition: service_started # Espera a que postgres se inicie

  workspace:
    # Para el servicio workspace, también crearemos una imagen personalizada para instalar y configurar todo lo necesario.
    build:
      context: .
      dockerfile: ./docker/development/workspace/Dockerfile
      args:
        UID: ${UID:-1000}
        GID: ${GID:-1000}
        XDEBUG_ENABLED: ${XDEBUG_ENABLED:-true}
        XDEBUG_MODE: develop,coverage,debug,profile
        XDEBUG_HOST: ${XDEBUG_HOST:-host.docker.internal}
        XDEBUG_IDE_KEY: ${XDEBUG_IDE_KEY:-DOCKER}
        XDEBUG_LOG: /dev/stdout
        XDEBUG_LOG_LEVEL: 0
    tty: true # Habilita una terminal interactiva
    stdin_open: true # Mantiene abierta la entrada estándar para 'docker exec'
    env_file:
      - .env
    volumes:
      - ./:/var/www
    networks:
      - laravel-development

  postgres:
    image: postgres:18
    ports:
      - "${POSTGRES_PORT:-5432}:5432"
    environment:
      - POSTGRES_DB=app
      - POSTGRES_USER=laravel
      - POSTGRES_PASSWORD=secret
    volumes:
      - postgres-data-development:/var/lib/postgresql
    networks:
      - laravel-development

  redis:
    image: redis:alpine
    networks:
      - laravel-development

networks:
  laravel-development:

volumes:
  postgres-data-development:
```

> [!NOTE]
> Asegúrate de tener un archivo `.env` en la raíz de tu proyecto Laravel con las configuraciones necesarias. Puedes usar el archivo `.env.example` como plantilla.

## Ejecutar tu entorno de desarrollo

Para iniciar el entorno de desarrollo, utiliza:

```console
$ docker compose -f compose.dev.yaml up --build -d
```

Ejecuta este comando para compilar e iniciar el entorno de desarrollo en modo desacoplado (detached). Cuando los contenedores terminen de inicializarse, visita [http://localhost/](http://localhost/) para ver tu aplicación Laravel en acción.

## Resumen

Al construir sobre la imagen de producción y agregar herramientas de depuración como Xdebug, creas un flujo de trabajo de desarrollo de Laravel que refleja fielmente la producción. El contenedor workspace opcional simplifica tareas como la compilación de recursos y la ejecución de comandos de Artisan. Si prefieres un contenedor separado para cada servicio (por ejemplo, un contenedor dedicado para `php-cli` y otro para `node`), puedes omitir el enfoque de workspace. De cualquier manera, Docker Compose proporciona una forma eficiente y consistente de desarrollar tu proyecto Laravel.

