--- title: Laravel Development Setup with Docker Compose description: Set up a Laravel development environment using Docker Compose. weight: 30 --- This guide demonstrates how to configure a **development** environment for a Laravel application using Docker and Docker Compose. It builds **on top of** the production image for PHP-FPM and then adds developer-focused features—like Xdebug—to streamline debugging. By basing the development container on a known production image, you keep both environments closely aligned. This setup includes PHP-FPM, Nginx, and PostgreSQL services (although you can easily swap PostgreSQL for another database, like MySQL or MariaDB). Everything runs in containers, so you can develop in isolation without altering your host system. > [!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/ │ │ ├── php-fpm/ │ │ │ └── entrypoint.sh │ │ ├── workspace/ │ │ │ └── Dockerfile │ │ └── nginx │ │ ├── Dockerfile │ │ └── nginx.conf │ └── production/ ├── 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. The environment includes a `workspace` service, a sidecar container for tasks like building front-end assets, running Artisan commands, and other CLI tools your project may require. While this extra container may seem unusual, it’s a familiar pattern in solutions like **Laravel Sail** and **Laradock**. It also includes **Xdebug** to aid in debugging. ## Create a Dockerfile for PHP-FPM This Dockerfile **extends** the production image by installing Xdebug and adjusting user permissions to ease local development. That way, your development environment stays consistent with production while still offering extra debug features and improved file mounting. ```dockerfile # Builds a dev-only layer on top of the production image FROM production AS development # Use ARGs to define environment variables passed from the Docker build command or 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 # Configure Xdebug if enabled 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 # Add ARGs for syncing permissions ARG UID=1000 ARG GID=1000 # Create a new user with the specified UID and GID, reusing an existing group if GID exists 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 # Dynamically update php-fpm to use the new user and group 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 # Set the working directory WORKDIR /var/www # Copy the entrypoint script COPY ./docker/development/php-fpm/entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh # Switch back 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 Workspace A workspace container provides a dedicated shell for asset compilation, Artisan/Composer commands, and other CLI tasks. This approach follows patterns from Laravel Sail and Laradock, consolidating all development tools into one container for convenience. ```dockerfile # docker/development/workspace/Dockerfile # Use the official PHP CLI image as the base FROM php:8.3-cli # Set environment variables for user and group ID ARG UID=1000 ARG GID=1000 ARG NODE_VERSION=22.0.0 # Install system dependencies and build libraries 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 xdebug \ && docker-php-ext-enable redis xdebug\ && 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/* # Use ARG to define environment variables passed from the Docker build command or Docker Compose. ARG XDEBUG_ENABLED ARG XDEBUG_MODE ARG XDEBUG_HOST ARG XDEBUG_IDE_KEY ARG XDEBUG_LOG ARG XDEBUG_LOG_LEVEL # Configure Xdebug if enabled RUN if [ "${XDEBUG_ENABLED}" = "true" ]; then \ 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 # If the group already exists, use it; otherwise, create the 'www' group 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 # Switch to the non-root user to install NVM and Node.js USER www # Install NVM (Node Version Manager) as the www user 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 # Ensure NVM is available for all future shells 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 # Set the working directory WORKDIR /var/www # Override the entrypoint to avoid the default php entrypoint ENTRYPOINT [] # Default command to keep the container running CMD ["bash"] ``` > [!NOTE] > If you prefer a **one-service-per-container** approach, simply omit the workspace container and run separate containers for each task. For example, you could use a dedicated `php-cli` container for your PHP scripts, and a `node` container to handle the asset building. ## Create a Docker Compose Configuration for development Here's the `compose.yaml` file to set up the development environment: ```yaml services: web: image: nginx:latest # Using the default Nginx image with custom configuration. volumes: # Mount the application code for live updates - ./:/var/www # Mount the Nginx configuration file - ./docker/development/nginx/nginx.conf:/etc/nginx/nginx.conf:ro ports: # Map port 80 inside the container to the port specified by 'NGINX_PORT' on the host machine - "80:80" environment: - NGINX_HOST=localhost networks: - laravel-development depends_on: php-fpm: condition: service_started # Wait for php-fpm to start php-fpm: # For the php-fpm service, we will use our common PHP-FPM Dockerfile with the development 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: # Load the environment variables from the Laravel application - .env user: "${UID:-1000}:${GID:-1000}" volumes: # Mount the application code for live updates - ./:/var/www networks: - laravel-development depends_on: postgres: condition: service_started # Wait for postgres to start workspace: # For the workspace service, we will also create a custom image to install and setup all the necessary stuff. 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 # Enables an interactive terminal stdin_open: true # Keeps standard input open for 'docker exec' env_file: - .env volumes: - ./:/var/www networks: - laravel-development postgres: image: postgres:16 ports: - "${POSTGRES_PORT:-5432}:5432" environment: - POSTGRES_DB=app - POSTGRES_USER=laravel - POSTGRES_PASSWORD=secret volumes: - postgres-data-development:/var/lib/postgresql/data networks: - laravel-development redis: image: redis:alpine networks: - laravel-development networks: laravel-development: volumes: postgres-data-development: ``` > [!NOTE] > Ensure you have an `.env` file at the root of your Laravel project with the necessary configurations. You can use the `.env.example` file as a template. ## Run your development environment To start the development environment, use: ```console $ docker compose -f compose.dev.yaml up --build -d ``` Run this command to build and start the development environment in detached mode. When the containers finish initializing, visit [http://localhost/](http://localhost/) to see your Laravel app in action. ## Summary By building on top of the production image and adding debug tools like Xdebug, you create a Laravel development workflow that closely mirrors production. The optional workspace container simplifies tasks like asset building and running Artisan commands. If you prefer a separate container for every service (e.g., a dedicated `php-cli` and `node` container), you can skip the workspace approach. Either way, Docker Compose provides an efficient, consistent way to develop your Laravel project.