Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,37 @@ aws ecs update-service --cluster $CLUSTER --service $(terraform output -raw ecs_

## Configuration Guide

### Application Server Mode

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 (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
- 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):
Expand All @@ -167,6 +198,9 @@ github_repo = "your-repo"
app_db_password = "..."
db_reporting_password = "..."

# Application server mode
app_server_mode = "php-fpm" # or "octane-swoole", "octane-roadrunner", or "octane-frankenphp" for better performance

# Small instance sizes
container_cpu = 512
container_memory = 1024
Expand Down Expand Up @@ -201,6 +235,9 @@ github_repo = "your-repo"
app_db_password = "..."
db_reporting_password = "..."

# 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
container_memory = 4096
Expand Down Expand Up @@ -230,6 +267,110 @@ 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 (with any driver) 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

# 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:**
- 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 with your preferred driver:
```hcl
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:
```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 your chosen Octane driver
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 .

# 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 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-frankenphp \
-e APP_KEY=$APP_KEY \
myapp

# Test in browser
curl http://localhost:8080
```

## Optional Features

### Enable Meilisearch (Search Engine)
Expand Down
25 changes: 22 additions & 3 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 with Swoole)
&& 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/*
Expand Down Expand Up @@ -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

Comment on lines +129 to +133
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add pipefail and consider checksum verification for binary downloads.

The RoadRunner download uses a pipe without the pipefail option, which could mask download failures. Additionally, downloading binaries without checksum verification poses a security risk.

Apply this diff to add pipefail and improve error handling:

 # 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 && \
+RUN set -o pipefail && \
+    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

Optional enhancement: Consider verifying checksums for both binaries to ensure integrity and security. You can obtain checksums from the respective GitHub release pages.

Based on static analysis hints.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# 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 RoadRunner binary for Laravel Octane
RUN set -o pipefail && \
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
🧰 Tools
🪛 Hadolint (2.14.0)

[warning] 130-130: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check

(DL4006)

🤖 Prompt for AI Agents
In docker/Dockerfile around lines 129 to 133, the RoadRunner download uses a
piped curl->tar without pipefail and no integrity check; update the RUN step to
enable bash pipefail (e.g., set -o pipefail), use curl with fail flags (curl
-fSL) and fail fast so download errors surface, write the archive to a temporary
file, verify the archive (preferably by checking a published checksum or
signature) before extracting to /usr/local/bin, and ensure proper cleanup and
executable permission changes after extraction.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot In docker/Dockerfile around lines 129 to 133, the RoadRunner download uses a
piped curl->tar without pipefail and no integrity check; update the RUN step to
enable bash pipefail (e.g., set -o pipefail), use curl with fail flags (curl
-fSL) and fail fast so download errors surface, write the archive to a temporary
file, verify the archive (preferably by checking a published checksum or
signature) before extracting to /usr/local/bin, and ensure proper cleanup and
executable permission changes after extraction.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

# 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
Comment on lines +130 to +137
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify that binary versions are current and consider platform compatibility.

The Dockerfile downloads specific versions of RoadRunner (v2024.2.1) and FrankenPHP (v1.3.5). Additionally, the binaries are hardcoded for x86_64 architecture, which won't work on ARM-based systems (e.g., Apple Silicon, AWS Graviton).

Run the following script to check for the latest versions:


🏁 Script executed:

#!/bin/bash
# Description: Check latest versions of RoadRunner and FrankenPHP

echo "=== Latest RoadRunner Release ==="
gh api repos/roadrunner-server/roadrunner/releases/latest --jq '.tag_name, .published_at'

echo ""
echo "=== Latest FrankenPHP Release ==="
gh api repos/dunglas/frankenphp/releases/latest --jq '.tag_name, .published_at'

echo ""
echo "=== Current versions in Dockerfile ==="
rg -n "roadrunner.*v[0-9]" docker/Dockerfile
rg -n "frankenphp.*v[0-9]" docker/Dockerfile

Length of output: 283



---

🏁 Script executed:

```shell
#!/bin/bash
# Description: Verify versions and architecture hardcoding in Dockerfile

echo "=== Dockerfile lines 130-137 ==="
sed -n '130,137p' docker/Dockerfile

echo ""
echo "=== Verify x86_64 hardcoding ==="
rg -n "x86_64|amd64|architecture" docker/Dockerfile -A2 -B2

Length of output: 1049






🌐 Web query:

RoadRunner GitHub releases ARM64 Linux support

💡 Result:

Yes — official RoadRunner releases include Linux ARM64 binaries. The roadrunner v2025.1.2 release (published 2025-06-14) includes a linux-arm64 asset, and earlier releases added linux/arm64 Docker support. (github.com)

Citations:


🌐 Web query:

FrankenPHP GitHub releases ARM64 aarch64 builds

💡 Result:

Yes — FrankenPHP GitHub releases include ARM64 / aarch64 builds (e.g., frankenphp-linux-aarch64 and frankenphp-*.aarch64.rpm in the release assets). [1][2]

Sources:

  • FrankenPHP releases (shows aarch64 assets, e.g., v1.9.1). [1]
  • FrankenPHP GitHub README (mentions static binaries for Linux/macOS). [2]

Update binary versions and add platform-agnostic architecture detection.

The Dockerfile downloads outdated binaries: RoadRunner v2024.2.1 (latest is v2025.1.4, released 2025-10-02) and FrankenPHP v1.3.5 (latest is v1.9.1, released 2025-08-29). Additionally, both binaries are hardcoded for x86_64 architecture, which will fail on ARM-based systems (e.g., Apple Silicon, AWS Graviton) even though both projects publish ARM64 binaries.

Lines 130–137 require:

  • Update RoadRunner to v2025.1.4 or later
  • Update FrankenPHP to v1.9.1 or later
  • Use dynamic architecture detection (e.g., $(uname -m) or Docker buildx multiplatform) instead of hardcoded linux-amd64 and linux-x86_64
🧰 Tools
🪛 Hadolint (2.14.0)

[warning] 130-130: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check

(DL4006)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Update RoadRunner to v2025.1.4 or later
Update FrankenPHP to v1.9.1 or later
Use dynamic architecture detection (e.g., $(uname -m) or Docker buildx multiplatform) instead of hardcoded linux-amd64 and linux-x86_64

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!


# ========================================
# Stage 3: Final application image
# ========================================
Expand All @@ -137,23 +147,32 @@ 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-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

# 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 \
Expand Down
55 changes: 53 additions & 2 deletions docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,36 @@

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 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-swoole**: Laravel Octane with Swoole and Nginx as reverse proxy
- 2-5x better performance and throughput
- 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

The ECS deployment consists of three separate services:
Expand All @@ -25,10 +51,35 @@ 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 (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-frankenphp \
-e APP_KEY=base64:$(php artisan key:generate --show) \
laravel-local

Expand Down
52 changes: 50 additions & 2 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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..."
Expand Down Expand Up @@ -47,8 +50,53 @@ 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
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..."
Expand Down
Loading