
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Setting up a reliable Docker Compose for local Laravel dev environment eliminates the "works on my machine" problem that still plagues PHP teams in 2026. Instead of wrestling with conflicting system PHP versions or Homebrew updates, you define your exact stack—PHP 8.4, Nginx, MariaDB, and Redis—in code that travels with your repository. This guide walks through a production-mirroring configuration that uses bind mounts for instant file reflection and includes practical optimizations for macOS and Linux performance.
How do you structure Docker Compose for local Laravel dev environment services?
The most common mistake I see when teams adopt containers for PHP is treating the application as a single monolithic container. For a functional Docker Compose for local Laravel dev environment, you must separate concerns just as you would in production. This separation allows you to restart individual components without losing state and mirrors the architecture you will eventually deploy to Kubernetes or EC2. If you are new to containerization fundamentals, review Docker for beginners: containerize a Laravel app from scratch before proceeding.
Defining the service boundaries
Your docker-compose.yml should contain exactly four core services for local development. The web service runs Nginx solely as a reverse proxy and static file server; it never executes PHP. The app service runs PHP-FPM with your application code mounted at /var/www/html. The database service runs MariaDB (or PostgreSQL if you prefer PostgreSQL administration essentials) with a named volume for data persistence. The cache service runs Redis for both application caching and queue drivers.
Network and volume configuration
Always define a custom bridge network rather than relying on the default bridge. Custom networks provide automatic DNS resolution between containers, meaning your PHP application can connect to database instead of managing IP addresses. For volumes, distinguish between bind mounts and named volumes explicitly:
- Bind mounts (
./src:/var/www/html) for application code — changes reflect instantly without rebuilds - Named volumes (
db_data:/var/lib/mysql) for database files — survives container recreation - Anonymous volumes — avoid these entirely in development; they create orphaned storage
How do you configure PHP 8.4 and Nginx for Laravel containers?
The official PHP images are a starting point, not a destination. For a performant Docker Compose for local Laravel dev environment, you need a custom Dockerfile that installs required extensions, configures OPcache correctly for development, and sets appropriate user permissions. In 2026, PHP 8.4 is the stable target for new Laravel projects, bringing property hooks and improved type safety that your container should support natively.
Building the PHP-FPM image
Create a docker/php/Dockerfile that extends php:8.4-fpm-bookworm. Install system dependencies first (libpng, libjpeg, libonig, libzip), then install PHP extensions via docker-php-ext-install. Always include bcmath, gd, mbstring, pdo_mysql, zip, and opcache. For local development, add Xdebug conditionally using a build argument so CI builds can skip it:
FROM php:8.4-fpm-bookworm
ARG INSTALL_XDEBUG=true
RUN apt-get update && apt-get install -y \
libpng-dev libjpeg62-turbo-dev libfreetype6-dev \
libonig-dev libzip-dev unzip git curl \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
bcmath gd mbstring pdo_mysql zip opcache \
&& if [ "$INSTALL_XDEBUG" = "true" ]; then \
pecl install xdebug && docker-php-ext-enable xdebug; \
fi \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
COPY ./docker/php/custom.ini /usr/local/etc/php/conf.d/custom.ini
WORKDIR /var/www/html
USER www-data Nginx virtual host configuration
Your Nginx config must pass PHP requests to the FPM socket or TCP port while serving static assets directly. A common failure mode is misconfigured fastcgi_param SCRIPT_FILENAME; always use $realpath_root$fastcgi_script_name to resolve symlinks correctly during local development. Set client_max_body_size to match your upload limits and disable access logs for static assets to reduce I/O noise.
OPcache settings for development
Production OPcache settings destroy developer productivity. In your custom.ini, set opcache.validate_timestamps=1 and opcache.revalidate_freq=0 so PHP checks for file changes on every request. This adds ~5ms per request but eliminates the "why isn't my change showing?" debugging sessions. Keep opcache.enable_cli=0 unless you run long-lived workers.
How do you handle database persistence and seeding in containers?
Data loss is the primary complaint from developers adopting containerized databases. Your Docker Compose for local Laravel dev environment must guarantee that running docker compose down does not wipe your test data, while still allowing a clean reset when needed. This requires understanding the difference between stopping, removing, and pruning containers versus volumes.
Automating initialization
MariaDB and PostgreSQL images execute SQL files placed in /docker-entrypoint-initdb.d/ only on first volume creation. Place a create-database.sql script there to set up your database name, user, and grants automatically. For Laravel-specific setup, create an entrypoint script in your PHP container that waits for the database, runs migrations, and seeds data—but only if a FRESH_INSTALL environment variable is set. This prevents re-running destructive commands on every container start.
Handling credentials securely
Never commit production credentials to your repository. Create a .env.docker file specifically for container defaults and add it to .gitignore. Reference these values in your docker-compose.yml using the env_file directive. For teams working across Nepal and global offices, this also prevents timezone and locale mismatches; set TZ=Asia/Kathmandu in your compose file to ensure timestamps align with your business context.
What are the performance trade-offs between bind mounts and named volumes?
Performance is where many Docker Compose for local Laravel dev environment setups fail, particularly on macOS and Windows. Understanding the I/O characteristics of different mount types determines whether your containerized workflow feels native or painfully slow.
| Mount Type | Read Speed | Write Speed | Persistence | Best For |
|---|---|---|---|---|
| Bind Mount (native Linux) | Native | Native | Host filesystem | Linux hosts, CI runners |
| Bind Mount (macOS VirtioFS) | ~90% native | ~85% native | Host filesystem | macOS development (2026+) |
| Bind Mount (macOS gRPC FUSE) | ~60% native | ~40% native | Host filesystem | Legacy macOS Docker Desktop |
| Named Volume | Native | Native | Docker managed | Databases, caches, build artifacts |
| tmpfs | RAM speed | RAM speed | None (ephemeral) | Test suites, temp compilation |
Optimizing macOS file sharing
If you develop on macOS, enable VirtioFS in Docker Desktop settings immediately. The older gRPC FUSE layer causes 3–5x slowdowns on Laravel's typical read-heavy workload (autoloading thousands of classes per request). With VirtioFS enabled in 2026, the gap between native and containerized development has narrowed to negligible levels for most workflows. For teams still experiencing slowness, consider moving vendor/ and node_modules/ to named volumes while keeping application code on bind mounts—this hybrid approach trades some convenience for significant I/O gains.
When to use tmpfs for testing
Laravel test suites generate massive temporary file churn. Mount /tmp and your framework's cache directory as tmpfs in your test runner service definition. This keeps ephemeral writes out of the bind mount entirely and can cut test execution time by 30–40% on large projects. Remember that tmpfs contents vanish when the container stops, which is exactly the behavior you want for test artifacts.
How do you integrate Xdebug and Artisan commands efficiently?
A containerized workflow should not make debugging or CLI tasks harder. Your Docker Compose for local Laravel dev environment must support step-through debugging and convenient Artisan access without requiring developers to memorize lengthy docker compose exec invocations.
Configuring Xdebug 3 correctly
Xdebug 3 changed the connection model fundamentally. Set xdebug.client_host=host.docker.internal (works on Docker Desktop and Colima) rather than hardcoded IPs. Use xdebug.start_with_request=trigger instead of yes to avoid the massive performance penalty of attempting debug connections on every request. Enable debugging only when needed via browser extension or XDEBUG_TRIGGER query parameter. Map your local source path to /var/www/html in your IDE's launch configuration—this path mapping is where most developers get stuck.
Creating Artisan shell aliases
Typing docker compose exec app php artisan dozens of times daily destroys flow. Add a function to your team's shared .bashrc or project-level Makefile:
# Add to Makefile
artisan:
docker compose exec app php artisan $(ARGS)
migrate:
docker compose exec app php artisan migrate:fresh --seed
test:
docker compose exec app php artisan test --parallel This reduces cognitive load and makes the containerized workflow feel identical to native development. For teams transitioning from Valet or native PHP, this familiarity accelerates adoption significantly. See local Laravel dev with Sail and Docker if you prefer Laravel's official abstraction layer, though direct Compose configuration offers more control for complex stacks.
Queue worker handling
Don't forget background jobs. Add a dedicated worker service in your compose file that runs php artisan queue:work --tries=3 --timeout=60. Without this, queued emails, notifications, and jobs silently accumulate during local development, causing confusion when features appear broken. Set restart: unless-stopped so the worker auto-recovers after crashes, mirroring supervisor behavior in production.
Streamline Your Local Laravel Workflow
A well-configured Docker Compose for local Laravel dev environment pays dividends across every sprint: consistent PHP versions, isolated dependencies, and production-parity architecture that catches deployment issues before they reach staging. Start with the four-service pattern outlined here, optimize your bind mounts for your host OS, and integrate Xdebug and Artisan aliases from day one. If your team needs help designing a containerized development workflow that scales from solo developers to distributed teams across Nepal and beyond, reach out to discuss your infrastructure requirements.