DEV Community

Sumeet Shroff
Sumeet Shroff

Posted on Originally published at mumbaiwebdesigner.com

Laravel Cloud vs Forge vs a Self-Hosted VPS

Laravel Cloud vs Forge vs a Self-Hosted VPS

Three paths exist for getting a Laravel application into production: let Laravel Cloud handle everything, use Forge to manage your own servers, or configure a VPS from scratch. Each represents a different point on the control-vs-convenience spectrum, and picking the wrong one costs you either money, engineering time, or both.

This article cuts straight to the trade-offs. It assumes you already understand basic Laravel deployment concepts — if you want the broader picture, the Laravel Deployment and Hosting: Cloud, Forge, VPS, Docker, and Octane guide covers the full stack.

Prerequisites: Laravel 12 (PHP 8.2+) or Laravel 13 (PHP 8.3+), Composer 2.x, a GitHub/GitLab repository. For Forge: an account at forge.laravel.com. For Laravel Cloud: an account at cloud.laravel.com.


The Core Difference

Dimension Laravel Cloud Forge + VPS Self-Hosted VPS
Infrastructure owner Laravel/AWS EC2 You (via DigitalOcean, Hetzner, etc.) You
SSH access No Yes (full root) Yes (full root)
Deployment trigger Git push Git push or Forge webhook Custom CI/CD
Auto-scaling Yes (per-replica) No (manual resize) No
Pricing model Usage-based Flat monthly ($12–$29/mo for Forge + server cost) Server cost only
Zero-downtime deploys Built-in Built-in (single server) Manual setup
Ops overhead Near zero Low High

Laravel Cloud

Launched on February 24, 2025 alongside Laravel 12, Laravel Cloud is a fully managed, serverful platform running on Amazon EC2. "Serverful" is the important word — your application container stays warm between requests. There are no Lambda cold starts.

What it does automatically:

  • Provisions isolated environments (production, staging, preview per branch)
  • Runs composer install --no-dev, config/route/view cache, and migrations on each deploy
  • Scales replicas up and down based on traffic
  • Rotates SSL certificates
  • Manages encrypted environment variables

What you cannot do:

  • SSH into the underlying instance
  • Install custom PHP extensions not included in the platform image
  • Configure Nginx directly
  • Run long-lived daemons outside of the provided worker abstraction

Pricing: The Starter plan carries no monthly subscription fee — you pay only for compute and bandwidth consumed. Growth plans begin at $20/month with higher resource ceilings. Pricing is usage-based, which is predictable when traffic is steady but can spike with traffic bursts.

When to choose Cloud:
You want zero ops overhead. Your team does not have a dedicated DevOps engineer. Traffic is variable and you want auto-scaling without provisioning extra capacity manually. You are comfortable with the Laravel ecosystem's managed environment and the associated vendor dependency.


Laravel Forge

Forge is a server management panel, not a hosting provider. You bring the cloud server — DigitalOcean, AWS, Hetzner, Vultr, or any provider that lets Forge connect via API. Forge then provisions Ubuntu, configures Nginx, PHP-FPM, MySQL or PostgreSQL, Redis, and Supervisor, and wires up your deployment pipeline.

The October 2025 overhaul introduced Laravel VPS — a DigitalOcean-backed product within Forge that provisions an Ubuntu server in seconds with consolidated billing. You get full SSH and root access, but Forge handles the initial stack configuration.

Zero-downtime deployments (enabled by default for new Forge sites):

Forge uses an atomic symlink strategy. Each deployment clones your repository into a timestamped directory under releases/, runs your deployment script, then switches the current symlink. The last four releases are retained for instant rollback.

# Forge deployment script (configured in Forge dashboard)
cd /home/forge/example.com
git pull origin main

composer install --no-dev --optimize-autoloader --no-interaction

php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
php artisan migrate --force

php artisan queue:restart
Enter fullscreen mode Exit fullscreen mode

Critical note: php artisan migrate --force bypasses the production confirmation prompt. Always test migrations on a staging server first — schema changes can cause irreversible data loss.

Shared paths configuration:

For zero-downtime deployments, .env and storage/ must persist across releases. In Forge's dashboard, under a site's zero-downtime settings, add these shared paths:

.env
storage
Enter fullscreen mode Exit fullscreen mode

Forgetting this causes each release to deploy with an empty storage directory — uploaded files vanish and sessions break.

Multi-server limitation:

Forge's atomic deployments work on a single server only. If you run two application servers behind a load balancer, Forge cannot coordinate a simultaneous atomic deploy across both. For that you need Laravel Envoyer, which deploys a single project across multiple servers with health checks before traffic is switched.

When to choose Forge:
You want full server control — custom Nginx configs, specific PHP extensions, multiple queue worker groups, PostgreSQL 18 (now supported in Forge for new deployments), or the ability to SSH in when things go wrong. Traffic is relatively predictable and a fixed-size server covers your load. You want flat-rate pricing.


Self-Hosted VPS (No Forge)

