docker.recipes

Production Laravel Stack

advanced

Production Laravel with Nginx, PHP-FPM, MySQL, Redis, and Laravel Horizon for queues

Overview

Laravel is a PHP web application framework that follows the Model-View-Controller (MVC) architectural pattern, known for its elegant syntax, built-in ORM (Eloquent), and comprehensive tooling ecosystem. This production-ready stack combines Laravel with PHP-FPM for efficient PHP process management, NGINX as a high-performance reverse proxy and static file server, MySQL for reliable relational data storage, Redis for caching and session management, and Laravel Horizon for sophisticated queue monitoring and management. The architecture separates concerns effectively: NGINX handles HTTP requests and serves static assets while proxying dynamic requests to PHP-FPM, MySQL manages persistent data with ACID compliance, Redis provides sub-millisecond response times for caching and session storage, and Horizon offers real-time queue monitoring with automatic worker scaling. This combination is ideal for businesses running Laravel applications that require high availability, scalability, and professional queue management, particularly e-commerce platforms, SaaS applications, and content management systems that need robust background job processing and real-time features.

Key Features

  • Laravel Horizon dashboard for real-time queue monitoring with automatic worker balancing
  • PHP-FPM process manager with optimized memory usage and request handling
  • NGINX event-driven architecture serving static assets and proxying dynamic requests
  • Redis-based session storage and caching with sub-millisecond response times
  • MySQL InnoDB storage engine with ACID compliance and transaction support
  • Laravel scheduler container running artisan commands via cron-like functionality
  • Separated application, queue worker, and scheduler containers for scalability
  • Production-optimized Laravel configuration with opcache and Redis drivers

Common Use Cases

  • 1E-commerce platforms requiring background order processing and inventory management
  • 2SaaS applications with user authentication, subscription billing, and email notifications
  • 3Content management systems with media processing and scheduled publishing
  • 4API-driven applications serving mobile apps and third-party integrations
  • 5Multi-tenant applications requiring session isolation and background tenant provisioning
  • 6Educational platforms with automated course enrollment and progress tracking
  • 7Marketing platforms processing email campaigns and analytics data

Prerequisites

  • Docker Engine 20.0+ and Docker Compose 2.0+ installed on the host system
  • Minimum 2GB RAM available (1GB for MySQL, 512MB for Redis, 512MB for Laravel containers)
  • Port 80 available for NGINX web server (configurable via NGINX_PORT environment variable)
  • Generated Laravel APP_KEY using 'php artisan key:generate --show' command
  • Basic understanding of Laravel framework, Eloquent ORM, and queue job concepts
  • Familiarity with Redis data structures and MySQL database administration

For development & testing. Review security settings, change default credentials, and test thoroughly before production use. See Terms

docker-compose.yml

docker-compose.yml
1services:
2 nginx:
3 image: nginx:alpine
4 container_name: laravel-nginx
5 restart: unless-stopped
6 ports:
7 - "${NGINX_PORT:-80}:80"
8 volumes:
9 - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
10 - ./:/var/www/html
11 depends_on:
12 - app
13
14 app:
15 build:
16 context: .
17 dockerfile: Dockerfile
18 container_name: laravel-app
19 restart: unless-stopped
20 volumes:
21 - ./:/var/www/html
22 environment:
23 - APP_ENV=production
24 - APP_KEY=${APP_KEY}
25 - APP_DEBUG=false
26 - DB_CONNECTION=mysql
27 - DB_HOST=db
28 - DB_PORT=3306
29 - DB_DATABASE=${DB_NAME}
30 - DB_USERNAME=${DB_USER}
31 - DB_PASSWORD=${DB_PASSWORD}
32 - REDIS_HOST=redis
33 - CACHE_DRIVER=redis
34 - SESSION_DRIVER=redis
35 - QUEUE_CONNECTION=redis
36 depends_on:
37 - db
38 - redis
39
40 horizon:
41 build:
42 context: .
43 dockerfile: Dockerfile
44 container_name: laravel-horizon
45 restart: unless-stopped
46 command: php artisan horizon
47 volumes:
48 - ./:/var/www/html
49 environment:
50 - APP_ENV=production
51 - APP_KEY=${APP_KEY}
52 - DB_CONNECTION=mysql
53 - DB_HOST=db
54 - DB_DATABASE=${DB_NAME}
55 - DB_USERNAME=${DB_USER}
56 - DB_PASSWORD=${DB_PASSWORD}
57 - REDIS_HOST=redis
58 - QUEUE_CONNECTION=redis
59 depends_on:
60 - db
61 - redis
62
63 scheduler:
64 build:
65 context: .
66 dockerfile: Dockerfile
67 container_name: laravel-scheduler
68 restart: unless-stopped
69 command: sh -c "while true; do php artisan schedule:run; sleep 60; done"
70 volumes:
71 - ./:/var/www/html
72 environment:
73 - APP_ENV=production
74 - APP_KEY=${APP_KEY}
75 - DB_HOST=db
76 - DB_DATABASE=${DB_NAME}
77 - DB_USERNAME=${DB_USER}
78 - DB_PASSWORD=${DB_PASSWORD}
79 - REDIS_HOST=redis
80 depends_on:
81 - db
82 - redis
83
84 db:
85 image: mysql:8.0
86 container_name: laravel-db
87 restart: unless-stopped
88 environment:
89 - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
90 - MYSQL_DATABASE=${DB_NAME}
91 - MYSQL_USER=${DB_USER}
92 - MYSQL_PASSWORD=${DB_PASSWORD}
93 volumes:
94 - mysql_data:/var/lib/mysql
95
96 redis:
97 image: redis:7-alpine
98 container_name: laravel-redis
99 restart: unless-stopped
100 volumes:
101 - redis_data:/data
102
103volumes:
104 mysql_data:
105 redis_data:

