Compartir comentarios
Las respuestas se generan en base a la documentación.

Configuración de producción de Laravel con Docker Compose

Esta guía muestra cómo configurar un entorno de Laravel listo para producción utilizando Docker y Docker Compose. Esta configuración está diseñada para despliegues de aplicaciones Laravel rápidos, escalables y seguros.

Note

Para experimentar con una configuración lista para ejecutar, descarga el repositorio Laravel Docker Examples. Contiene configuraciones preconfiguradas tanto para desarrollo como para producción.

Estructura del proyecto

my-laravel-app/
├── app/
├── bootstrap/
├── config/
├── database/
├── public/
├── docker/
│   ├── common/
│   │   └── php-fpm/
│   │       ├── Dockerfile
│   │       └── conf.d/
│   │           └── 20-status-path.conf
│   ├── development/
│   ├── production/
│   │   ├── php-fpm/
│   │   │   └── entrypoint.sh
│   │   └── nginx
│   │       ├── Dockerfile
│   │       └── nginx.conf
├── 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.

Crear un Dockerfile para PHP-FPM (producción)

Para producción, el Dockerfile de php-fpm crea una imagen optimizada que contiene únicamente las extensiones y bibliotecas de PHP que tu aplicación necesita. Como se muestra en el ejemplo de GitHub, un único Dockerfile con compilaciones multietapa (multi-stage) mantiene la consistencia y reduce la duplicación entre desarrollo y producción. El siguiente fragmento muestra únicamente las etapas relacionadas con producción:

# Etapa 1: Entorno de compilación y dependencias de Composer
FROM php:8.5-fpm AS builder

# Instala dependencias del sistema y extensiones de PHP para Laravel con soporte para MySQL/PostgreSQL support.
# Las dependencias en esta etapa solo se requieren para construir la imagen final.
# Node.js y la compilación de recursos se manejan en la etapa de Nginx, no aquí.
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 \
    && apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Establece el directorio de trabajo dentro del contenedor
WORKDIR /var/www

# Copia todo el código de la aplicación Laravel en el contenedor
# -----------------------------------------------------------
# En Laravel, `composer install` puede desencadenar scripts
# que necesitan acceder al código de la aplicación.
# Por ejemplo, el evento `post-autoload-dump` podría ejecutar
# comandos de Artisan como `php artisan package:discover`. Si el
# código de la aplicación (incluido el archivo `artisan`) no
# está presente, estos comandos fallarán y provocarán errores de compilación.
#
# Al copiar todo el código de la aplicación antes de ejecutar
# `composer install`, nos aseguramos de que todos los archivos necesarios estén
# disponibles, lo que permite que estos scripts se ejecuten con éxito.
# En otros casos, sería posible copiar primero los archivos de Composer
# para aprovechar el mecanismo de almacenamiento en caché de capas de Docker.
# -----------------------------------------------------------
COPY . /var/www

# Instala Composer y sus dependencias
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
    && composer install --no-dev --optimize-autoloader --no-interaction --no-progress --prefer-dist

# Etapa 2: Entorno de producción
FROM php:8.5-fpm AS production

# Instala únicamente las bibliotecas de tiempo de ejecución necesarias en producción
# libfcgi-bin y procps son necesarios para el script php-fpm-healthcheck
RUN apt-get update && apt-get install -y --no-install-recommends \
    libpq-dev \
    libicu-dev \
    libzip-dev \
    libfcgi-bin \
    procps \
    && apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Descarga e instala el script de verificación de salud de php-fpm
RUN curl -o /usr/local/bin/php-fpm-healthcheck \
    https://raw.githubusercontent.com/renatomefi/php-fpm-healthcheck/master/php-fpm-healthcheck \
    && chmod +x /usr/local/bin/php-fpm-healthcheck

# Copia el script de inicialización
COPY ./docker/php-fpm/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh

# Copia la estructura de almacenamiento inicial
COPY ./storage /var/www/storage-init

# Copia extensiones y bibliotecas de PHP desde la etapa builder
COPY --from=builder /usr/local/lib/php/extensions/ /usr/local/lib/php/extensions/
COPY --from=builder /usr/local/etc/php/conf.d/ /usr/local/etc/php/conf.d/
COPY --from=builder /usr/local/bin/docker-php-ext-* /usr/local/bin/

# Usa la configuración recomendada de PHP para producción
# -----------------------------------------------------------
# PHP proporciona configuraciones de desarrollo y producción.
# Aquí reemplazamos el php.ini predeterminado con la versión de producción
# para aplicar configuraciones optimizadas para el rendimiento y la
# seguridad en un entorno en vivo.
# -----------------------------------------------------------
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"