Provisioning and configuring a VPS without a panel means owning every layer: OS updates, Nginx config, PHP-FPM pools, SSL renewal, firewall rules, and deployment scripting. This is operationally expensive but gives you complete control and the lowest possible cost per compute unit.

Baseline production stack (Ubuntu 24.04 LTS):

# /etc/nginx/sites-available/example.com
server {
    listen 80;
    server_name example.com;
    root /var/www/current/public;
    index index.php;

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

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }
}
Enter fullscreen mode Exit fullscreen mode

Queue worker via Supervisor:

# /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/current/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=8
redirect_stderr=true
stdout_logfile=/var/www/current/storage/logs/worker.log
stopwaitsecs=3600
Enter fullscreen mode Exit fullscreen mode

Run sudo supervisorctl reread && sudo supervisorctl update && sudo supervisorctl start laravel-worker:* after adding the config.

After every deployment, restart long-running processes:

php artisan queue:restart
sudo supervisorctl restart laravel-worker:*
Enter fullscreen mode Exit fullscreen mode

New application code is NOT picked up by queue workers until they restart. This is the most common production bug in self-managed deployments.

When to choose a bare VPS:
You are running predictable, steady-state traffic on a tight budget. A $6–12/month DigitalOcean or Hetzner droplet handles substantial CRUD traffic. You have the ops experience to manage patching, backups, and incident response, or you are willing to learn.


Common Mistakes Across All Three

1. Calling env() outside config files after config:cache

Once php artisan config:cache runs, .env is no longer read at runtime. Any env() call that is not inside a config/*.php file returns null.

// WRONG — returns null in production after config:cache
$apiKey = env('STRIPE_SECRET');

// CORRECT — read from config, which was cached from .env
$apiKey = config('services.stripe.secret');
Enter fullscreen mode Exit fullscreen mode

2. Not restarting Octane or queue workers after deploy

Laravel Cloud handles this automatically. Forge's deployment script must include php artisan queue:restart. A bare VPS requires a Supervisor restart command in your CI/CD pipeline.

3. Exposing database ports publicly

MySQL, PostgreSQL, and Redis should never be accessible on public interfaces. On a bare VPS, configure UFW to block ports 3306, 5432, and 6379 from external access. Forge provides a firewall UI to do this from the dashboard.

4. APP_DEBUG=true in production

With APP_DEBUG=true and CVE-2024-13918/CVE-2024-13919 present, Laravel's debug error page reflects request parameters unescaped, enabling reflected XSS. Always set APP_DEBUG=false in production .env.

5. Skipping --no-dev on composer install

Development dependencies (PHPUnit, Mockery, Laravel Pint) add 30–50 MB to the vendor directory and introduce unnecessary attack surface. Always use composer install --no-dev --optimize-autoloader in production.


Security Checklist Before Going Live

  • Patch to Laravel 12.1.1+ or 11.44.1+ to resolve CVE-2025-27515 (file validation bypass)
  • If using Livewire, update to v3.6.4+ — v3.6.3 and below carry CVE-2025-54068, a critical RCE vulnerability
  • Confirm register_argc_argv is disabled in php.ini (mitigates CVE-2024-52301, CVSS 8.7)
  • Verify APP_KEY is unique per environment — sharing keys between staging and production allows session/cookie forgery
  • Never bake .env files or secrets into Docker images

Verification After Deployment

Laravel 12 introduced native health check routes requiring no extra packages. Hit the endpoint after each deployment:

curl -s https://example.com/up
# Returns HTTP 200 with JSON: {"status":"ok"} when healthy
# Reports database, cache, and queue status
Enter fullscreen mode Exit fullscreen mode

In Forge, you can configure this URL as a deployment health check — Forge aborts the symlink switch if the health endpoint returns a non-200 status, preventing a broken release from going live.

For bare VPS deployments, add the health check as the final step in your CI/CD pipeline:

# In your deploy script — abort if health check fails
HEALTH=$(curl -s -o /dev/null -w "%{http_code}" https://example.com/up)
if [ "$HEALTH" != "200" ]; then
  echo "Health check failed ($HEALTH) — rolling back"
  ln -sfn /var/www/releases/previous /var/www/current
  exit 1
fi
Enter fullscreen mode Exit fullscreen mode

Decision Summary

  • Laravel Cloud if you want push-to-deploy with zero server management and are comfortable with usage-based pricing and no SSH access.
  • Forge if you want managed deployment tooling, full SSH access, predictable flat-rate billing, and the ability to customise your stack (PHP extensions, PostgreSQL 18, custom Nginx blocks).
  • Bare VPS if you have the ops expertise to manage the full stack, want the lowest cost per compute unit, and need maximum control over every layer of the environment.

For the large majority of Laravel projects — especially agencies and product teams without dedicated DevOps — Forge with a mid-tier DigitalOcean or Hetzner droplet is the pragmatic default. Laravel Cloud is compelling when auto-scaling is a hard requirement. A bare VPS pays off only when the ops cost is genuinely lower than the time saved by a managed tool.


If you need Laravel development in Mumbai, Mumbai Web Designer builds production-grade Laravel applications.

Top comments (0)