.env Template

.env
1# Laravel Production Stack
2NGINX_PORT=80
3
4# Laravel
5APP_KEY=base64:your-app-key-here
6
7# Database
8DB_ROOT_PASSWORD=root_password
9DB_NAME=laravel
10DB_USER=laravel
11DB_PASSWORD=laravel_password

Usage Notes

  1. 1Create Dockerfile with PHP-FPM and Laravel dependencies
  2. 2Generate APP_KEY: php artisan key:generate --show
  3. 3Run migrations: docker compose exec app php artisan migrate
  4. 4Horizon dashboard at /horizon (configure auth)
  5. 5Scheduler runs artisan schedule:run every minute
  6. 6Optimize: docker compose exec app php artisan optimize

Individual Services(6 services)

Copy individual services to mix and match with your existing compose files.

nginx
nginx:
  image: nginx:alpine
  container_name: laravel-nginx
  restart: unless-stopped
  ports:
    - ${NGINX_PORT:-80}:80
  volumes:
    - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    - ./:/var/www/html
  depends_on:
    - app
app
app:
  build:
    context: .
    dockerfile: Dockerfile
  container_name: laravel-app
  restart: unless-stopped
  volumes:
    - ./:/var/www/html
  environment:
    - APP_ENV=production
    - APP_KEY=${APP_KEY}
    - APP_DEBUG=false
    - DB_CONNECTION=mysql
    - DB_HOST=db
    - DB_PORT=3306
    - DB_DATABASE=${DB_NAME}
    - DB_USERNAME=${DB_USER}
    - DB_PASSWORD=${DB_PASSWORD}
    - REDIS_HOST=redis
    - CACHE_DRIVER=redis
    - SESSION_DRIVER=redis
    - QUEUE_CONNECTION=redis
  depends_on:
    - db
    - redis
horizon
horizon:
  build:
    context: .
    dockerfile: Dockerfile
  container_name: laravel-horizon
  restart: unless-stopped
  command: php artisan horizon
  volumes:
    - ./:/var/www/html
  environment:
    - APP_ENV=production
    - APP_KEY=${APP_KEY}
    - DB_CONNECTION=mysql
    - DB_HOST=db
    - DB_DATABASE=${DB_NAME}
    - DB_USERNAME=${DB_USER}
    - DB_PASSWORD=${DB_PASSWORD}
    - REDIS_HOST=redis
    - QUEUE_CONNECTION=redis
  depends_on:
    - db
    - redis
scheduler
scheduler:
  build:
    context: .
    dockerfile: Dockerfile
  container_name: laravel-scheduler
  restart: unless-stopped
  command: sh -c "while true; do php artisan schedule:run; sleep 60; done"
  volumes:
    - ./:/var/www/html
  environment:
    - APP_ENV=production
    - APP_KEY=${APP_KEY}
    - DB_HOST=db
    - DB_DATABASE=${DB_NAME}
    - DB_USERNAME=${DB_USER}
    - DB_PASSWORD=${DB_PASSWORD}
    - REDIS_HOST=redis
  depends_on:
    - db
    - redis
db
db:
  image: mysql:8.0
  container_name: laravel-db
  restart: unless-stopped
  environment:
    - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
    - MYSQL_DATABASE=${DB_NAME}
    - MYSQL_USER=${DB_USER}
    - MYSQL_PASSWORD=${DB_PASSWORD}
  volumes:
    - mysql_data:/var/lib/mysql
redis
redis:
  image: redis:7-alpine
  container_name: laravel-redis
  restart: unless-stopped
  volumes:
    - redis_data:/data

Quick Start

