Development
Jun 12, 2025
12 min read
12 views

Scaling Laravel on a Budget: Real-World AWS Config for <$100/mo

A hands-on guide for developers and founders who want to scale their Laravel MVPs cost-effectively. Learn how to leverage AWS free tier, serverless infrastructure, and AI-assisted tools to build production-ready applications without breaking the bank.

Scaling Laravel on a Budget: Real-World AWS Config for <$100/mo

Share this post

Building an MVP doesn't have to drain your budget or require complex infrastructure from day one. I've been experimenting with various AWS configurations for Laravel projects, and here are some patterns I've found useful for getting ideas to market quickly and cost-effectively. These are starting points for demonstration - every project has unique needs that will require adjustments.

Remember: all code is throwaway code. The goal is to validate your idea quickly and cheaply. If you get traction, you'll have much better problems to solve!

The MVP Mindset: Start Small, Learn Fast

There's no one-size-fits-all architecture, but there are patterns that work well for getting ideas off the ground. The key is balancing cost, simplicity, and the ability to iterate quickly based on user feedback.

Smart Starting Points

  • • Begin with free tiers and expand as needed
  • • Use serverless for variable or unpredictable workloads
  • • Build simple tools instead of subscribing to enterprise solutions
  • • Leverage AI to prototype and iterate quickly

Common Pitfalls

  • • Over-engineering before validating the idea
  • • Paying for features you don't actually need
  • • Optimizing prematurely instead of learning from users
  • • Building complex systems when simple would work

AWS Free Tier: A Great Foundation

The AWS free tier gives you plenty of room to experiment and validate ideas. Here are some configurations I've found useful for Laravel projects:

Basic EC2 Setup

Example Free Tier Configuration

# t2.micro instance (750 hours/month free)
# Ubuntu 22.04 LTS
# 1 vCPU, 1 GB RAM, EBS-optimized

# Basic setup script - customize for your needs
#!/bin/bash
sudo apt update && sudo apt upgrade -y

# Install PHP 8.3 with common extensions
sudo add-apt-repository ppa:ondrej/php -y
sudo apt install php8.3 php8.3-fpm php8.3-mysql php8.3-redis \
  php8.3-xml php8.3-curl php8.3-zip php8.3-mbstring \
  php8.3-gd php8.3-intl php8.3-opcache -y

# Basic OPcache configuration
echo "opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=2
opcache.fast_shutdown=1" >> /etc/php/8.3/fpm/conf.d/10-opcache.ini

# Install and configure Nginx
sudo apt install nginx -y
# Add your custom Nginx config here

Database with RDS Free Tier

For MVPs, the RDS free tier often provides a good balance of simplicity and reliability:

# RDS Free Tier example
# db.t3.micro (750 hours/month)
# 20 GB SSD storage
# Automated backups for 7 days

# Example Laravel .env configuration
DB_CONNECTION=mysql
DB_HOST=your-rds-endpoint.region.rds.amazonaws.com
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel_user
DB_PASSWORD=secure_password

# Basic connection pooling
DB_POOL_MIN=2
DB_POOL_MAX=10

File Storage with S3

S3 with CloudFront can handle file uploads and static assets efficiently from day one:

# S3 Free Tier: 5GB storage, 20,000 GET requests
# CloudFront Free Tier: 1TB data transfer out

# Example Laravel filesystem config
's3' => [
    'driver' => 's3',
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    'bucket' => env('AWS_BUCKET'),
    'url' => env('AWS_URL'),
    'endpoint' => env('AWS_ENDPOINT'),
    'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
    'throw' => false,
    'options' => [
        'CacheControl' => 'max-age=31536000',
        'Metadata' => [
            'laravel-app' => config('app.name'),
        ],
    ],
],

Simple Infrastructure Patterns

In-Memory Caching

For small MVPs, running Redis on your EC2 instance can be simpler than managing separate services:

# Install Redis on EC2
sudo apt install redis-server -y

# Basic Redis optimization for limited memory
sudo nano /etc/redis/redis.conf

# Example optimizations - adjust for your needs:
maxmemory 256mb
maxmemory-policy allkeys-lru
save 900 1
save 300 10
save 60 10000

# Laravel Redis config
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
CACHE_DRIVER=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis

Preparing for Growth

💡 Pro Tip:

