--- title: Laravel Production Setup with Docker Compose description: Set up a production-ready environment for Laravel using Docker Compose. weight: 20 --- This guide demonstrates how to set up a production-ready Laravel environment using Docker and Docker Compose. This configuration is designed for streamlined, scalable, and secure Laravel application deployments. > [!NOTE] > To experiment with a ready-to-run configuration, download the [Laravel Docker Examples](https://github.com/dockersamples/laravel-docker-examples) repository. It contains pre-configured setups for both development and production. ## Project structure ```plaintext my-laravel-app/ ├── app/ ├── bootstrap/ ├── config/ ├── database/ ├── public/ ├── docker/ │ ├── common/ │ │ └── php-fpm/ │ │ └── Dockerfile │ ├── development/ │ ├── production/ │ │ ├── php-fpm/ │ │ │ └── entrypoint.sh │ │ └── nginx │ │ ├── Dockerfile │ │ └── nginx.conf ├── compose.dev.yaml ├── compose.prod.yaml ├── .dockerignore ├── .env ├── vendor/ ├── ... ``` This layout represents a typical Laravel project, with Docker configurations stored in a unified `docker` directory. You’ll find **two** Compose files — `compose.dev.yaml` (for development) and `compose.prod.yaml` (for production) — to keep your environments separate and manageable. ## Create a Dockerfile for PHP-FPM (production) For production, the `php-fpm` Dockerfile creates an optimized image with only the PHP extensions and libraries your application needs. As demonstrated in the [GitHub example](https://github.com/dockersamples/laravel-docker-examples), a single Dockerfile with multi-stage builds maintains consistency and reduces duplication between development and production. The following snippet shows only the production-related stages: ```dockerfile # Stage 1: Build environment and Composer dependencies FROM php:8.3-fpm AS builder # Install system dependencies and PHP extensions for Laravel with MySQL/PostgreSQL support. # Dependencies in this stage are only required for building the final image. # Node.js and asset building are handled in the Nginx stage, not here. 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 \ opcache \ 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/* # Set the working directory inside the container WORKDIR /var/www # Copy the entire Laravel application code into the container # ----------------------------------------------------------- # In Laravel, `composer install` may trigger scripts # needing access to application code. # For example, the `post-autoload-dump` event might execute # Artisan commands like `php artisan package:discover`. If the # application code (including the `artisan` file) is not # present, these commands will fail, leading to build errors. # # By copying the entire application code before running # `composer install`, we ensure that all necessary files are # available, allowing these scripts to run successfully. # In other cases, it would be possible to copy composer files # first, to leverage Docker's layer caching mechanism. # ----------------------------------------------------------- COPY . /var/www # Install Composer and dependencies 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 # Stage 2: Production environment FROM php:8.3-fpm # Install only runtime libraries needed in production # libfcgi-bin and procps are required for the php-fpm-healthcheck script 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/* # Download and install php-fpm health check script 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 # Copy the initialization script COPY ./docker/php-fpm/entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh # Copy the initial storage structure COPY ./storage /var/www/storage-init # Copy PHP extensions and libraries from the builder stage 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/ # Use the recommended production PHP configuration # ----------------------------------------------------------- # PHP provides development and production configurations. # Here, we replace the default php.ini with the production # version to apply settings optimized for performance and # security in a live environment. # ----------------------------------------------------------- RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" # Enable PHP-FPM status page by modifying zz-docker.conf with sed RUN sed -i '/\[www\]/a pm.status_path = /status' /usr/local/etc/php-fpm.d/zz-docker.conf # Update the variables_order to include E (for ENV) #RUN sed -i 's/variables_order = "GPCS"/variables_order = "EGPCS"/' "$PHP_INI_DIR/php.ini" # Copy the application code and dependencies from the build stage COPY --from=builder /var/www /var/www # Set working directory WORKDIR /var/www # Ensure correct permissions RUN chown -R www-data:www-data /var/www # Switch to the non-privileged user to run the application USER www-data # Change the default command to run the entrypoint script ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] # Expose port 9000 and start php-fpm server EXPOSE 9000 CMD ["php-fpm"] ``` ## Create a Dockerfile for PHP-CLI (production) For production, you often need a separate container to run Artisan commands, migrations, and other CLI tasks. In most cases you can run these commands by reusing existing PHP-FPM container: ```console $ docker compose -f compose.prod.yaml exec php-fpm php artisan route:list ``` If you need a separate CLI container with different extensions or strict separation of concerns, consider a php-cli Dockerfile: ```dockerfile # Stage 1: Build environment and Composer dependencies FROM php:8.3-cli AS builder # Install system dependencies and PHP extensions required for Laravel + MySQL/PostgreSQL support # Some dependencies are required for PHP extensions only in the build stage 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 \ opcache \ 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/* # Set the working directory inside the container WORKDIR /var/www # Copy the entire Laravel application code into the container COPY . /var/www # Install Composer and dependencies 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 # Stage 2: Production environment FROM php:8.3-cli # Install client libraries required for php extensions in runtime 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/* # Copy PHP extensions and libraries from the builder stage 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/ # Use the default production configuration for PHP runtime arguments RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" # Copy the application code and dependencies from the build stage COPY --from=builder /var/www /var/www # Set working directory WORKDIR /var/www # Ensure correct permissions RUN chown -R www-data:www-data /var/www # Switch to the non-privileged user to run the application USER www-data # Default command: Provide a bash shell to allow running any command CMD ["bash"] ``` This Dockerfile is similar to the PHP-FPM Dockerfile, but it uses the `php:8.3-cli` image as the base image and sets up the container for running CLI commands. ## Create a Dockerfile for Nginx (production) Nginx serves as the web server for the Laravel application. You can include static assets directly to the container. Here's an example of possible Dockerfile for Nginx: ```dockerfile # docker/nginx/Dockerfile # Stage 1: Build assets FROM debian AS builder # Install Node.js and build tools 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/* # Set working directory WORKDIR /var/www # Copy Laravel application code COPY . /var/www # Install Node.js dependencies and build assets RUN npm install && npm run build # Stage 2: Nginx production image FROM nginx:alpine # Copy custom Nginx configuration # ----------------------------------------------------------- # Replace the default Nginx configuration with our custom one # that is optimized for serving a Laravel application. # ----------------------------------------------------------- COPY ./docker/nginx/nginx.conf /etc/nginx/nginx.conf # Copy Laravel's public assets from the builder stage # ----------------------------------------------------------- # We only need the 'public' directory from our Laravel app. # ----------------------------------------------------------- COPY --from=builder /var/www/public /var/www/public # Set the working directory to the public folder WORKDIR /var/www/public # Expose port 80 and start Nginx EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] ``` This Dockerfile uses a multi-stage build to separate the asset building process from the final production image. The first stage installs Node.js and builds the assets, while the second stage sets up the Nginx production image with the optimized configuration and the built assets. ## Create a Docker Compose configuration for production To bring all the services together, create a `compose.prod.yaml` file that defines the services, volumes, and networks for the production environment. Here's an example configuration: ```yaml services: web: build: context: . dockerfile: ./docker/production/nginx/Dockerfile restart: unless-stopped # Automatically restart unless the service is explicitly stopped volumes: # Mount the 'laravel-storage' volume to '/var/www/storage' inside the container. # ----------------------------------------------------------- # This volume stores persistent data like uploaded files and cache. # The ':ro' option mounts it as read-only in the 'web' service because Nginx only needs to read these files. # The 'php-fpm' service mounts the same volume without ':ro' to allow write operations. # ----------------------------------------------------------- - laravel-storage-production:/var/www/storage:ro networks: - laravel-production ports: # Map port 80 inside the container to the port specified by 'NGINX_PORT' on the host machine. # ----------------------------------------------------------- # This allows external access to the Nginx web server running inside the container. # For example, if 'NGINX_PORT' is set to '8080', accessing 'http://localhost:8080' will reach the application. # ----------------------------------------------------------- - "${NGINX_PORT:-80}:80" depends_on: php-fpm: condition: service_healthy # Wait for php-fpm health check php-fpm: # For the php-fpm service, we will create a custom image to install the necessary PHP extensions and setup proper permissions. build: context: . dockerfile: ./docker/common/php-fpm/Dockerfile target: production # Use the 'production' stage in the Dockerfile restart: unless-stopped volumes: - laravel-storage-production:/var/www/storage # Mount the storage volume env_file: - .env networks: - laravel-production healthcheck: test: ["CMD-SHELL", "php-fpm-healthcheck || exit 1"] interval: 10s timeout: 5s retries: 3 # The 'depends_on' attribute with 'condition: service_healthy' ensures that # this service will not start until the 'postgres' service passes its health check. # This prevents the application from trying to connect to the database before it's ready. depends_on: postgres: condition: service_healthy # The 'php-cli' service provides a command-line interface for running Artisan commands and other CLI tasks. # ----------------------------------------------------------- # This is useful for running migrations, seeders, or any custom scripts. # It shares the same codebase and environment as the 'php-fpm' service. # ----------------------------------------------------------- php-cli: build: context: . dockerfile: ./docker/php-cli/Dockerfile tty: true # Enables an interactive terminal stdin_open: true # Keeps standard input open for 'docker exec' env_file: - .env networks: - laravel postgres: image: postgres:16 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/data networks: - laravel-production # Health check for PostgreSQL # ----------------------------------------------------------- # Health checks allow Docker to determine if a service is operational. # The 'pg_isready' command checks if PostgreSQL is ready to accept connections. # This prevents dependent services from starting before the database is ready. # ----------------------------------------------------------- healthcheck: test: ["CMD", "pg_isready"] interval: 10s timeout: 5s retries: 5 redis: image: redis:alpine restart: unless-stopped # Automatically restart unless the service is explicitly stopped networks: - laravel-production # Health check for Redis # ----------------------------------------------------------- # Checks if Redis is responding to the 'PING' command. # This ensures that the service is not only running but also operational. # ----------------------------------------------------------- healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 3 networks: # Attach the service to the 'laravel-production' network. # ----------------------------------------------------------- # This custom network allows all services within it to communicate using their service names as hostnames. # For example, 'php-fpm' can connect to 'postgres' by using 'postgres' as the hostname. # ----------------------------------------------------------- laravel-production: volumes: postgres-data-production: laravel-storage-production: ``` > [!NOTE] > Ensure you have an `.env` file at the root of your Laravel project with the necessary configurations (e.g., database and Xdebug settings) to match the Docker Compose setup. ## Running your production environment To start the production environment, run: ```console $ docker compose -f compose.prod.yaml up --build -d ``` This command will build and start all the services in detached mode, providing a scalable and production-ready setup for your Laravel application. ## Summary By setting up a Docker Compose environment for Laravel in production, you ensure that your application is optimized for performance, scalable, and secure. This setup makes deployments consistent and easier to manage, reducing the likelihood of errors due to differences between environments.