# Mantén intacta la configuración global de FPM proporcionada por la imagen y añade los overrides de pool por separado
COPY ./docker/common/php-fpm/conf.d/*.conf /usr/local/etc/php-fpm.d/
# Update the variables_order to include E (for ENV)
#RUN sed -i 's/variables_order = "GPCS"/variables_order = "EGPCS"/' "$PHP_INI_DIR/php.ini"

# Copia el código de la aplicación y las dependencias desde la etapa de compilación
COPY --from=builder /var/www /var/www

# Establece el directorio de trabajo
WORKDIR /var/www

# Asegura los permisos correctos
RUN chown -R www-data:www-data /var/www

# Cambia al usuario no privilegiado 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 PHP-CLI (producción)

Para producción, a menudo necesitas un contenedor separado para ejecutar comandos de Artisan, migraciones y otras tareas de CLI. En la mayoría de los casos, puedes ejecutar estos comandos reutilizando el contenedor PHP-FPM existente:

$ docker compose -f compose.prod.yaml exec php-fpm php artisan route:list

Si necesitas un contenedor CLI separado con diferentes extensiones o una separación estricta de responsabilidades, considera usar un Dockerfile para php-cli:

# Etapa 1: Entorno de compilación y dependencias de Composer
FROM php:8.5-cli AS builder

# Instala dependencias del sistema y extensiones de PHP requeridas para Laravel + soporte de MySQL/PostgreSQL
# Algunas dependencias se requieren para las extensiones PHP solo en la etapa 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 \
    && apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Establece el directorio de trabajo dentro del contenedor
WORKDIR /var/www

# Copia todo el código de la aplicación Laravel en el contenedor
COPY . /var/www

# Instala Composer y sus dependencias
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
    && composer install --no-dev --optimize-autoloader --no-interaction --no-progress --prefer-dist

# Etapa 2: Entorno de producción
FROM php:8.5-cli

# Instala las bibliotecas cliente requeridas para las extensiones PHP en el tiempo de ejecución
RUN apt-get update && apt-get install -y --no-install-recommends \
    libpq-dev \
    libicu-dev \
    libzip-dev \
    && apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Copia las extensiones y bibliotecas de PHP desde la etapa builder
COPY --from=builder /usr/local/lib/php/extensions/ /usr/local/lib/php/extensions/
COPY --from=builder /usr/local/etc/php/conf.d/ /usr/local/etc/php/conf.d/
COPY --from=builder /usr/local/bin/docker-php-ext-* /usr/local/bin/

# Usa la configuración de producción predeterminada para los argumentos del tiempo de ejecución de PHP
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"

# Copia el código de la aplicación y las dependencias desde la etapa de compilación
COPY --from=builder /var/www /var/www

# Establece el directorio de trabajo
WORKDIR /var/www

# Asegura los permisos correctos
RUN chown -R www-data:www-data /var/www

# Cambia al usuario no privilegiado para ejecutar la aplicación
USER www-data

# Comando predeterminado: Proporciona una terminal bash para permitir la ejecución de cualquier comando
CMD ["bash"]

Este Dockerfile es similar al Dockerfile de PHP-FPM, pero utiliza la imagen php:8.5-cli como imagen base y configura el contenedor para ejecutar comandos de CLI.

Crear un Dockerfile para Nginx (producción)

Nginx actúa como el servidor web para la aplicación Laravel. Puedes incluir recursos estáticos directamente en el contenedor. Aquí tienes un ejemplo de un Dockerfile posible para Nginx:

# docker/nginx/Dockerfile
# Etapa 1: Compilar recursos
FROM debian AS builder

# Instala Node.js y herramientas de compilación
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    nodejs \
    npm \
    && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Establece el directorio de trabajo
WORKDIR /var/www

# Copia el código de la aplicación Laravel
COPY . /var/www

# Instala las dependencias de Node.js y compila los recursos
RUN npm install && npm run build

# Etapa 2: Imagen de producción de Nginx
FROM nginx:alpine

# Copia la configuración personalizada de Nginx
# -----------------------------------------------------------
# Reemplaza la configuración predeterminada de Nginx con nuestra configuración
# personalizada optimizada para servir una aplicación Laravel.
# -----------------------------------------------------------
COPY ./docker/nginx/nginx.conf /etc/nginx/nginx.conf

# Copia los recursos públicos de Laravel desde la etapa builder
# -----------------------------------------------------------
# Solo necesitamos el directorio 'public' de nuestra aplicación Laravel.
# -----------------------------------------------------------
COPY --from=builder /var/www/public /var/www/public

# Establece el directorio de trabajo en la carpeta public
WORKDIR /var/www/public

# Expone el puerto 80 e inicia Nginx
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Este Dockerfile utiliza una compilación multietapa para separar el proceso de compilación de recursos de la imagen de producción final. La primera etapa instala Node.js y compila los recursos, mientras que la segunda etapa configura la imagen de producción de Nginx con la configuración optimizada y los recursos compilados.

Crear una configuración de Docker Compose para producción

Para unir todos los servicios, crea un archivo compose.prod.yaml que defina los servicios, volúmenes y redes para el entorno de producción. Aquí tienes un ejemplo de configuración:

services:
  web:
    build:
      context: .
      dockerfile: ./docker/production/nginx/Dockerfile
    restart: unless-stopped # Reinicia automáticamente a menos que el servicio se detenga explícitamente
    volumes:
      # Monta el volumen 'laravel-storage' en '/var/www/storage' dentro del contenedor.
      # -----------------------------------------------------------
      # Este volumen almacena datos persistentes como archivos subidos y caché.
      # La opción ':ro' lo monta como de solo lectura en el servicio 'web' porque Nginx solo necesita leer estos archivos.
      # El servicio 'php-fpm' monta el mismo volumen sin ':ro' para permitir operaciones de escritura.
      # -----------------------------------------------------------
      - laravel-storage-production:/var/www/storage:ro
    networks:
      - laravel-production
    ports:
      # Asocia el puerto 80 dentro del contenedor con el puerto de la máquina host.
      # -----------------------------------------------------------
      # Esto permite el acceso externo al servidor web Nginx que se ejecuta dentro del contenedor.
      # Por ejemplo, si 'NGINX_PORT' se establece en '8080', al acceder a 'http://localhost:8080' se llegará a la aplicación.
      # -----------------------------------------------------------
      - "${NGINX_PORT:-80}:80"
    depends_on:
      php-fpm:
        condition: service_healthy # Espera a la verificación de salud (health check) de php-fpm

  php-fpm:
    # Para el servicio php-fpm, crearemos una imagen personalizada para instalar las extensiones de PHP necesarias y configurar los permisos adecuados.
    build:
      context: .
      dockerfile: ./docker/common/php-fpm/Dockerfile
      target: production # Usa la etapa 'production' en el Dockerfile
    restart: unless-stopped
    volumes:
      - laravel-storage-production:/var/www/storage # Monta el volumen de almacenamiento
    env_file:
      - .env
    networks:
      - laravel-production
    healthcheck:
      test: ["CMD-SHELL", "php-fpm-healthcheck || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 3
    # El atributo 'depends_on' con 'condition: service_healthy' garantiza que
    # este servicio no se inicie hasta que el servicio 'postgres' pase su verificación de salud.
    # Esto evita que la aplicación intente conectarse a la base de datos antes de que esté lista.
    depends_on:
      postgres:
        condition: service_healthy

  # El servicio 'php-cli' proporciona una interfaz de línea de comandos para ejecutar comandos de Artisan y otras tareas de CLI.
  # -----------------------------------------------------------
  # Esto es útil para ejecutar migraciones, seeders o cualquier script personalizado.
  # Comparte la misma base de código y entorno que el servicio 'php-fpm'.
  # -----------------------------------------------------------
  php-cli:
    build:
      context: .
      dockerfile: ./docker/php-cli/Dockerfile
    tty: true # Habilita una terminal interactiva
    stdin_open: true # Mantiene abierta la entrada estándar para 'docker exec'
    env_file:
      - .env
    networks:
      - laravel-production

  postgres:
    image: postgres:18
    restart: unless-stopped
    user: postgres
    ports:
      - "${POSTGRES_PORT}:5432"
    environment:
      - POSTGRES_DB=${POSTGRES_DATABASE}
      - POSTGRES_USER=${POSTGRES_USERNAME}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    volumes:
      - postgres-data-production:/var/lib/postgresql
    networks:
      - laravel-production
    # Verificación de salud (health check) para PostgreSQL
    # -----------------------------------------------------------
    # Las verificaciones de salud permiten a Docker determinar si un servicio está operativo.
    # El comando 'pg_isready' comprueba si PostgreSQL está listo para aceptar conexiones.
    # Esto evita que los servicios dependientes se inicien antes de que la base de datos esté lista.
    # -----------------------------------------------------------
    healthcheck:
      test: ["CMD", "pg_isready"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:alpine
    restart: unless-stopped # Reinicia automáticamente a menos que el servicio se detenga explícitamente
    networks:
      - laravel-production
    # Verificación de salud (health check) para Redis
    # -----------------------------------------------------------
    # Comprueba si Redis responde al comando 'PING'.
    # Esto garantiza que el servicio no solo esté ejecutándose, sino también operativo.
    # -----------------------------------------------------------
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3

networks:
  # Asocia el servicio a la red 'laravel-production'.
  # -----------------------------------------------------------
  # Esta red personalizada permite que todos los servicios dentro de ella se comuniquen utilizando sus nombres de servicio como hostnames.
  # Por ejemplo, 'php-fpm' puede conectarse a 'postgres' utilizando 'postgres' como hostname.
  # -----------------------------------------------------------
  laravel-production:

volumes:
  postgres-data-production:
  laravel-storage-production:
Note

Asegúrate de tener un archivo .env en la raíz de tu proyecto Laravel con las configuraciones necesarias para que coincidan con la configuración de Docker Compose.

Ejecutar tu entorno de producción

Para iniciar el entorno de producción, ejecuta:

$ docker compose -f compose.prod.yaml up --build -d

Este comando compilará e iniciará todos los servicios en modo desacoplado (detached), proporcionando una configuración escalable y lista para producción para tu aplicación Laravel.

Resumen

Al configurar un entorno de Docker Compose para Laravel en producción, garantizas que tu aplicación esté optimizada para el rendimiento, sea escalable y segura. Esta configuración hace que los despliegues sean consistentes y más fáciles de gestionar, lo que reduce la probabilidad de errores causados por diferencias entre los entornos.