Start simple, but design for easy expansion. Using an Application Load Balancer from the beginning makes it trivial to add more instances later when (if!) you need them.

Budget-Friendly CI/CD

GitHub Actions Example

GitHub Actions provides plenty of free minutes for small projects, and 2,000 minutes/month for private repos:

# .github/workflows/deploy.yml
# Basic deployment workflow - customize for your needs
name: Deploy to AWS

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: 8.3
        extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv
        coverage: none
        
    - name: Install Composer dependencies
      run: composer install --no-dev --optimize-autoloader
      
    - name: Create deployment artifact
      env:
        GITHUB_SHA: ${{ github.sha }}
      run: tar -czf "${GITHUB_SHA}".tar.gz --exclude=*.git --exclude=node_modules *
      
    - name: Store artifact for distribution
      uses: actions/upload-artifact@v3
      with:
        name: app-build
        path: ${{ github.sha }}.tar.gz
        
    - name: Deploy to AWS
      uses: appleboy/ssh-action@v0.1.5
      with:
        host: ${{ secrets.AWS_HOST }}
        username: ubuntu
        key: ${{ secrets.AWS_PRIVATE_KEY }}
        envs: GITHUB_SHA
        script: |
          cd /var/www/html
          wget https://github.com/${{ github.repository }}/releases/download/latest/${GITHUB_SHA}.tar.gz
          tar -xzf ${GITHUB_SHA}.tar.gz
          php artisan migrate --force
          php artisan config:cache
          php artisan route:cache
          php artisan view:cache
          sudo systemctl reload php8.3-fpm
          sudo systemctl reload nginx

Simple Deployment Script

Sometimes a basic deployment script is all you need for MVP validation:

#!/bin/bash
# deploy.sh - Simple deployment example

REPO_URL="git@github.com:youruser/yourrepo.git"
DEPLOY_PATH="/var/www/html"
RELEASES_PATH="$DEPLOY_PATH/releases"
CURRENT_PATH="$DEPLOY_PATH/current"
RELEASE_NAME=$(date +%Y%m%d%H%M%S)
RELEASE_PATH="$RELEASES_PATH/$RELEASE_NAME"

# Create release directory
mkdir -p "$RELEASE_PATH"

# Clone repository
git clone --depth 1 "$REPO_URL" "$RELEASE_PATH"

# Install dependencies
cd "$RELEASE_PATH"
composer install --no-dev --optimize-autoloader

# Link storage and .env
ln -nfs "$DEPLOY_PATH/storage" "$RELEASE_PATH/storage"
ln -nfs "$DEPLOY_PATH/.env" "$RELEASE_PATH/.env"

# Run Laravel commands
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache

# Switch to new release
ln -nfs "$RELEASE_PATH" "$CURRENT_PATH"

# Reload services
sudo systemctl reload php8.3-fpm
sudo systemctl reload nginx

# Keep only last 5 releases
cd "$RELEASES_PATH" && ls -t | tail -n +6 | xargs -d \n rm -rf --

echo "Deployment completed: $RELEASE_NAME"

AI-Assisted Development and Monitoring

Building Simple Monitoring Tools

Instead of expensive monitoring solutions, you can quickly build focused tools with AI assistance:

🤖 Example AI Prompt:

"Create a Laravel command that checks basic server metrics and sends alerts when things look unusual. Keep it simple - just CPU, memory, and disk usage for now."

# Example monitoring command - starting point only
class MonitorBasics extends Command
{
    protected $signature = 'monitor:basics';
    
    public function handle()
    {
        $metrics = [
            'cpu_load' => $this->getCpuLoad(),
            'memory_usage' => $this->getMemoryUsage(),
            'disk_usage' => $this->getDiskUsage(),
        ];
        
        $this->checkSimpleThresholds($metrics);
        $this->logMetrics($metrics);
    }
    
    private function getCpuLoad(): float
    {
        $load = sys_getloadavg();
        return $load[0]; // 1-minute load average
    }
    
    private function getMemoryUsage(): array
    {
        $free = shell_exec('free -m');
        preg_match_all('/\s+(\d+)/', $free, $matches);
        
        return [
            'total' => (int) $matches[1][0],
            'used' => (int) $matches[1][1],
            'percentage' => round(($matches[1][1] / $matches[1][0]) * 100, 2)
        ];
    }
}

