Skip to main content

Laravel Vapor to Cloud Migration Guide

Step-by-step guide for migrating Laravel projects from Vapor to Cloud. For Cloud-specific configuration patterns and known gotchas, see the Configuration Reference in the Laravel Cloud overview.

Migration Checklist​

1. Initial Setup​

  • Create app in Laravel Cloud (connect GitHub repo)
  • Set environment name (staging/production)
  • Configure branch (Cloud defaults to "master"; set to main for migrated repos)
  • Disable Push-to-deploy in the environment settings if CI is going to trigger deploy hooks (otherwise every push fires two deploys)
  • Copy env vars from Vapor equivalent environment
    APP_KEY, APP_DEBUG, DB_*, etc.
    warning

    Vapor secrets are NOT exported. Add them manually in Cloud.

  • Add AWS credentials (see "AWS - vapor-laravel user, for Laravel Cloud" in 1Password)
  • Use public endpoint for external databases (Cloud has no VPC peering with external providers like SingleStore)
  • Create managed Valkey resource (Cache) in the Cloud dashboard if the project uses Redis
  • Enable Scheduler in the environment settings (separate toggle, not enabled by default)
  • Deploy and verify on .laravel.cloud domain

2. Code Changes​

Remove Vapor:

  • Delete vapor.yml, vapor.staging.yml, vapor.production.yml
  • Delete vapor.sh, deploy.sh, vapor-base.Dockerfile, post-deploy scripts
  • Remove laravel/vapor-cli and laravel/vapor-core from composer
  • Clean up .gitignore (remove Vapor, Homestead, Vagrant entries)
  • Remove orphaned vapor-ui public assets if present
  • If public/vendor/horizon/ is tracked in git: git rm -r public/vendor/horizon and add /public/vendor/horizon to .gitignore. The Cloud build runs php artisan horizon:publish, which regenerates these assets on every deploy. Leaving them committed is a landmine (stale assets keep the dashboard "working" until a Horizon upgrade desyncs them).

Update CI/CD:

  • Add Laravel Cloud deploy hooks to CI (see How We Deploy for the hardened curl snippet). A bare curl -X POST "$HOOK" silently swallows non-2xx responses; always include --fail-with-body --retry 3 --retry-all-errors --connect-timeout 10 --max-time 30.
  • Append ?commit_hash=<sha> to the hook URL so the Cloud dashboard shows the real commit per deploy
  • Remove the Vapor deploy job (for production, keep a manual-trigger backup until Cloud is validated)
  • Remove post-deploy health checks (hooks are async)
  • Add LARAVEL_CLOUD_STAGING_DEPLOY_HOOK and LARAVEL_CLOUD_PRODUCTION_DEPLOY_HOOK as CI secrets. On GitHub, scope them to the matching environment (staging, production) so they're only exposed to jobs declaring that environment.
  • Add a workflow_dispatch trigger so deploys can be re-fired manually when the hook is async and a single retry isn't enough

Configure Build/Deploy Commands: set in the Laravel Cloud dashboard per environment. See Build & Deploy Commands for typical values.

3. Optimizations​

  • Configure faster CPU (if needed for workload)
  • Enable persistent DB connections - disable in test environment
  • Add php artisan optimize to deploy commands (see artisan optimize)
  • Replace Sentry with Nightwatch (server-side PHP only). Do these in order:
    1. Switch config/logging.php stack from a hardcoded array to 'channels' => explode(',', env('LOG_STACK', 'stderr,single')) so Cloud can append the nightwatch channel via env without a code change. A hardcoded channels array silently drops it.
    2. Remove the sentry channel definition from config/logging.php. If the LOG_STACK default still contained sentry, your first boot after deploy would fail referencing a missing channel; keeping the two steps in this order avoids the gap.
    3. Remove sentry/sentry-laravel from composer.json and delete config/sentry.php
    4. Remove Sentry deploy notifications from CI
    5. Install laravel/nightwatch
    6. Create project/environment in Nightwatch and copy the token
    7. Activate Nightwatch integration in the Cloud environment with the token
    • Keep Sentry JS SDK if the project has a frontend (e.g., Farfalla)
info

Once activated, Nightwatch SDK env vars are auto-injected by Cloud. No further app config needed.

4. Queue/Horizon Setup (if applicable)​

See Horizon, Queue Connection, and Valkey in the Configuration Reference.

  • Move laravel/horizon from require-dev to require (if dev-only)
  • Add QUEUE_CONNECTION=redis to env vars (not auto-set by Cloud!)
  • Verify REDIS_USERNAME is auto-injected by Cloud (set when creating the managed Valkey resource)
  • Configure Horizon supervisors for each environment in config/horizon.php
  • Enable horizon:snapshot for non-local environments
  • Add HTTP Basic Auth middleware for Horizon (Cloud doesn't auto-protect)
  • Create Worker Cluster in Laravel Cloud dashboard
    • Add background process: php artisan horizon
    • Configure compute size for queue workload

5. Pre-cutover Validation​

Smoke test on the .laravel.cloud domain before swapping DNS:

  • Verify the app responds (main routes return 200)
  • Test admin panel login (Nova/Filament/etc.)
  • Verify DB reads and writes
  • Confirm migrations applied (migrate:status is green)
  • Open Horizon dashboard at /horizon and confirm auth protection
  • Dispatch a test job and confirm it processes (no jobs piling up in pending)
  • Test S3 reads/writes (upload + signed URL)
  • Verify scheduled tasks running (schedule:list shows next run timestamps)
  • Confirm Nightwatch is reporting traces
  • Confirm Sentry JS is reporting frontend errors (if applicable)

6. Domain Swap​

  • At least a day before the swap: lower the DNS TTL on the domain record (Cloudflare: 30-60s). Without this, propagation can take hours; with it, the swap effectively completes in minutes.
  • Add custom domain in Laravel Cloud
  • Create CNAME in Cloudflare pointing to Laravel Cloud (proxy ON)
  • Wait for DNS propagation. Practical signal: watch Vapor Lambda invocations in CloudWatch drop to ~0 for 5-10 min consecutive while Cloud request volume rises in Nightwatch.
  • Test custom domain
  • Disable the .laravel.cloud domain
  • Restore the DNS TTL to its normal value once the cutover is stable

7. Final Cleanup​


Resources​

X

Graph View