Managing servers for sporadic traffic is wasteful. Serverless architecture with AWS Lambda eliminates servers entirely—your Laravel app scales automatically and you pay only for execution time. At ZIRA Software, serverless Laravel powers our event-driven APIs, processing millions of requests monthly while paying cents.
Why Serverless Laravel?
Traditional hosting problems:
- Pay for idle servers
- Manual scaling
- Server maintenance
- Traffic spike handling
- Fixed capacity
Serverless benefits:
- Zero server management
- Automatic scaling (0 to infinity)
- Pay per request ($0.20/million)
- No idle costs
- Built-in high availability
- Cold starts (trade-off)
Best for:
- APIs with variable traffic
- Scheduled tasks
- Event-driven processing
- Microservices
- Low-traffic applications
Not ideal for:
- WebSocket connections
- Long-running processes (>15 min)
- High-frequency, low-latency apps
- Traditional web apps with sessions
Bref Framework
Bref makes Laravel on Lambda easy:
composer require bref/bref bref/laravel-bridge
Install Bref CLI:
npm install -g serverless
Configuration
serverless.yml:
service: laravel-app
provider:
name: aws
region: us-east-1
runtime: provided.al2
environment:
APP_ENV: production
APP_KEY: ${ssm:/laravel-app/APP_KEY}
DB_CONNECTION: mysql
DB_HOST: ${ssm:/laravel-app/DB_HOST}
DB_DATABASE: ${ssm:/laravel-app/DB_DATABASE}
DB_USERNAME: ${ssm:/laravel-app/DB_USERNAME}
DB_PASSWORD: ${ssm:/laravel-app/DB_PASSWORD}
functions:
web:
handler: public/index.php
timeout: 28
layers:
- ${bref:layer.php-80-fpm}
events:
- httpApi: '*'
artisan:
handler: artisan
timeout: 120
layers:
- ${bref:layer.php-80}
- ${bref:layer.console}
plugins:
- ./vendor/bref/bref
Deploy:
serverless deploy
Database Configuration
Use RDS or Aurora Serverless:
resources:
Resources:
Database:
Type: AWS::RDS::DBCluster
Properties:
Engine: aurora-mysql
EngineMode: serverless
ScalingConfiguration:
MinCapacity: 1
MaxCapacity: 16
AutoPause: true
SecondsUntilAutoPause: 300
Connection from Lambda:
// config/database.php
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST'),
'database' => env('DB_DATABASE'),
'username' => env('DB_USERNAME'),
'password' => env('DB_PASSWORD'),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
],
File Storage
Use S3 for files:
composer require league/flysystem-aws-s3-v3
config/filesystems.php:
'disks' => [
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
],
],
'default' => env('FILESYSTEM_DRIVER', 's3'),
Use S3:
Storage::disk('s3')->put('file.txt', 'Contents');
$url = Storage::disk('s3')->url('file.txt');
Caching
Use DynamoDB or ElastiCache:
'stores' => [
'dynamodb' => [
'driver' => 'dynamodb',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
'endpoint' => env('DYNAMODB_ENDPOINT'),
],
],
Queues
Use SQS:
composer require aws/aws-sdk-php
config/queue.php:
'connections' => [
'sqs' => [
'driver' => 'sqs',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
'queue' => env('SQS_QUEUE', 'default'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
],
Worker function:
functions:
worker:
handler: worker.php
timeout: 60
layers:
- ${bref:layer.php-80}
events:
- sqs:
arn: arn:aws:sqs:us-east-1:ACCOUNT:queue-name
batchSize: 1
worker.php:
<?php
use Illuminate\Contracts\Queue\Job;
require __DIR__.'/vendor/autoload.php';
$app = require_once __DIR__.'/bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
$kernel->bootstrap();
return function ($event) {
foreach ($event['Records'] as $record) {
$job = json_decode($record['body'], true);
// Process job
$kernel->call('queue:work', [
'--once' => true,
]);
}
};
Scheduled Tasks
serverless.yml:
functions:
schedule:
handler: artisan
timeout: 120
layers:
- ${bref:layer.php-80}
- ${bref:layer.console}
events:
- schedule:
rate: cron(0 0 * * ? *) # Daily at midnight
input: '"schedule:run"'
Environment Variables
Store in SSM Parameter Store:
aws ssm put-parameter \
--name /laravel-app/APP_KEY \
--value "base64:..." \
--type SecureString
Reference in serverless.yml:
environment:
APP_KEY: ${ssm:/laravel-app/APP_KEY}
Cold Starts
Problem: First request after idle is slow (1-3 seconds)
Solutions:
- Keep warm:
functions:
web:
handler: public/index.php
events:
- httpApi: '*'
- schedule:
rate: rate(5 minutes)
enabled: true
input: '{"warming": true}'
- Provisioned concurrency:
functions:
web:
provisionedConcurrency: 1 # Always 1 instance ready
- Optimize bootstrap:
// Optimize autoloader
composer install --optimize-autoloader --no-dev
// Cache config
php artisan config:cache
php artisan route:cache
php artisan view:cache
Monitoring
CloudWatch Logs:
serverless logs -f web --tail
X-Ray tracing:
provider:
tracing:
lambda: true
Custom metrics:
use Aws\CloudWatch\CloudWatchClient;
$client = new CloudWatchClient([
'region' => 'us-east-1',
'version' => 'latest'
]);
$client->putMetricData([
'Namespace' => 'Laravel/API',
'MetricData' => [
[
'MetricName' => 'OrdersProcessed',
'Value' => 1,
'Unit' => 'Count',
],
],
]);
Cost Optimization
Pricing (us-east-1):
- Requests: $0.20 per 1M requests
- Duration: $0.0000166667 per GB-second
- Free tier: 1M requests + 400,000 GB-seconds/month
Example costs:
- 1M requests at 512MB, 500ms each: ~$10/month
- 10M requests: ~$95/month
Optimize:
- Reduce memory - Lower costs proportionally
- Reduce duration - Optimize code
- Use caching - Fewer invocations
- Batch operations - Process multiple items
Deployment Pipeline
GitHub Actions:
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.0'
- name: Install dependencies
run: composer install --no-dev --optimize-autoloader
- name: Cache config
run: |
php artisan config:cache
php artisan route:cache
php artisan view:cache
- name: Deploy
run: serverless deploy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
Best Practices
- Cache everything - Config, routes, views
- Optimize autoloader - Use --optimize-autoloader
- Use CDN - CloudFront for assets
- Monitor cold starts - Keep functions warm if needed
- Set timeouts appropriately - Max 15 minutes
- Use layers - Share dependencies
- Handle errors gracefully - Retry logic
Limitations
Cannot use:
- WebSockets
- Long-running processes (>15 min)
- Write to filesystem (except /tmp)
- Background workers (use SQS)
Workarounds:
- WebSockets → API Gateway WebSocket API
- Long processes → Step Functions
- File writes → S3
- Workers → SQS + Lambda
When to Use Serverless
✅ Good fit:
- APIs with variable traffic
- Event-driven processing
- Scheduled tasks
- Low-traffic applications
- Microservices
- Cost-sensitive projects
❌ Not ideal:
- Real-time WebSocket apps
- High-frequency, low-latency
- Traditional web apps with sessions
- Predictable, constant traffic
- Complex state management
Conclusion
Serverless Laravel with AWS Lambda eliminates server management while providing infinite scaling. While cold starts and stateless architecture require adjustments, the operational simplicity and cost savings are compelling. At ZIRA Software, serverless powers our event-driven services, providing reliability and cost efficiency.
Start with API endpoints or scheduled tasks. Migrate as you understand the constraints. Your infrastructure bill will thank you.
Considering serverless for your Laravel application? Contact ZIRA Software to discuss serverless architecture, cost optimization, and cloud-native application design.