Quick Cost Optimization

AI can help you build simple scripts to keep an eye on AWS costs:

# Example cost monitoring script
#!/bin/bash
# Basic cost awareness script

# Find unused EBS volumes
aws ec2 describe-volumes --filters Name=status,Values=available \
  --query 'Volumes[*].[VolumeId,Size,CreateTime]' --output table

# Check for idle instances
aws cloudwatch get-metric-statistics --namespace AWS/EC2 \
  --metric-name CPUUtilization --dimensions Name=InstanceId,Value=i-1234567890abcdef0 \
  --statistics Average --start-time $(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) --period 86400

# Clean up old files (customize retention as needed)
aws s3api list-objects-v2 --bucket your-bucket \
  --query "Contents[?LastModified<'$(date -d '30 days ago' --iso-8601)'].[Key]" \
  --output text | xargs -I {} aws s3 rm s3://your-bucket/{}

Serverless for Variable Workloads

Lambda for Background Tasks

For tasks that run sporadically, Lambda can be more cost-effective than always-on infrastructure:

# Example Laravel Queue Worker as Lambda Function
# Using Bref for PHP on Lambda

composer require bref/bref bref/laravel-bridge

# serverless.yml example
service: laravel-queue-worker

provider:
  name: aws
  runtime: provided.al2
  region: us-east-1

functions:
  queue:
    handler: worker.php
    timeout: 120
    events:
      - sqs:
          arn: !GetAtt Queue.Arn
          batchSize: 1

resources:
  Resources:
    Queue:
      Type: AWS::SQS::Queue
      Properties:
        QueueName: laravel-queue
        VisibilityTimeoutSeconds: 120

Simple Auto-Scaling

Setting up basic auto-scaling gives you flexibility to handle traffic spikes:

# Example Auto Scaling Group configuration
{
  "AutoScalingGroupName": "laravel-asg",
  "MinSize": 1,
  "MaxSize": 3,
  "DesiredCapacity": 1,
  "DefaultCooldown": 300,
  "HealthCheckType": "ELB",
  "HealthCheckGracePeriod": 300,
  "LaunchTemplate": {
    "LaunchTemplateId": "lt-12345678",
    "Version": "$Latest"
  },
  "TargetGroupARNs": ["arn:aws:elasticloadbalancing:..."],
  "Tags": [
    {
      "Key": "Name",
      "Value": "laravel-instance",
      "PropagateAtLaunch": true
    }
  ]
}

Free and Low-Cost Tool Combinations

Free Tier Favorites

Sentry (Free): Error tracking for small projects
SENTRY_LARAVEL_DSN=your-dsn-here
SENTRY_TRACES_SAMPLE_RATE=0.1
Uptime Robot: Basic uptime monitoring
Cloudflare: Free CDN and basic DDoS protection
Let's Encrypt: Free SSL certificates

Quick AI-Built Alternatives

Simple Analytics: Basic visitor tracking
A/B Testing: Laravel + Alpine.js experiments
Basic CRM: Just what you need, nothing more
Performance Dashboard: Custom metrics for your app

Example Cost Breakdown

Sample Monthly Costs for a Growing MVP

EC2 t3.small instances $30-40
RDS database $15-25
Load balancer $15-20
S3 + CloudFront $5-15
Miscellaneous $5-10
Typical Range $70-110/month

⚠️ Remember:

These are example ranges based on typical usage patterns. Your actual costs will vary significantly based on traffic, data storage, and feature requirements. Start small and monitor closely!

Optimization Patterns

Database Query Monitoring

AI can help you spot performance issues before they become problems:

# Example query monitoring for development
composer require laravel/telescope --dev

# Simple query analyzer
class BasicQueryAnalyzer extends Command
{
    public function handle()
    {
        DB::listen(function ($query) {
            if ($query->time > 100) { // Queries over 100ms
                Log::warning('Slow query detected', [
                    'sql' => $query->sql,
                    'time' => $query->time,
                    'bindings' => $query->bindings
                ]);
            }
        });
    }
}

Simple Caching Strategy

A basic caching approach can handle significant traffic growth:

# Example multi-layer caching
class SimpleCacheService
{
    public function remember($key, $ttl, $callback)
    {
        // Try in-memory first (if available)
        if (function_exists('apcu_fetch')) {
            $value = apcu_fetch($key);
            if ($value !== false) return $value;
        }
        
        // Try Redis/database cache
        $value = Cache::get($key);
        if ($value !== null) {
            if (function_exists('apcu_store')) {
                apcu_store($key, $value, 300); // 5 min local cache
            }
            return $value;
        }
        
        // Generate new value
        $value = $callback();
        Cache::put($key, $value, $ttl);
        
        if (function_exists('apcu_store')) {
            apcu_store($key, $value, 300);
        }
        
        return $value;
    }
}

Security Basics

Essential AWS Security

Basic security doesn't require expensive tools - just good practices:

# Example Security Group configuration
{
  "GroupName": "laravel-web-sg",
  "Description": "Security group for Laravel web servers",
  "VpcId": "vpc-12345678",
  "SecurityGroupRules": [
    {
      "IpPermissions": [
        {
          "IpProtocol": "tcp",
          "FromPort": 80,
          "ToPort": 80,
          "IpRanges": [{"CidrIp": "0.0.0.0/0"}]
        },
        {
          "IpProtocol": "tcp",
          "FromPort": 443,
          "ToPort": 443,
          "IpRanges": [{"CidrIp": "0.0.0.0/0"}]
        },
        {
          "IpProtocol": "tcp",
          "FromPort": 22,
          "ToPort": 22,
          "IpRanges": [{"CidrIp": "YOUR_IP/32"}]
        }
      ]
    }
  ]
}

Automated Security Checks

# Example GitHub Actions security scan
- name: Basic Security Scan
  run: |
    composer require --dev phpstan/phpstan
    composer require --dev larastan/larastan
    ./vendor/bin/phpstan analyse --memory-limit=2G
    
    # Check for known vulnerabilities
    composer audit
    
    # Add custom security checks here
    php artisan security:check

When to Consider Scaling

🚨 Signs You Might Need to Scale

  • • Consistent high CPU or memory usage
  • • Slow response times affecting user experience
  • • Database connection limits being reached
  • • Queue jobs backing up regularly
  • • Your MVP is gaining real traction!

Scaling Options to Consider

First: Optimize What You Have

Often the biggest gains come from caching, query optimization, and fixing bottlenecks

Then: Vertical Scaling

Upgrade to larger instances - simple and effective for many use cases

Finally: Horizontal Scaling

Add more instances behind your load balancer

Advanced: Architectural Changes

Microservices, CDN optimization, advanced caching - these are good problems to have!

Getting Started: A Week-Long Plan

7-Day MVP Infrastructure Setup

Days 1-2: Foundation
  • • Set up AWS account and free tier resources
  • • Launch basic EC2 instance
  • • Configure simple RDS database
  • • Set up S3 for file storage
Days 3-4: Deployment
  • • Set up basic GitHub Actions
  • • Configure simple monitoring
  • • Implement basic caching
  • • Set up SSL certificates
Days 5-6: Polish
  • • Add basic monitoring alerts
  • • Set up auto-scaling foundation
  • • Configure backup strategies
  • • Optimize for your specific use case
Day 7: Validation Ready
  • • Test the complete system
  • • Set up basic analytics
  • • Document your setup
  • • Launch and start learning!

The Real Goal: Learning and Validation

Remember, the architecture isn't the product - it's just the foundation that lets you test your ideas with real users. Start simple, measure everything, and let user feedback guide your technical decisions.

AI makes it easier than ever to prototype quickly and iterate based on what you learn. If your idea gains traction, you'll face scaling challenges - but those are great problems to have! You can rebuild with confidence knowing there's real demand for what you're building.

💡 Key Takeaways

The best MVP architecture is the one that gets you to market fastest with the least complexity. Perfect is the enemy of good - focus on validating your idea, not building the perfect system.

Every successful product started with imperfect infrastructure. What matters is solving real problems for real users, not having flawless architecture from day one.

Need help getting your Laravel MVP off the ground quickly and cost-effectively? Let's chat about the fastest path from idea to validation. I love helping founders focus on what matters most - building something people actually want.

Chris Page

Chris Page

Fractional CTO and Software Engineer with 25+ years of experience. I help startups scale from 0 to 7 figures using AI-assisted development and proven frameworks.

Related Posts

Continue reading

Ready to Scale Your Startup?

Let's discuss how I can help you build your MVP or optimize your existing technology stack.