terminal
1# 1. Create the compose file
2cat > docker-compose.yml << 'EOF'
3services:
4 nginx:
5 image: nginx:alpine
6 container_name: laravel-nginx
7 restart: unless-stopped
8 ports:
9 - "${NGINX_PORT:-80}:80"
10 volumes:
11 - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
12 - ./:/var/www/html
13 depends_on:
14 - app
15
16 app:
17 build:
18 context: .
19 dockerfile: Dockerfile
20 container_name: laravel-app
21 restart: unless-stopped
22 volumes:
23 - ./:/var/www/html
24 environment:
25 - APP_ENV=production
26 - APP_KEY=${APP_KEY}
27 - APP_DEBUG=false
28 - DB_CONNECTION=mysql
29 - DB_HOST=db
30 - DB_PORT=3306
31 - DB_DATABASE=${DB_NAME}
32 - DB_USERNAME=${DB_USER}
33 - DB_PASSWORD=${DB_PASSWORD}
34 - REDIS_HOST=redis
35 - CACHE_DRIVER=redis
36 - SESSION_DRIVER=redis
37 - QUEUE_CONNECTION=redis
38 depends_on:
39 - db
40 - redis
41
42 horizon:
43 build:
44 context: .
45 dockerfile: Dockerfile
46 container_name: laravel-horizon
47 restart: unless-stopped
48 command: php artisan horizon
49 volumes:
50 - ./:/var/www/html
51 environment:
52 - APP_ENV=production
53 - APP_KEY=${APP_KEY}
54 - DB_CONNECTION=mysql
55 - DB_HOST=db
56 - DB_DATABASE=${DB_NAME}
57 - DB_USERNAME=${DB_USER}
58 - DB_PASSWORD=${DB_PASSWORD}
59 - REDIS_HOST=redis
60 - QUEUE_CONNECTION=redis
61 depends_on:
62 - db
63 - redis
64
65 scheduler:
66 build:
67 context: .
68 dockerfile: Dockerfile
69 container_name: laravel-scheduler
70 restart: unless-stopped
71 command: sh -c "while true; do php artisan schedule:run; sleep 60; done"
72 volumes:
73 - ./:/var/www/html
74 environment:
75 - APP_ENV=production
76 - APP_KEY=${APP_KEY}
77 - DB_HOST=db
78 - DB_DATABASE=${DB_NAME}
79 - DB_USERNAME=${DB_USER}
80 - DB_PASSWORD=${DB_PASSWORD}
81 - REDIS_HOST=redis
82 depends_on:
83 - db
84 - redis
85
86 db:
87 image: mysql:8.0
88 container_name: laravel-db
89 restart: unless-stopped
90 environment:
91 - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
92 - MYSQL_DATABASE=${DB_NAME}
93 - MYSQL_USER=${DB_USER}
94 - MYSQL_PASSWORD=${DB_PASSWORD}
95 volumes:
96 - mysql_data:/var/lib/mysql
97
98 redis:
99 image: redis:7-alpine
100 container_name: laravel-redis
101 restart: unless-stopped
102 volumes:
103 - redis_data:/data
104
105volumes:
106 mysql_data:
107 redis_data:
108EOF
109
110# 2. Create the .env file
111cat > .env << 'EOF'
112# Laravel Production Stack
113NGINX_PORT=80
114
115# Laravel
116APP_KEY=base64:your-app-key-here
117
118# Database
119DB_ROOT_PASSWORD=root_password
120DB_NAME=laravel
121DB_USER=laravel
122DB_PASSWORD=laravel_password
123EOF
124
125# 3. Start the services
126docker compose up -d
127
128# 4. View logs
129docker compose logs -f

One-Liner

Run this command to download and set up the recipe in one step:

terminal
1curl -fsSL https://docker.recipes/api/recipes/production-laravel-stack/run | bash

Troubleshooting

  • Laravel Horizon shows 'Inactive' status: Ensure Redis connection is working and run 'php artisan horizon:terminate' then restart the horizon container
  • MySQL connection refused errors: Verify DB_HOST=db matches the service name and database container is fully initialized before application startup
  • PHP-FPM 502 Bad Gateway errors: Check PHP-FPM pool configuration and ensure sufficient worker processes are configured for your traffic load
  • Redis connection timeout issues: Increase Redis timeout settings in config/database.php and verify REDIS_HOST environment variable matches service name
  • Queue jobs not processing: Confirm QUEUE_CONNECTION=redis is set and Horizon workers are active in the dashboard at /horizon
  • Laravel scheduler not running: Verify the scheduler container is running and check Laravel's app/Console/Kernel.php for scheduled task definitions

Community Notes

Loading...
Loading notes...

Download Recipe Kit

Get all files in a ready-to-deploy package

Includes docker-compose.yml, .env template, README, and license

Ad Space