From 8919813f45bc88b12eaa2aa26b139544a039bf01 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 02:59:57 +0000 Subject: [PATCH 1/6] Initial plan From e6c2981684a39e60481128b6d09237368d0cd081 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 03:14:01 +0000 Subject: [PATCH 2/6] Add Laravel Octane support with configurable app_server_mode Co-authored-by: leek <60204+leek@users.noreply.github.com> --- README.md | 26 +++++++++++++++++++ docker/Dockerfile | 9 ++++--- docker/README.md | 28 ++++++++++++++++++-- docker/entrypoint.sh | 27 ++++++++++++++++++-- docker/nginx/custom.d/laravel-octane.conf | 30 ++++++++++++++++++++++ docker/supervisord-web-octane.conf | 28 ++++++++++++++++++++ terraform/environments/example.tfvars | 28 ++++++++++++-------- terraform/main.tf | 31 ++++++++++++----------- terraform/modules/compute/main.tf | 4 +++ terraform/modules/compute/variables.tf | 6 +++++ terraform/variables.tf | 11 ++++++++ 11 files changed, 195 insertions(+), 33 deletions(-) create mode 100644 docker/nginx/custom.d/laravel-octane.conf create mode 100644 docker/supervisord-web-octane.conf diff --git a/README.md b/README.md index 5d62c65..3fb88ca 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,26 @@ aws ecs update-service --cluster $CLUSTER --service $(terraform output -raw ecs_ ## Configuration Guide +### Application Server Mode + +Choose between **PHP-FPM** (default) or **Laravel Octane** for serving your application: + +```hcl +# Traditional PHP-FPM with Nginx (default, most compatible) +app_server_mode = "php-fpm" + +# Laravel Octane with Swoole (better performance, requires Laravel 8+ with Octane package) +app_server_mode = "octane" +``` + +**Laravel Octane Benefits:** +- 2-5x better request throughput +- Lower latency for API responses +- Reduced memory usage per request +- Better for high-traffic applications + +**Important:** Ensure your Laravel application is installed with the Octane package and is compatible with Octane (no global state, stateless services). See [Laravel Octane documentation](https://laravel.com/docs/octane) for details. + ### Minimal Configuration For a basic setup (good for staging/development): @@ -167,6 +187,9 @@ github_repo = "your-repo" app_db_password = "..." db_reporting_password = "..." +# Application server mode +app_server_mode = "php-fpm" # or "octane" for better performance + # Small instance sizes container_cpu = 512 container_memory = 1024 @@ -201,6 +224,9 @@ github_repo = "your-repo" app_db_password = "..." db_reporting_password = "..." +# Use Laravel Octane for better performance in production +app_server_mode = "octane" + # Larger instance sizes container_cpu = 2048 container_memory = 4096 diff --git a/docker/Dockerfile b/docker/Dockerfile index 1af6c0a..fa3ef64 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -53,9 +53,9 @@ RUN docker-php-ext-configure gd --with-freetype --with-jpeg \ zip \ pcntl \ sockets \ - # PECL - && pecl install redis \ - && docker-php-ext-enable redis \ + # PECL - Install redis and swoole (for Laravel Octane) + && pecl install redis swoole \ + && docker-php-ext-enable redis swoole \ # Cleanup after PHP extensions are built && apk del build-base autoconf automake libtool nasm git \ && rm -rf /var/cache/apk/* /tmp/* /var/tmp/* @@ -143,17 +143,20 @@ COPY --chown=www-data:www-data docker/nginx/ /etc/nginx COPY docker/php/php-fpm.conf /usr/local/etc/php-fpm.d/www.conf COPY docker/php/php.ini /usr/local/etc/php/conf.d/99-custom.ini COPY docker/supervisord-web.conf /etc/supervisor/conf.d/supervisord-web.conf +COPY docker/supervisord-web-octane.conf /etc/supervisor/conf.d/supervisord-web-octane.conf COPY docker/supervisord-queue-worker.conf /etc/supervisor/conf.d/supervisord-queue-worker.conf COPY docker/supervisord-scheduler.conf /etc/supervisor/conf.d/supervisord-scheduler.conf COPY docker/entrypoint.sh /entrypoint.sh # Make entrypoint executable and create supervisor log directory +# Disable Octane config by default (will be enabled by entrypoint.sh if APP_SERVER_MODE=octane) RUN chmod +x /entrypoint.sh \ && mkdir -p /var/log/supervisor \ && mkdir -p /var/www/html/storage /var/www/html/bootstrap/cache \ && chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache \ && chmod -R 775 /var/www/html/storage /var/www/html/bootstrap/cache \ && chown -R www-data:www-data /etc/nginx/custom.d \ + && mv /etc/nginx/custom.d/laravel-octane.conf /etc/nginx/custom.d/.laravel-octane.conf \ && mkdir -p /var/lib/nginx/tmp/client_body \ /var/lib/nginx/tmp/proxy \ /var/lib/nginx/tmp/fastcgi \ diff --git a/docker/README.md b/docker/README.md index a059a83..9150f98 100644 --- a/docker/README.md +++ b/docker/README.md @@ -2,10 +2,25 @@ This Docker image supports multiple container roles via the `CONTAINER_ROLE` environment variable: -- **web** (default): Runs nginx + php-fpm to serve the web application +- **web** (default): Runs nginx + php-fpm (or Laravel Octane) to serve the web application - **queue-worker**: Runs Laravel queue workers to process SQS jobs - **scheduler**: Runs Laravel scheduler to dispatch scheduled tasks +## Application Server Mode + +The **web** container role supports two modes via the `APP_SERVER_MODE` environment variable: + +- **php-fpm** (default): Traditional PHP-FPM with Nginx as FastCGI proxy + - Most compatible with all Laravel applications + - Battle-tested and stable + - Good for applications with moderate traffic + +- **octane**: Laravel Octane with Swoole and Nginx as reverse proxy + - 2-5x better performance and throughput + - Lower latency and memory usage + - Requires Laravel 8+ with Octane package installed + - Application must be Octane-compatible (no global state) + ## Architecture The ECS deployment consists of three separate services: @@ -25,10 +40,19 @@ This separation ensures: # Build the container docker build -f docker/Dockerfile -t laravel-local . -# Run web server locally +# Run web server locally with PHP-FPM (default) +docker run -p 8080:80 \ + -e APP_ENV=local \ + -e CONTAINER_ROLE=web \ + -e APP_SERVER_MODE=php-fpm \ + -e APP_KEY=base64:$(php artisan key:generate --show) \ + laravel-local + +# Run web server locally with Laravel Octane docker run -p 8080:80 \ -e APP_ENV=local \ -e CONTAINER_ROLE=web \ + -e APP_SERVER_MODE=octane \ -e APP_KEY=base64:$(php artisan key:generate --show) \ laravel-local diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index a3692b1..48ae01d 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -11,6 +11,9 @@ set -e # This can be set via environment variable CONTAINER_ROLE CONTAINER_ROLE=${CONTAINER_ROLE:-web} +# Determine application server mode (php-fpm or octane) +APP_SERVER_MODE=${APP_SERVER_MODE:-php-fpm} + # Create .env file from .env.example if it doesn't exist if [ ! -f /var/www/html/.env ]; then echo "Creating .env file from .env.example..." @@ -47,8 +50,28 @@ php artisan config:cache --no-interaction || true # Select the appropriate supervisord config based on container role case "$CONTAINER_ROLE" in web) - echo "Starting web server..." - SUPERVISOR_CONF="/etc/supervisor/conf.d/supervisord-web.conf" + # Select web server config based on APP_SERVER_MODE + if [ "$APP_SERVER_MODE" = "octane" ]; then + echo "Starting web server with Laravel Octane..." + SUPERVISOR_CONF="/etc/supervisor/conf.d/supervisord-web-octane.conf" + # Swap nginx config to use Octane reverse proxy + if [ -f /etc/nginx/custom.d/laravel.conf ]; then + mv /etc/nginx/custom.d/laravel.conf /etc/nginx/custom.d/.laravel.conf.disabled + fi + if [ -f /etc/nginx/custom.d/.laravel-octane.conf ]; then + mv /etc/nginx/custom.d/.laravel-octane.conf /etc/nginx/custom.d/laravel-octane.conf + fi + else + echo "Starting web server with PHP-FPM..." + SUPERVISOR_CONF="/etc/supervisor/conf.d/supervisord-web.conf" + # Ensure PHP-FPM config is active + if [ -f /etc/nginx/custom.d/.laravel.conf.disabled ]; then + mv /etc/nginx/custom.d/.laravel.conf.disabled /etc/nginx/custom.d/laravel.conf + fi + if [ -f /etc/nginx/custom.d/laravel-octane.conf ]; then + mv /etc/nginx/custom.d/laravel-octane.conf /etc/nginx/custom.d/.laravel-octane.conf + fi + fi ;; queue-worker) echo "Starting queue worker..." diff --git a/docker/nginx/custom.d/laravel-octane.conf b/docker/nginx/custom.d/laravel-octane.conf new file mode 100644 index 0000000..6643b88 --- /dev/null +++ b/docker/nginx/custom.d/laravel-octane.conf @@ -0,0 +1,30 @@ +index index.php index.html index.htm; + +# Increase upload size limit +client_max_body_size 32M; + +# Set real IP from ALB +real_ip_header X-Forwarded-For; +set_real_ip_from 10.0.0.0/8; +real_ip_recursive on; + +# Proxy all requests to Laravel Octane +location / { + proxy_pass http://127.0.0.1:8000; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # Pass ALB headers to Octane/Laravel + proxy_set_header X-Forwarded-Host $host; + + # Timeouts + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; +} diff --git a/docker/supervisord-web-octane.conf b/docker/supervisord-web-octane.conf new file mode 100644 index 0000000..eaec058 --- /dev/null +++ b/docker/supervisord-web-octane.conf @@ -0,0 +1,28 @@ +[supervisord] +nodaemon=true +user=root +logfile=/var/log/supervisor/supervisord.log +pidfile=/var/run/supervisord.pid + +[program:nginx] +command=nginx -g "daemon off;" +priority=20 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=true +startretries=3 +startsecs=5 + +[program:octane] +command=php /var/www/html/artisan octane:start --host=127.0.0.1 --port=8000 --workers=auto --max-requests=500 +priority=10 +user=www-data +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=true +startretries=3 +startsecs=5 diff --git a/terraform/environments/example.tfvars b/terraform/environments/example.tfvars index 58a7a43..6c79611 100644 --- a/terraform/environments/example.tfvars +++ b/terraform/environments/example.tfvars @@ -64,24 +64,30 @@ meilisearch_master_key = "CHANGE_ME_MEILISEARCH_KEY" # CPU 2048: 4096 to 16384 (1GB increments) # CPU 4096: 8192 to 30720 (1GB increments) +# Application server mode: "php-fpm" (default) or "octane" +# Laravel Octane provides significantly better performance by keeping the application +# in memory and serving requests through Swoole. Requires Laravel 8+ with Octane installed. +# Note: When using Octane, ensure your application is Octane-compatible (no global state). +app_server_mode = "php-fpm" + # Web Service (handles HTTP requests) -container_cpu = 1024 # CPU units (1024 = 1 vCPU) -container_memory = 2048 # Memory in MB +container_cpu = 1024 # CPU units (1024 = 1 vCPU) +container_memory = 2048 # Memory in MB # Web service scaling configuration -desired_count = 2 # Number of tasks to run -min_capacity = 1 # Minimum tasks for auto-scaling -max_capacity = 10 # Maximum tasks for auto-scaling +desired_count = 2 # Number of tasks to run +min_capacity = 1 # Minimum tasks for auto-scaling +max_capacity = 10 # Maximum tasks for auto-scaling # Queue Worker (processes background jobs) -queue_worker_cpu = 512 # CPU units (512 = 0.5 vCPU) -queue_worker_memory = 1024 # Memory in MB -queue_worker_desired_count = 1 # Number of queue worker tasks +queue_worker_cpu = 512 # CPU units (512 = 0.5 vCPU) +queue_worker_memory = 1024 # Memory in MB +queue_worker_desired_count = 1 # Number of queue worker tasks # Scheduler (runs Laravel's cron/scheduled tasks) -scheduler_cpu = 256 # CPU units (256 = 0.25 vCPU) -scheduler_memory = 512 # Memory in MB -scheduler_desired_count = 1 # Number of scheduler tasks (typically 1) +scheduler_cpu = 256 # CPU units (256 = 0.25 vCPU) +scheduler_memory = 512 # Memory in MB +scheduler_desired_count = 1 # Number of scheduler tasks (typically 1) # ======================================== # Database Configuration diff --git a/terraform/main.tf b/terraform/main.tf index 0664b33..c65b566 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -217,21 +217,21 @@ module "compute" { source = "./modules/compute" depends_on = [module.load_balancer] - app_name = var.app_name - environment = var.environment - aws_region = var.aws_region - domain_name = var.domain_name - vpc_id = module.networking.vpc_id - private_subnets = module.networking.private_subnets - ecs_security_group_id = module.networking.ecs_security_group_id - target_group_arn = module.load_balancer.target_group_arn - ecr_repository_url = module.container_registry.repository_url - ecs_execution_role_arn = module.security.ecs_execution_role_arn - ecs_task_role_arn = module.security.ecs_task_role_arn - log_group_name = module.monitoring.log_group_name - s3_filesystem_bucket_name = module.storage.app_filesystem_bucket_name - sqs_queue_name = module.messaging.queue_name - caller_identity_account_id = data.aws_caller_identity.current.account_id + app_name = var.app_name + environment = var.environment + aws_region = var.aws_region + domain_name = var.domain_name + vpc_id = module.networking.vpc_id + private_subnets = module.networking.private_subnets + ecs_security_group_id = module.networking.ecs_security_group_id + target_group_arn = module.load_balancer.target_group_arn + ecr_repository_url = module.container_registry.repository_url + ecs_execution_role_arn = module.security.ecs_execution_role_arn + ecs_task_role_arn = module.security.ecs_task_role_arn + log_group_name = module.monitoring.log_group_name + s3_filesystem_bucket_name = module.storage.app_filesystem_bucket_name + sqs_queue_name = module.messaging.queue_name + caller_identity_account_id = data.aws_caller_identity.current.account_id # Web service configuration container_cpu = var.container_cpu @@ -254,6 +254,7 @@ module "compute" { meilisearch_master_key = var.enable_meilisearch ? var.meilisearch_master_key : "" redis_endpoint = module.cache.redis_endpoint redis_port = module.cache.redis_port + app_server_mode = var.app_server_mode additional_environment_variables = var.additional_environment_variables common_tags = local.common_tags } diff --git a/terraform/modules/compute/main.tf b/terraform/modules/compute/main.tf index 80b7e74..826719e 100644 --- a/terraform/modules/compute/main.tf +++ b/terraform/modules/compute/main.tf @@ -17,6 +17,10 @@ locals { name = "APP_URL" value = "https://${var.domain_name}" }, + { + name = "APP_SERVER_MODE" + value = var.app_server_mode + }, { name = "REDIS_HOST" value = var.redis_endpoint diff --git a/terraform/modules/compute/variables.tf b/terraform/modules/compute/variables.tf index 3da3490..f523f4e 100644 --- a/terraform/modules/compute/variables.tf +++ b/terraform/modules/compute/variables.tf @@ -197,3 +197,9 @@ variable "enable_scheduler" { type = bool default = true } + +variable "app_server_mode" { + description = "Application server mode: 'php-fpm' or 'octane'" + type = string + default = "php-fpm" +} diff --git a/terraform/variables.tf b/terraform/variables.tf index 88a30af..cd47e60 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -102,6 +102,17 @@ variable "max_capacity" { default = 10 } +# Application Server Mode +variable "app_server_mode" { + description = "Application server mode: 'php-fpm' (default) or 'octane'. Octane provides better performance for Laravel applications." + type = string + default = "php-fpm" + validation { + condition = contains(["php-fpm", "octane"], var.app_server_mode) + error_message = "app_server_mode must be either 'php-fpm' or 'octane'" + } +} + # Queue Worker (processes background jobs) variable "queue_worker_cpu" { description = "CPU units for the queue worker container (256 = 0.25 vCPU, 512 = 0.5 vCPU, 1024 = 1 vCPU)" From 48a10ad7b966087fdd7c1446c18c2237733fc0dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 03:16:53 +0000 Subject: [PATCH 3/6] Add comprehensive documentation for switching between PHP-FPM and Octane Co-authored-by: leek <60204+leek@users.noreply.github.com> --- README.md | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/README.md b/README.md index 3fb88ca..e9c85de 100644 --- a/README.md +++ b/README.md @@ -256,6 +256,77 @@ healthcheck_alarm_emails = ["ops@example.com"] **Estimated cost**: ~$300-500/month +## Switching Between PHP-FPM and Octane + +You can switch between PHP-FPM and Laravel Octane at any time by updating your Terraform configuration: + +### Prerequisites for Octane + +Before switching to Octane, ensure your Laravel application: + +1. **Has Laravel Octane installed:** + ```bash + composer require laravel/octane + php artisan octane:install --server=swoole + ``` + +2. **Is Octane-compatible:** + - No reliance on global state or static variables + - Uses dependency injection properly + - Stateless service classes + - See [Laravel Octane documentation](https://laravel.com/docs/octane#introduction) for details + +### Switching to Octane + +1. Update your `.tfvars` file: + ```hcl + app_server_mode = "octane" + ``` + +2. Apply the Terraform changes: + ```bash + terraform apply -var-file="environments/production.tfvars" + ``` + +3. Deploy your updated Docker image (with Octane installed) and force a new ECS deployment: + ```bash + # The new tasks will automatically start with Octane + aws ecs update-service --cluster $CLUSTER --service $(terraform output -raw ecs_service_name) --force-new-deployment + ``` + +### Switching Back to PHP-FPM + +If you need to revert to PHP-FPM: + +1. Update your `.tfvars` file: + ```hcl + app_server_mode = "php-fpm" + ``` + +2. Apply the Terraform changes and redeploy: + ```bash + terraform apply -var-file="environments/production.tfvars" + aws ecs update-service --cluster $CLUSTER --service $(terraform output -raw ecs_service_name) --force-new-deployment + ``` + +### Testing Your Configuration + +Test Octane locally before deploying to production: + +```bash +# Build and run locally with Octane +docker build -f docker/Dockerfile -t myapp . +docker run -p 8080:80 \ + -e APP_ENV=local \ + -e CONTAINER_ROLE=web \ + -e APP_SERVER_MODE=octane \ + -e APP_KEY=base64:$(php artisan key:generate --show) \ + myapp + +# Test in browser +curl http://localhost:8080 +``` + ## Optional Features ### Enable Meilisearch (Search Engine) From 34ae9362678a40fb78f59ce6bd8b5b93029a0e2e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 03:20:05 +0000 Subject: [PATCH 4/6] Fix APP_KEY generation example in documentation Co-authored-by: leek <60204+leek@users.noreply.github.com> --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e9c85de..a5402a7 100644 --- a/README.md +++ b/README.md @@ -316,11 +316,20 @@ Test Octane locally before deploying to production: ```bash # Build and run locally with Octane docker build -f docker/Dockerfile -t myapp . + +# Generate an APP_KEY first (use an existing Laravel project or Docker) +# Option 1: From your Laravel project directory +APP_KEY=$(php artisan key:generate --show) + +# Option 2: Or generate inside a temporary container +APP_KEY=$(docker run --rm myapp php artisan key:generate --show) + +# Run with Octane docker run -p 8080:80 \ -e APP_ENV=local \ -e CONTAINER_ROLE=web \ -e APP_SERVER_MODE=octane \ - -e APP_KEY=base64:$(php artisan key:generate --show) \ + -e APP_KEY=$APP_KEY \ myapp # Test in browser From a4ab8a8d4ba0e86711c44827c62433a1731cb39e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 03:57:05 +0000 Subject: [PATCH 5/6] Add support for multiple Octane drivers (Swoole, RoadRunner, FrankenPHP) Co-authored-by: leek <60204+leek@users.noreply.github.com> --- README.md | 59 ++++++++++++---- docker/Dockerfile | 20 +++++- docker/README.md | 41 ++++++++++-- docker/entrypoint.sh | 67 +++++++++++++------ docker/supervisord-web-octane-frankenphp.conf | 28 ++++++++ docker/supervisord-web-octane-roadrunner.conf | 28 ++++++++ ...onf => supervisord-web-octane-swoole.conf} | 0 terraform/environments/example.tfvars | 10 ++- terraform/variables.tf | 6 +- 9 files changed, 211 insertions(+), 48 deletions(-) create mode 100644 docker/supervisord-web-octane-frankenphp.conf create mode 100644 docker/supervisord-web-octane-roadrunner.conf rename docker/{supervisord-web-octane.conf => supervisord-web-octane-swoole.conf} (100%) diff --git a/README.md b/README.md index a5402a7..a88986f 100644 --- a/README.md +++ b/README.md @@ -155,16 +155,27 @@ aws ecs update-service --cluster $CLUSTER --service $(terraform output -raw ecs_ ### Application Server Mode -Choose between **PHP-FPM** (default) or **Laravel Octane** for serving your application: +Choose between **PHP-FPM** (default) or **Laravel Octane** with your preferred driver: ```hcl # Traditional PHP-FPM with Nginx (default, most compatible) app_server_mode = "php-fpm" -# Laravel Octane with Swoole (better performance, requires Laravel 8+ with Octane package) -app_server_mode = "octane" +# Laravel Octane with Swoole (battle-tested, excellent performance) +app_server_mode = "octane-swoole" + +# Laravel Octane with RoadRunner (Go-based, great for long-running tasks) +app_server_mode = "octane-roadrunner" + +# Laravel Octane with FrankenPHP (modern, includes Early Hints support) +app_server_mode = "octane-frankenphp" ``` +**Octane Driver Comparison:** +- **Swoole**: Battle-tested, excellent performance, requires Swoole PHP extension +- **RoadRunner**: Go-based server, great for long-running tasks, excellent stability +- **FrankenPHP**: Modern PHP app server built on Caddy, supports Early Hints and modern HTTP features + **Laravel Octane Benefits:** - 2-5x better request throughput - Lower latency for API responses @@ -224,8 +235,8 @@ github_repo = "your-repo" app_db_password = "..." db_reporting_password = "..." -# Use Laravel Octane for better performance in production -app_server_mode = "octane" +# Use Laravel Octane for better performance in production (choose your preferred driver) +app_server_mode = "octane-swoole" # or "octane-roadrunner" or "octane-frankenphp" # Larger instance sizes container_cpu = 2048 @@ -258,7 +269,7 @@ healthcheck_alarm_emails = ["ops@example.com"] ## Switching Between PHP-FPM and Octane -You can switch between PHP-FPM and Laravel Octane at any time by updating your Terraform configuration: +You can switch between PHP-FPM and Laravel Octane (with any driver) at any time by updating your Terraform configuration: ### Prerequisites for Octane @@ -267,7 +278,11 @@ Before switching to Octane, ensure your Laravel application: 1. **Has Laravel Octane installed:** ```bash composer require laravel/octane - php artisan octane:install --server=swoole + + # Install your preferred server + php artisan octane:install --server=swoole # For Swoole + php artisan octane:install --server=roadrunner # For RoadRunner + php artisan octane:install --server=frankenphp # For FrankenPHP ``` 2. **Is Octane-compatible:** @@ -278,9 +293,13 @@ Before switching to Octane, ensure your Laravel application: ### Switching to Octane -1. Update your `.tfvars` file: +1. Update your `.tfvars` file with your preferred driver: ```hcl - app_server_mode = "octane" + app_server_mode = "octane-swoole" # Most battle-tested + # OR + app_server_mode = "octane-roadrunner" # Great for long-running tasks + # OR + app_server_mode = "octane-frankenphp" # Modern with Early Hints support ``` 2. Apply the Terraform changes: @@ -290,7 +309,7 @@ Before switching to Octane, ensure your Laravel application: 3. Deploy your updated Docker image (with Octane installed) and force a new ECS deployment: ```bash - # The new tasks will automatically start with Octane + # The new tasks will automatically start with your chosen Octane driver aws ecs update-service --cluster $CLUSTER --service $(terraform output -raw ecs_service_name) --force-new-deployment ``` @@ -324,11 +343,27 @@ APP_KEY=$(php artisan key:generate --show) # Option 2: Or generate inside a temporary container APP_KEY=$(docker run --rm myapp php artisan key:generate --show) -# Run with Octane +# Run with Octane Swoole +docker run -p 8080:80 \ + -e APP_ENV=local \ + -e CONTAINER_ROLE=web \ + -e APP_SERVER_MODE=octane-swoole \ + -e APP_KEY=$APP_KEY \ + myapp + +# Or run with Octane RoadRunner +docker run -p 8080:80 \ + -e APP_ENV=local \ + -e CONTAINER_ROLE=web \ + -e APP_SERVER_MODE=octane-roadrunner \ + -e APP_KEY=$APP_KEY \ + myapp + +# Or run with Octane FrankenPHP docker run -p 8080:80 \ -e APP_ENV=local \ -e CONTAINER_ROLE=web \ - -e APP_SERVER_MODE=octane \ + -e APP_SERVER_MODE=octane-frankenphp \ -e APP_KEY=$APP_KEY \ myapp diff --git a/docker/Dockerfile b/docker/Dockerfile index fa3ef64..fce9eba 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -53,7 +53,7 @@ RUN docker-php-ext-configure gd --with-freetype --with-jpeg \ zip \ pcntl \ sockets \ - # PECL - Install redis and swoole (for Laravel Octane) + # PECL - Install redis and swoole (for Laravel Octane with Swoole) && pecl install redis swoole \ && docker-php-ext-enable redis swoole \ # Cleanup after PHP extensions are built @@ -126,6 +126,16 @@ RUN CACHE_STORE=array php -d memory_limit=512M artisan event:cache --no-interact CACHE_STORE=array php -d memory_limit=512M artisan icons:cache --no-interaction --no-ansi && \ CACHE_STORE=array php -d memory_limit=512M artisan filament:cache-components --no-interaction --no-ansi +# Download RoadRunner binary for Laravel Octane +RUN curl -sSL https://github.com/roadrunner-server/roadrunner/releases/download/v2024.2.1/roadrunner-2024.2.1-linux-amd64.tar.gz \ + | tar -xz -C /usr/local/bin roadrunner && \ + chmod +x /usr/local/bin/roadrunner + +# Download FrankenPHP binary for Laravel Octane +RUN curl -sSL https://github.com/dunglas/frankenphp/releases/download/v1.3.5/frankenphp-linux-x86_64 \ + -o /usr/local/bin/frankenphp && \ + chmod +x /usr/local/bin/frankenphp + # ======================================== # Stage 3: Final application image # ======================================== @@ -137,13 +147,19 @@ WORKDIR /var/www/html # Copy built application from the builder stage COPY --from=builder --chown=www-data:www-data /var/www/html . +# Copy Octane binaries from builder stage +COPY --from=builder /usr/local/bin/roadrunner /usr/local/bin/roadrunner +COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp + # Copy configuration files COPY docker/.profile /root/.profile COPY --chown=www-data:www-data docker/nginx/ /etc/nginx COPY docker/php/php-fpm.conf /usr/local/etc/php-fpm.d/www.conf COPY docker/php/php.ini /usr/local/etc/php/conf.d/99-custom.ini COPY docker/supervisord-web.conf /etc/supervisor/conf.d/supervisord-web.conf -COPY docker/supervisord-web-octane.conf /etc/supervisor/conf.d/supervisord-web-octane.conf +COPY docker/supervisord-web-octane-swoole.conf /etc/supervisor/conf.d/supervisord-web-octane-swoole.conf +COPY docker/supervisord-web-octane-roadrunner.conf /etc/supervisor/conf.d/supervisord-web-octane-roadrunner.conf +COPY docker/supervisord-web-octane-frankenphp.conf /etc/supervisor/conf.d/supervisord-web-octane-frankenphp.conf COPY docker/supervisord-queue-worker.conf /etc/supervisor/conf.d/supervisord-queue-worker.conf COPY docker/supervisord-scheduler.conf /etc/supervisor/conf.d/supervisord-scheduler.conf COPY docker/entrypoint.sh /entrypoint.sh diff --git a/docker/README.md b/docker/README.md index 9150f98..85b3c2e 100644 --- a/docker/README.md +++ b/docker/README.md @@ -8,18 +8,29 @@ This Docker image supports multiple container roles via the `CONTAINER_ROLE` env ## Application Server Mode -The **web** container role supports two modes via the `APP_SERVER_MODE` environment variable: +The **web** container role supports multiple modes via the `APP_SERVER_MODE` environment variable: - **php-fpm** (default): Traditional PHP-FPM with Nginx as FastCGI proxy - Most compatible with all Laravel applications - Battle-tested and stable - Good for applications with moderate traffic -- **octane**: Laravel Octane with Swoole and Nginx as reverse proxy +- **octane-swoole**: Laravel Octane with Swoole and Nginx as reverse proxy - 2-5x better performance and throughput - - Lower latency and memory usage - - Requires Laravel 8+ with Octane package installed - - Application must be Octane-compatible (no global state) + - Battle-tested Octane driver with excellent performance + - Requires Swoole PHP extension (included in image) + +- **octane-roadrunner**: Laravel Octane with RoadRunner and Nginx as reverse proxy + - Go-based server with excellent stability + - Great for long-running tasks and memory management + - RoadRunner binary included in image + +- **octane-frankenphp**: Laravel Octane with FrankenPHP and Nginx as reverse proxy + - Modern PHP app server built on Caddy + - Supports Early Hints and modern HTTP features + - FrankenPHP binary included in image + +All Octane modes require Laravel 8+ with Octane package installed and an Octane-compatible application (no global state). ## Architecture @@ -48,11 +59,27 @@ docker run -p 8080:80 \ -e APP_KEY=base64:$(php artisan key:generate --show) \ laravel-local -# Run web server locally with Laravel Octane +# Run web server locally with Laravel Octane (Swoole) +docker run -p 8080:80 \ + -e APP_ENV=local \ + -e CONTAINER_ROLE=web \ + -e APP_SERVER_MODE=octane-swoole \ + -e APP_KEY=base64:$(php artisan key:generate --show) \ + laravel-local + +# Run web server locally with Laravel Octane (RoadRunner) +docker run -p 8080:80 \ + -e APP_ENV=local \ + -e CONTAINER_ROLE=web \ + -e APP_SERVER_MODE=octane-roadrunner \ + -e APP_KEY=base64:$(php artisan key:generate --show) \ + laravel-local + +# Run web server locally with Laravel Octane (FrankenPHP) docker run -p 8080:80 \ -e APP_ENV=local \ -e CONTAINER_ROLE=web \ - -e APP_SERVER_MODE=octane \ + -e APP_SERVER_MODE=octane-frankenphp \ -e APP_KEY=base64:$(php artisan key:generate --show) \ laravel-local diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 48ae01d..69d74f5 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -51,27 +51,52 @@ php artisan config:cache --no-interaction || true case "$CONTAINER_ROLE" in web) # Select web server config based on APP_SERVER_MODE - if [ "$APP_SERVER_MODE" = "octane" ]; then - echo "Starting web server with Laravel Octane..." - SUPERVISOR_CONF="/etc/supervisor/conf.d/supervisord-web-octane.conf" - # Swap nginx config to use Octane reverse proxy - if [ -f /etc/nginx/custom.d/laravel.conf ]; then - mv /etc/nginx/custom.d/laravel.conf /etc/nginx/custom.d/.laravel.conf.disabled - fi - if [ -f /etc/nginx/custom.d/.laravel-octane.conf ]; then - mv /etc/nginx/custom.d/.laravel-octane.conf /etc/nginx/custom.d/laravel-octane.conf - fi - else - echo "Starting web server with PHP-FPM..." - SUPERVISOR_CONF="/etc/supervisor/conf.d/supervisord-web.conf" - # Ensure PHP-FPM config is active - if [ -f /etc/nginx/custom.d/.laravel.conf.disabled ]; then - mv /etc/nginx/custom.d/.laravel.conf.disabled /etc/nginx/custom.d/laravel.conf - fi - if [ -f /etc/nginx/custom.d/laravel-octane.conf ]; then - mv /etc/nginx/custom.d/laravel-octane.conf /etc/nginx/custom.d/.laravel-octane.conf - fi - fi + case "$APP_SERVER_MODE" in + octane-swoole) + echo "Starting web server with Laravel Octane (Swoole)..." + SUPERVISOR_CONF="/etc/supervisor/conf.d/supervisord-web-octane-swoole.conf" + # Swap nginx config to use Octane reverse proxy + if [ -f /etc/nginx/custom.d/laravel.conf ]; then + mv /etc/nginx/custom.d/laravel.conf /etc/nginx/custom.d/.laravel.conf.disabled + fi + if [ -f /etc/nginx/custom.d/.laravel-octane.conf ]; then + mv /etc/nginx/custom.d/.laravel-octane.conf /etc/nginx/custom.d/laravel-octane.conf + fi + ;; + octane-roadrunner) + echo "Starting web server with Laravel Octane (RoadRunner)..." + SUPERVISOR_CONF="/etc/supervisor/conf.d/supervisord-web-octane-roadrunner.conf" + # Swap nginx config to use Octane reverse proxy + if [ -f /etc/nginx/custom.d/laravel.conf ]; then + mv /etc/nginx/custom.d/laravel.conf /etc/nginx/custom.d/.laravel.conf.disabled + fi + if [ -f /etc/nginx/custom.d/.laravel-octane.conf ]; then + mv /etc/nginx/custom.d/.laravel-octane.conf /etc/nginx/custom.d/laravel-octane.conf + fi + ;; + octane-frankenphp) + echo "Starting web server with Laravel Octane (FrankenPHP)..." + SUPERVISOR_CONF="/etc/supervisor/conf.d/supervisord-web-octane-frankenphp.conf" + # Swap nginx config to use Octane reverse proxy + if [ -f /etc/nginx/custom.d/laravel.conf ]; then + mv /etc/nginx/custom.d/laravel.conf /etc/nginx/custom.d/.laravel.conf.disabled + fi + if [ -f /etc/nginx/custom.d/.laravel-octane.conf ]; then + mv /etc/nginx/custom.d/.laravel-octane.conf /etc/nginx/custom.d/laravel-octane.conf + fi + ;; + php-fpm|*) + echo "Starting web server with PHP-FPM..." + SUPERVISOR_CONF="/etc/supervisor/conf.d/supervisord-web.conf" + # Ensure PHP-FPM config is active + if [ -f /etc/nginx/custom.d/.laravel.conf.disabled ]; then + mv /etc/nginx/custom.d/.laravel.conf.disabled /etc/nginx/custom.d/laravel.conf + fi + if [ -f /etc/nginx/custom.d/laravel-octane.conf ]; then + mv /etc/nginx/custom.d/laravel-octane.conf /etc/nginx/custom.d/.laravel-octane.conf + fi + ;; + esac ;; queue-worker) echo "Starting queue worker..." diff --git a/docker/supervisord-web-octane-frankenphp.conf b/docker/supervisord-web-octane-frankenphp.conf new file mode 100644 index 0000000..ed2d060 --- /dev/null +++ b/docker/supervisord-web-octane-frankenphp.conf @@ -0,0 +1,28 @@ +[supervisord] +nodaemon=true +user=root +logfile=/var/log/supervisor/supervisord.log +pidfile=/var/run/supervisord.pid + +[program:nginx] +command=nginx -g "daemon off;" +priority=20 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=true +startretries=3 +startsecs=5 + +[program:octane] +command=php /var/www/html/artisan octane:start --server=frankenphp --host=127.0.0.1 --port=8000 --workers=auto --max-requests=500 +priority=10 +user=www-data +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=true +startretries=3 +startsecs=5 diff --git a/docker/supervisord-web-octane-roadrunner.conf b/docker/supervisord-web-octane-roadrunner.conf new file mode 100644 index 0000000..e8ef919 --- /dev/null +++ b/docker/supervisord-web-octane-roadrunner.conf @@ -0,0 +1,28 @@ +[supervisord] +nodaemon=true +user=root +logfile=/var/log/supervisor/supervisord.log +pidfile=/var/run/supervisord.pid + +[program:nginx] +command=nginx -g "daemon off;" +priority=20 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=true +startretries=3 +startsecs=5 + +[program:octane] +command=php /var/www/html/artisan octane:start --server=roadrunner --host=127.0.0.1 --port=8000 --workers=auto --max-requests=500 +priority=10 +user=www-data +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=true +startretries=3 +startsecs=5 diff --git a/docker/supervisord-web-octane.conf b/docker/supervisord-web-octane-swoole.conf similarity index 100% rename from docker/supervisord-web-octane.conf rename to docker/supervisord-web-octane-swoole.conf diff --git a/terraform/environments/example.tfvars b/terraform/environments/example.tfvars index 6c79611..e008376 100644 --- a/terraform/environments/example.tfvars +++ b/terraform/environments/example.tfvars @@ -64,10 +64,14 @@ meilisearch_master_key = "CHANGE_ME_MEILISEARCH_KEY" # CPU 2048: 4096 to 16384 (1GB increments) # CPU 4096: 8192 to 30720 (1GB increments) -# Application server mode: "php-fpm" (default) or "octane" +# Application server mode: "php-fpm" (default), "octane-swoole", "octane-roadrunner", or "octane-frankenphp" # Laravel Octane provides significantly better performance by keeping the application -# in memory and serving requests through Swoole. Requires Laravel 8+ with Octane installed. -# Note: When using Octane, ensure your application is Octane-compatible (no global state). +# in memory and serving requests through various high-performance servers. +# - php-fpm: Traditional PHP-FPM (most compatible) +# - octane-swoole: Octane with Swoole (battle-tested, excellent performance) +# - octane-roadrunner: Octane with RoadRunner (Go-based, great for long-running tasks) +# - octane-frankenphp: Octane with FrankenPHP (modern, includes Early Hints support) +# Requires Laravel 8+ with Octane installed and an Octane-compatible application (no global state). app_server_mode = "php-fpm" # Web Service (handles HTTP requests) diff --git a/terraform/variables.tf b/terraform/variables.tf index cd47e60..b679b5b 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -104,12 +104,12 @@ variable "max_capacity" { # Application Server Mode variable "app_server_mode" { - description = "Application server mode: 'php-fpm' (default) or 'octane'. Octane provides better performance for Laravel applications." + description = "Application server mode: 'php-fpm' (default), 'octane-swoole', 'octane-frankenphp', or 'octane-roadrunner'. Octane modes provide better performance for Laravel applications." type = string default = "php-fpm" validation { - condition = contains(["php-fpm", "octane"], var.app_server_mode) - error_message = "app_server_mode must be either 'php-fpm' or 'octane'" + condition = contains(["php-fpm", "octane-swoole", "octane-frankenphp", "octane-roadrunner"], var.app_server_mode) + error_message = "app_server_mode must be one of: 'php-fpm', 'octane-swoole', 'octane-frankenphp', or 'octane-roadrunner'" } } From 3ac0a75cce1427eaa99f8ebf6390cfb14378ad9b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Oct 2025 09:21:24 +0000 Subject: [PATCH 6/6] Fix documentation inconsistencies for app_server_mode variable Co-authored-by: leek <60204+leek@users.noreply.github.com> --- README.md | 2 +- terraform/modules/compute/variables.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a88986f..1333f73 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,7 @@ app_db_password = "..." db_reporting_password = "..." # Application server mode -app_server_mode = "php-fpm" # or "octane" for better performance +app_server_mode = "php-fpm" # or "octane-swoole", "octane-roadrunner", or "octane-frankenphp" for better performance # Small instance sizes container_cpu = 512 diff --git a/terraform/modules/compute/variables.tf b/terraform/modules/compute/variables.tf index f523f4e..eb26a84 100644 --- a/terraform/modules/compute/variables.tf +++ b/terraform/modules/compute/variables.tf @@ -199,7 +199,7 @@ variable "enable_scheduler" { } variable "app_server_mode" { - description = "Application server mode: 'php-fpm' or 'octane'" + description = "Application server mode. Valid values: 'php-fpm', 'octane-swoole', 'octane-roadrunner', and 'octane-frankenphp'." type = string default = "php-fpm" }