← ALL POSTS
DEVOPS MAY 27, 2026·10 MIN

Deploying Laravel on AWS Lightsail, the lean way

A pragmatic setup with zero-downtime deploys that costs less than a coffee subscription.

rj
Rollie John Jaictin
Senior Software Developer
Server rack with patch cables and status lights in a data center

Not every client needs Kubernetes. For a lot of Laravel apps, a single well-configured Lightsail instance with a simple deploy script gets you 90% of the reliability at a fraction of the cost and complexity. This is that setup.

Key Takeaways

  • Lightsail costs $5–20/month for a solid Laravel app; Kubernetes costs 10x more for the same workload
  • Zero-downtime deploys work via atomic symlink swaps; no need for a platform
  • Use supervisor for queue workers so they auto-restart on crash
  • Monitor with CloudWatch; alerting catches issues faster than customer complaints

The setup

Nginx, PHP-FPM, and a supervisor process for queue workers, all on one instance. A deploy script pulls the latest code into a new release directory and symlinks it into place — the same pattern tools like Deployer and Envoyer use, just hand-rolled.

Initial setup:

# Create release directory structure
mkdir -p /var/www/releases
mkdir -p /var/www/shared/{storage,logs,node_modules}

# Create the symlink that points to current release
ln -s /var/www/releases/release-20260724-001 /var/www/app

# Give the web server permission to write to shared folders
chown -R www-data:www-data /var/www/shared

Deploy script (/home/ubuntu/deploy.sh):

#!/bin/bash
set -e

REPO="https://github.com/yourname/yourapp.git"
RELEASE_DIR="/var/www/releases/release-$(date +%s)"
SHARED_DIR="/var/www/shared"
CURRENT_LINK="/var/www/app"

echo "Deploying to $RELEASE_DIR..."

# 1. Clone the latest code
git clone --depth 1 --branch main $REPO $RELEASE_DIR

# 2. Install dependencies
cd $RELEASE_DIR
composer install --no-dev --optimize-autoloader

# 3. Link shared folders
ln -s $SHARED_DIR/storage $RELEASE_DIR/storage
ln -s $SHARED_DIR/logs $RELEASE_DIR/storage/logs
ln -s $SHARED_DIR/.env $RELEASE_DIR/.env

# 4. Run migrations (optional; only if needed)
php artisan migrate --force

# 5. Swap the symlink (atomic operation)
ln -sfn $RELEASE_DIR $CURRENT_LINK

# 6. Reload PHP-FPM to pick up the new code
systemctl reload php7.4-fpm  # or your PHP version

# 7. Restart queue workers
systemctl restart app-queue

# 8. Clear old releases (keep last 5)
ls -1d /var/www/releases/release-* | sort -r | tail -n +6 | xargs rm -rf

echo "Deploy complete!"

Run it:

bash /home/ubuntu/deploy.sh

The script is idempotent — run it twice, it works both times. No manual steps in Lightsail or AWS console.

Zero-downtime, without the platform

The secret is the atomic symlink swap. When you do ln -sfn $RELEASE_DIR $CURRENT_LINK, the OS guarantees the symlink changes instantaneously. Requests in-flight finish against the old code; new requests hit the new code. No downtime.

Combine that with php artisan down --render for migrations that need a moment:

# Before migrations (show maintenance page)
php artisan down --render=errors::503

# Run migrations
php artisan migrate --force

# Bring the app back up
php artisan up

Users see a brief “Under Maintenance” page; the app never goes truly down. Most users don’t notice.

Nginx config for serving the public folder:

server {
    listen 80;
    server_name example.com;
    root /var/www/app/public;

    location / {
        try_files $uri /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php7.4-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

Queue workers with supervisor

Queue jobs need a process that runs in the background. Supervisor manages it and auto-restarts on crash.

Install supervisor:

apt-get install supervisor

Configure it (/etc/supervisor/conf.d/app-queue.conf):

[program:app-queue]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/app/artisan queue:work --sleep=3 --tries=3
autostart=true
autorestart=true
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/shared/logs/queue.log
user=www-data

This runs 2 queue worker processes. If one crashes, supervisor restarts it. No manual intervention needed.

Start it:

systemctl restart supervisor

Monitor the logs:

tail -f /var/www/shared/logs/queue.log

Monitoring and alerting

Lightsail instances send metrics to CloudWatch by default. Set up alarms so you hear about issues before customers do.

Via AWS CLI:

aws cloudwatch put-metric-alarm \
  --alarm-name "app-cpu-high" \
  --alarm-description "Alert if CPU > 80%" \
  --metric-name CPUUtilization \
  --namespace AWS/EC2 \
  --statistic Average \
  --period 300 \
  --threshold 80 \
  --comparison-operator GreaterThanThreshold \
  --evaluation-periods 2 \
  --alarm-actions arn:aws:sns:us-east-1:123456789:your-topic

This sends an email when CPU usage stays above 80% for 10 minutes.

Set up alarms for:

  • CPU > 80%
  • Disk usage > 80%
  • Network errors (if disk fills, writes fail silently)

Cost breakdown (as of 2026):

  • Lightsail instance (2 GB RAM, 2 vCPU): $12/month
  • Static IP: $5/month
  • Database (if separate): $15–30/month
  • Total: ~$30/month for a small to medium app

A single EC2 instance with auto-scaling groups and load balancers costs 3–5x more. Kubernetes, 10x more.

When to graduate

This setup works until:

  • You need to scale horizontally (add a second server for load balancing)
  • Your deploy frequency outpaces what one person can babysit (invest in CI/CD automation)
  • Your database becomes a bottleneck (migrate to RDS, add read replicas)
  • You need cross-region redundancy (multi-region setup is more complex)

Until then, it’s the setup I reach for first. Simple, cheap, and reliable.

Takeaways

Lightsail + a deploy script gives you zero-downtime deploys, automatic queue handling, and monitoring for less than a coffee subscription. The symlink swap pattern is used by professional tools; implement it yourself and you get the same reliability without the overhead. Use supervisor for background jobs, monitor in CloudWatch, and keep the architecture single-server until it doesn’t fit. That discipline keeps costs predictable and deployments boring.