Laracon 2026 follow-up · research memo
Testing the claim that Laravel Cloud beats a $6 Forge box — against four real apps, their real cron schedules, and today's published rates.
The claim holds — but not for a lift-and-shift. Moved naively, your four apps cost more on Cloud than on Forge. Architected around one mechanic — how long the stack stays awake — they cost roughly a third less. The talk isn't "is Taylor right." It's "here is the one number that decides whether he's right for you."
The $6 in your notes is the droplet. It isn't what you pay. Forge is a subscription on top of the server, and the cheapest plan that provisions unlimited VPS servers is Hobby at $12/month.
| Forge side | Monthly | Note |
|---|---|---|
| Forge Hobby subscription | $12.00 | Flat, regardless of app count |
| VPS (the "$6 box") | $6.00 | Hosts all 4 apps |
| True baseline | $18.00 | $4.50 per app |
This changes the whole framing, and it's the strongest opening you have. Cloud doesn't need to beat $6. It needs to beat $18. That's a far easier bar — and saying so on stage is more interesting than agreeing or disagreeing with the keynote, because it reframes a comparison most of the room is also getting wrong.
You've been on Forge since launch. Confirm what you're actually billed — legacy plans differ from today's published tiers, and a grandfathered rate would change your personal number (though not the one your audience will pay).
Forge's $12 is flat. Moving one app off saves you nothing — you pay Forge $12 and Cloud on top. Savings are binary: they arrive only when the last app leaves. Budget for a period of paying both.
You named a database migration as the major hurdle and budgeted serious time for it. Both halves of that concern turn out to be wrong.
As of July 20, 2026, Laravel MySQL Flex sizes scale to zero. Sleep is triggered by an idle timeout you configure between 1 minute and 1 hour; the next connection wakes it. A proxy layer holds the incoming connection while compute resumes, so you get a slower first query, not a connection error — which directly answers the "DB is asleep and it will fail" worry in your notes. Storage stays online and billed the whole time.
You do not need to migrate any engine. Cut that work item.
ocean-city/config/database.php defaults to pgsql. Your highest-traffic app isn't part of the MySQL problem you were bracing for. The premise "most of my sites run on MySQL and that's the blocker" doesn't survive contact with the code.
Between them, these remove the single largest task from your plan and the single largest risk from your demo. That frees the September window for the work that actually determines the answer — which is all in the next section.
Compute is billed per second while awake. So your bill isn't a function of traffic — it's a function of how many seconds per month the stack is not asleep. And one line in the docs makes that number much larger than intuition suggests:
"When the environment wakes for a scheduled task or a manual wake interval, it stays awake for the duration of the sleep timeout."
A cron job that runs for two seconds doesn't bill two seconds. It bills the entire sleep timeout — and any inbound request resets the clock.
Two consequences follow, and they're the practical heart of your talk:
Cloud doesn't run schedule:run every minute against a sleeping app. It captures php artisan schedule:list at deploy time, stores it, and computes the next due moment for each task so it can wake at exactly the right time.
Because the schedule is captured at deploy time, changing a cron expression has no effect until you redeploy. This will bite someone in your audience.
Read from routes/console.php in each repo. Your notes guessed "once maybe twice a day." That's right for two apps and badly wrong for the third.
| App | Scheduled tasks found | Wakes/day | Awake @ 5-min timeout | Verdict |
|---|---|---|---|---|
| masterslog | 1 × daily() — purge expired team invitations |
1 | 2.5 h/mo | Ideal |
| boat.kzlvs.com | 2 daily + 2 weekly, all at 03:00 | 1 | 2.5 h/mo | Ideal |
| ocean-city.rentals | barefoot:unit:poll → everyFifteenMinutes(), plus daily sitemap |
97 | 240 h/mo | 33% awake |
One app is 99% of the risk. everyFifteenMinutes() on a 5-minute timeout keeps ocean-city — and every database it touches — awake a third of the month. Three fixes, in order of preference:
Worth saying plainly on stage: the tuned number is a projection from published rates and your real schedules, not a measurement. Landing it as a measurement is exactly what the migration is for.
You assumed Postgres was the privileged engine and MySQL the poor cousin. At the published rates it's the reverse, and it matters most for exactly the app with the highest duty cycle.
| Engine | Billing shape | If awake 100% |
|---|---|---|
| MySQL Flex 512 MB | Per-second, published monthly figure | ~$6 |
| Serverless Postgres | $0.320 per CPU-hour, no monthly figure quoted | ~$58 @ 0.25 CU |
Postgres is priced as an uncapped hourly rate. That's cheap when the database genuinely sleeps and expensive the moment duty cycle climbs — which is precisely ocean-city's situation today. So the migration worth considering isn't MySQL → Postgres. It's ocean-city Postgres → MySQL, and only if its duty cycle stays high after tuning.
This is the number I'd least want you to state on stage unverified. Run both shapes through the Cloud pricing calculator at your real duty cycle before quoting it. The direction is solid; the magnitude deserves a second source.
You called this "critical to get right," and guessed each app needs its own cluster. Half right — and the half that's wrong is the expensive half.
A cluster holds many databases (schemas). Four apps can share one. But scale-to-zero is a property of the cluster, not the schema — a shared cluster only sleeps when every app on it has gone idle. That gives a clean rule:
Share a cluster among apps whose idle windows overlap. Isolate any app that would hold the cluster awake for the others.
| Cluster | Contents | Why |
|---|---|---|
| Shared Flex 512 MB | masterslog + boat.kzlvs + the 2 remaining Forge apps | All quiet; boat and masterslog already cluster their cron near 03:00, so they wake together and sleep together |
| Dedicated Flex | ocean-city.rentals | Its polling would pin the shared cluster awake and hand the quiet apps a bill they didn't earn |
The saving is structural, not marginal. MySQL storage starts at 5 GB and bills whether or not the database sleeps, so every extra cluster is a permanent floor you pay forever. Four clusters means paying that floor four times over — before a single query runs. Two clusters instead of four is the difference between the architected column and the naive one in the headline numbers.
MySQL defaults to 7-day backup retention, billed separately at $0.10/GB-month. Fine for production; pure waste on a throwaway demo environment. Set it to 0 for anything disposable.
masterslog defines app/Console/Commands/SweepMaintenance.php (signature maintenance:sweep) — and nothing schedules it. The only scheduled task in that app is the daily invitation purge.
Your demo plan is "observe crons and queues run by checking sent timely reminders." The command that rolls maintenance schedules forward isn't wired to the scheduler, so there are no timely reminders to observe. This would have surfaced live, on stage.
boat.kzlvs.com runs Vessel::each(fn($v) => UpdateVesselHours::dispatchSync($v)) inside a scheduled closure. That's an unbounded loop running synchronously in the wake window — and per the docs, the app cluster stops when the sleep timeout elapses even if work is still running. Enough vessels and the sweep gets killed mid-flight, silently.
Fix: real dispatch() onto a Managed Queue, which runs on independent workers and is never interrupted by the app sleeping.
Cloud points the scheduler's cache driver at your environment's cache store. Tasks using withoutOverlapping() or onOneServer() query that store on every run — and those lookups can keep an attached database or cache awake, defeating scale-to-zero. A defensive habit picked up from years of Forge quietly becomes a line item on Cloud. That's a great slide.
On queues generally: you guessed image uploads. The code says otherwise — ocean-city has 11 ShouldQueue classes, boat has 8, masterslog only 2. Note that boat's scheduled work uses dispatchSync, so those jobs never touch a queue today. Worth documenting properly, as your notes intended — the answer is more interesting than the guess.
Starter allows 1 managed queue per environment, max 3 workers, Flex compute up to 1 GB. Enough for all four apps as they stand — but ocean-city at "hundreds of pages" is the one that could outgrow it. Also note deploying a managed queue sets QUEUE_CONNECTION=cloud environment-wide, silently rerouting every job that didn't name a connection.
That's tomorrow. "Move everything I have on Forge to Cloud" is not a one-day job — the cluster topology alone needs deliberate setup, and rushing it produces exactly the naive configuration that loses the argument.
The good news is that the deadline was mostly protecting you from the engine migration, and that work no longer exists. Suggested sequence:
maintenance:sweep first so the demo has something to demonstrate. Attach object storage for images, per your note.dispatchSync loop to a Managed Queue on the way in."I moved one app, measured it, and here's what the bill actually did" beats "I moved everything in a weekend and here are my projections." You'll have real invoice data for one app instead of estimates for four — and the migration-in-progress framing lets you show the naive configuration and the tuned one side by side. That comparison is the talk.
Your instinct to generate fake maintenance reminders is right, but aim it at a sharper target. Don't demo "the app works on Cloud." Demo the duty-cycle cliff — the thing nobody in the room has internalized.
maintenance:sweep has real work and reminders actually send.The Usage page gives an hour-by-hour breakdown by app and by resource, so this is directly screenshottable — no instrumentation needed. Set a hard org spend limit before you start, so a misconfigured demo can't produce a surprise invoice mid-experiment.
Everything above is drawn from published documentation and your repositories as they stand today. Five things I could not confirm from here, in rough order of how badly a wrong answer would hurt on stage:
Scale to Zero is cheaper than a Forge box — but it moves the cost from a monthly invoice you ignore to a cron expression you wrote two years ago and forgot. The bill is now a function of your code. That's the story, and it's better than a verdict.
masterslog, boating, ocean-city — routes/console.php, config/database.php, app/