{"id":46,"date":"2026-07-10T12:30:00","date_gmt":"2026-07-10T07:00:00","guid":{"rendered":"https:\/\/softcrony.com\/blog\/?p=46"},"modified":"2026-07-10T12:30:00","modified_gmt":"2026-07-10T07:00:00","slug":"docker-compose-laravel-php-2026","status":"publish","type":"post","link":"https:\/\/softcrony.com\/blog\/docker-compose-laravel-php-2026\/","title":{"rendered":"Docker Compose in 2026: The Complete Guide for PHP and Laravel Developers"},"content":{"rendered":"<p>Docker Compose has become the standard way to run local development environments for PHP and Laravel projects. One command starts your entire stack \u2014 PHP, MySQL, Redis, Nginx, queue workers \u2014 consistently across every developer&#8217;s machine.<\/p>\n<p>This guide covers everything you need for a production-grade Docker Compose setup for Laravel in 2026.<\/p>\n<h2>Why Docker Compose for Laravel?<\/h2>\n<p>The classic problem: &#8220;It works on my machine.&#8221; Docker Compose eliminates this by defining your entire environment as code. Every developer runs identical containers, and your staging environment matches production exactly.<\/p>\n<ul>\n<li>No more PHP version conflicts between developers<\/li>\n<li>MySQL, Redis, and other services start with one command<\/li>\n<li>New team members are productive in minutes, not hours<\/li>\n<li>Local environment matches staging and production exactly<\/li>\n<\/ul>\n<h2>Project Structure<\/h2>\n<pre><code>your-laravel-app\/\r\n\u251c\u2500\u2500 docker\/\r\n\u2502   \u251c\u2500\u2500 php\/\r\n\u2502   \u2502   \u2514\u2500\u2500 Dockerfile\r\n\u2502   \u251c\u2500\u2500 nginx\/\r\n\u2502   \u2502   \u2514\u2500\u2500 default.conf\r\n\u2502   \u2514\u2500\u2500 mysql\/\r\n\u2502       \u2514\u2500\u2500 my.cnf\r\n\u251c\u2500\u2500 docker-compose.yml\r\n\u251c\u2500\u2500 docker-compose.prod.yml\r\n\u2514\u2500\u2500 ... (Laravel files)<\/code><\/pre>\n<h2>Step 1 \u2014 PHP Dockerfile<\/h2>\n<p>Create <code>docker\/php\/Dockerfile<\/code>:<\/p>\n<pre><code>FROM php:8.3-fpm-alpine\r\n\r\n# Install system dependencies\r\nRUN apk add --no-cache \\\r\n    git \\\r\n    curl \\\r\n    libpng-dev \\\r\n    libxml2-dev \\\r\n    zip \\\r\n    unzip \\\r\n    nodejs \\\r\n    npm\r\n\r\n# Install PHP extensions\r\nRUN docker-php-ext-install \\\r\n    pdo_mysql \\\r\n    mbstring \\\r\n    exif \\\r\n    pcntl \\\r\n    bcmath \\\r\n    gd \\\r\n    xml \\\r\n    opcache\r\n\r\n# Install Redis extension\r\nRUN pecl install redis &amp;&amp; docker-php-ext-enable redis\r\n\r\n# Install Composer\r\nCOPY --from=composer:latest \/usr\/bin\/composer \/usr\/bin\/composer\r\n\r\n# Set working directory\r\nWORKDIR \/var\/www\r\n\r\n# Copy application files\r\nCOPY . .\r\n\r\n# Install PHP dependencies\r\nRUN composer install --no-dev --optimize-autoloader\r\n\r\n# Set permissions\r\nRUN chown -R www-data:www-data \/var\/www\/storage \/var\/www\/bootstrap\/cache\r\n\r\nEXPOSE 9000\r\nCMD [\"php-fpm\"]<\/code><\/pre>\n<h2>Step 2 \u2014 Nginx Configuration<\/h2>\n<p>Create <code>docker\/nginx\/default.conf<\/code>:<\/p>\n<pre><code>server {\r\n    listen 80;\r\n    server_name localhost;\r\n    root \/var\/www\/public;\r\n    index index.php index.html;\r\n\r\n    # Security headers\r\n    add_header X-Frame-Options \"SAMEORIGIN\";\r\n    add_header X-Content-Type-Options \"nosniff\";\r\n    add_header X-XSS-Protection \"1; mode=block\";\r\n\r\n    location \/ {\r\n        try_files $uri $uri\/ \/index.php?$query_string;\r\n    }\r\n\r\n    location = \/favicon.ico { access_log off; log_not_found off; }\r\n    location = \/robots.txt  { access_log off; log_not_found off; }\r\n\r\n    error_page 404 \/index.php;\r\n\r\n    location ~ \\.php$ {\r\n        fastcgi_pass php:9000;\r\n        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;\r\n        include fastcgi_params;\r\n        fastcgi_hide_header X-Powered-By;\r\n    }\r\n\r\n    location ~ \/\\.(?!well-known).* {\r\n        deny all;\r\n    }\r\n\r\n    # Cache static assets\r\n    location ~* \\.(jpg|jpeg|png|gif|ico|css|js|woff|woff2)$ {\r\n        expires 1y;\r\n        add_header Cache-Control \"public, immutable\";\r\n    }\r\n}<\/code><\/pre>\n<h2>Step 3 \u2014 docker-compose.yml<\/h2>\n<pre><code>version: '3.9'\r\n\r\nservices:\r\n  # Nginx Web Server\r\n  nginx:\r\n    image: nginx:alpine\r\n    container_name: laravel_nginx\r\n    restart: unless-stopped\r\n    ports:\r\n      - \"80:80\"\r\n    volumes:\r\n      - .:\/var\/www\r\n      - .\/docker\/nginx\/default.conf:\/etc\/nginx\/conf.d\/default.conf\r\n    depends_on:\r\n      - php\r\n    networks:\r\n      - laravel\r\n\r\n  # PHP-FPM\r\n  php:\r\n    build:\r\n      context: .\r\n      dockerfile: docker\/php\/Dockerfile\r\n    container_name: laravel_php\r\n    restart: unless-stopped\r\n    volumes:\r\n      - .:\/var\/www\r\n      - .\/docker\/php\/php.ini:\/usr\/local\/etc\/php\/conf.d\/custom.ini\r\n    environment:\r\n      - APP_ENV=local\r\n    networks:\r\n      - laravel\r\n    depends_on:\r\n      mysql:\r\n        condition: service_healthy\r\n      redis:\r\n        condition: service_started\r\n\r\n  # MySQL Database\r\n  mysql:\r\n    image: mysql:8.0\r\n    container_name: laravel_mysql\r\n    restart: unless-stopped\r\n    ports:\r\n      - \"3306:3306\"\r\n    environment:\r\n      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}\r\n      MYSQL_DATABASE: ${DB_DATABASE}\r\n      MYSQL_USER: ${DB_USERNAME}\r\n      MYSQL_PASSWORD: ${DB_PASSWORD}\r\n    volumes:\r\n      - mysql_data:\/var\/lib\/mysql\r\n      - .\/docker\/mysql\/my.cnf:\/etc\/mysql\/conf.d\/my.cnf\r\n    healthcheck:\r\n      test: [\"CMD\", \"mysqladmin\", \"ping\", \"-h\", \"localhost\"]\r\n      interval: 10s\r\n      timeout: 5s\r\n      retries: 5\r\n    networks:\r\n      - laravel\r\n\r\n  # Redis Cache\r\n  redis:\r\n    image: redis:7-alpine\r\n    container_name: laravel_redis\r\n    restart: unless-stopped\r\n    ports:\r\n      - \"6379:6379\"\r\n    volumes:\r\n      - redis_data:\/data\r\n    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}\r\n    networks:\r\n      - laravel\r\n\r\n  # Laravel Queue Worker\r\n  queue:\r\n    build:\r\n      context: .\r\n      dockerfile: docker\/php\/Dockerfile\r\n    container_name: laravel_queue\r\n    restart: unless-stopped\r\n    command: php artisan queue:work --sleep=3 --tries=3 --max-time=3600\r\n    volumes:\r\n      - .:\/var\/www\r\n    depends_on:\r\n      - php\r\n      - redis\r\n    networks:\r\n      - laravel\r\n\r\n  # Laravel Scheduler\r\n  scheduler:\r\n    build:\r\n      context: .\r\n      dockerfile: docker\/php\/Dockerfile\r\n    container_name: laravel_scheduler\r\n    restart: unless-stopped\r\n    command: sh -c \"while true; do php artisan schedule:run --verbose --no-interaction &amp;&amp; sleep 60; done\"\r\n    volumes:\r\n      - .:\/var\/www\r\n    depends_on:\r\n      - php\r\n      - mysql\r\n    networks:\r\n      - laravel\r\n\r\n  # Mailpit (local email testing)\r\n  mailpit:\r\n    image: axllent\/mailpit:latest\r\n    container_name: laravel_mailpit\r\n    ports:\r\n      - \"8025:8025\"\r\n      - \"1025:1025\"\r\n    networks:\r\n      - laravel\r\n\r\nvolumes:\r\n  mysql_data:\r\n  redis_data:\r\n\r\nnetworks:\r\n  laravel:\r\n    driver: bridge<\/code><\/pre>\n<h2>Step 4 \u2014 Environment Variables<\/h2>\n<p>Update your <code>.env<\/code> file for Docker:<\/p>\n<pre><code>APP_URL=http:\/\/localhost\r\n\r\nDB_CONNECTION=mysql\r\nDB_HOST=mysql\r\nDB_PORT=3306\r\nDB_DATABASE=laravel\r\nDB_USERNAME=laravel\r\nDB_PASSWORD=secret\r\n\r\nREDIS_HOST=redis\r\nREDIS_PASSWORD=secret\r\nREDIS_PORT=6379\r\n\r\nCACHE_STORE=redis\r\nSESSION_DRIVER=redis\r\nQUEUE_CONNECTION=redis\r\n\r\nMAIL_MAILER=smtp\r\nMAIL_HOST=mailpit\r\nMAIL_PORT=1025<\/code><\/pre>\n<h2>Step 5 \u2014 Common Commands<\/h2>\n<pre><code># Start all containers\r\ndocker compose up -d\r\n\r\n# Stop all containers\r\ndocker compose down\r\n\r\n# Run artisan commands\r\ndocker compose exec php php artisan migrate\r\ndocker compose exec php php artisan db:seed\r\ndocker compose exec php php artisan cache:clear\r\n\r\n# Run composer\r\ndocker compose exec php composer install\r\n\r\n# Run npm (if needed)\r\ndocker compose exec php npm install\r\ndocker compose exec php npm run build\r\n\r\n# View logs\r\ndocker compose logs -f php\r\ndocker compose logs -f queue\r\n\r\n# Access MySQL\r\ndocker compose exec mysql mysql -u laravel -p laravel\r\n\r\n# Rebuild after Dockerfile changes\r\ndocker compose build --no-cache php\r\ndocker compose up -d<\/code><\/pre>\n<h2>Production Configuration<\/h2>\n<p>Create a separate <code>docker-compose.prod.yml<\/code> that overrides development settings:<\/p>\n<pre><code>version: '3.9'\r\n\r\nservices:\r\n  php:\r\n    build:\r\n      target: production\r\n    environment:\r\n      - APP_ENV=production\r\n      - APP_DEBUG=false\r\n\r\n  nginx:\r\n    ports:\r\n      - \"443:443\"\r\n    volumes:\r\n      - .\/docker\/nginx\/prod.conf:\/etc\/nginx\/conf.d\/default.conf\r\n      - \/etc\/letsencrypt:\/etc\/letsencrypt:ro\r\n\r\n  # Remove mailpit in production\r\n  mailpit:\r\n    profiles:\r\n      - dev<\/code><\/pre>\n<p>Run in production:<\/p>\n<pre><code>docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d<\/code><\/pre>\n<h2>Performance Tips<\/h2>\n<p><strong>Use Alpine images<\/strong> \u2014 <code>php:8.3-fpm-alpine<\/code> is 80% smaller than the full Debian image.<\/p>\n<p><strong>Enable OPcache in production<\/strong> \u2014 add to your PHP configuration:<\/p>\n<pre><code>opcache.enable=1\r\nopcache.memory_consumption=256\r\nopcache.interned_strings_buffer=64\r\nopcache.max_accelerated_files=20000\r\nopcache.validate_timestamps=0<\/code><\/pre>\n<p><strong>Use health checks<\/strong> \u2014 as shown in the MySQL service above. This prevents Laravel from starting before the database is ready.<\/p>\n<p><strong>Named volumes for persistence<\/strong> \u2014 always use named volumes for database data, never bind mounts.<\/p>\n<p>If you need Docker Compose set up for your Laravel project or want a containerized deployment pipeline built for your team, <a href=\"https:\/\/softcrony.com\/contact\/\">our DevOps team at Softcrony can help<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Docker Compose has become the standard way to run local development environments for PHP and Laravel projects. One command starts your entire stack \u2014 PHP, MySQL, Redis, Nginx, queue workers \u2014 consistently across every developer&#8217;s machine. This guide covers everything you need for a production-grade Docker Compose setup for Laravel in 2026. Why Docker Compose [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":47,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[53,52,54,19,30],"class_list":["post-46","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","tag-devops","tag-docker","tag-docker-compose","tag-laravel","tag-php"],"_links":{"self":[{"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/posts\/46","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/comments?post=46"}],"version-history":[{"count":1,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/posts\/46\/revisions"}],"predecessor-version":[{"id":48,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/posts\/46\/revisions\/48"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/media\/47"}],"wp:attachment":[{"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/media?parent=46"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/categories?post=46"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/tags?post=46"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}