Going Cloud-Only With Laravel: What 7 Weeks on a €5 Dev Box Taught Me
Taylor Otwell wants to skip local dev tools. I moved my Laravel work onto a €5 box seven weeks ago. What it costs, and what still breaks.
On October 12, Taylor Otwell posted that if he bought a new laptop today, he didn't think he'd install any local dev tools or languages at all. "I think I could do everything I currently do with nothing but Laravel Cloud and @AmpCode Orbs," he wrote, and said he was going to try it. The post pulled 71 replies, and three questions kept coming up. What does it cost? How do you debug when nothing runs on your machine? And can you trust what the agent ships?
I started a version of this experiment seven weeks before he posted. Not with cloud agents, though. With one €5 VPS that holds my projects, a tmux session per project, and Claude Code running inside them. I wrote up the setup and the first round of failures at the end of August.
One correction first. I'm not cloud-only, and that turned out to be the most useful finding.
Two ways to put development in the cloud
From a phone, Taylor's setup and mine look the same. A terminal, an agent, a staging URL. Underneath they're opposite shapes.
Amp's Orbs are ephemeral remote machines. Every new thread gets its own one, with the repository cloned and whatever the agent needs installed. An orb pauses when the agent goes idle, and a paused orb costs nothing. Amp's own description is that "only the diff leaves the orb." Deploys then go through Laravel Cloud, which builds from the repository.
My box keeps everything. One persistent machine, each project checked out once, a tmux session per project that survives any disconnect. The Mac and the iPhone are windows onto it. Staging runs on the box behind a Cloudflare Tunnel with an Access login in front, and production deploys go out through each project's deploy.sh.
The real difference is state. An orb starts from the repository and keeps only the diff. My box holds SQLite copies of production data, queue workers that never stop, a Telegram bot that's always polling, and whatever work is in progress. That persistence is the reason the box is useful, and the cause of most of what broke.
I'm not cloud-only, and the Mac proves it
The Mac still has PHP, Composer and Node. I ran php artisan on it today. A few kinds of work never moved. NativePHP sessions run on the Mac, where the phone and the dev server share a Wi-Fi network. The repo I use for client demos stays local on purpose. So two machines still hold checkouts.
And two checkouts drift, which is why I built the box in the first place. A sync script that reports what's out of step helps. The real fix was a rule instead. A project lives in exactly one place. For anything that has moved to the box, the Mac doesn't keep a copy at all. It attaches to the box's tmux session with one alias, and the laptop becomes the same kind of window the phone is.
Push still matters, for a different reason now. The box is one machine that can die, so every project's CLAUDE.md tells the agent to pull at session start and push after every commit. Each deploy.sh also refuses to run with unpushed commits, because it ships files rather than pulling from git. Without that gate, production can end up running code that exists on no remote. That happened once.
Orbs make this whole section disappear. Nothing lives outside the repository, so there's nothing to drift. That's the first point in Taylor's favour.
What it costs after 52 days
These are the box's numbers on October 16, read straight from it:
| Measure | Value |
|---|---|
| Price | €5 a month, VAT included |
| Uptime | 52 days, no reboot |
| RAM in use | 1.3 GB of 3.9 GB, with two Claude Code instances open |
| One Claude Code instance | 330 to 355 MB resident |
| Swap in use | 178 MB of 2 GB |
| Disk | 15 GB of 78 GB |
| Load average | 0.05 |
In August I estimated roughly 450 MB per Claude Code instance and called 4 GB the binding constraint. The real figure is lower, and the box spends most of its time waiting on the model, which is why the load average sits near zero.
Taylor's side, from the public price lists. I haven't used Orbs, so read this as a reading of pricing pages, not a review.
- Amp's pricing page lists an Individual tier at $20 a month that includes 45,000 minutes of orb time, and tokens can come from your own Claude or ChatGPT subscription. Those minutes are counted at
a1.small, the size closest to my box. That's 750 hours, which Amp says covers three at once for eight hours a day. - Pay-as-you-go orb rates run from $0.08 an hour (1 CPU, 2 GB) to $2.13 an hour for the largest size. The closest match to my box,
a1.smallwith 2 CPUs and 4 GB, is $0.17 an hour. - Laravel Cloud's Starter plan is $5 a month plus usage, with $5 of usage credit included and compute that scales to zero.
Careful with the comparison, though. My €5 covers development only. Production runs on servers I was already paying for, which is its own trade-off and the subject of my Cloud vs Forge vs VPS breakdown. So the fair line is Amp's $20 against my €5. The gap is real and small. The other difference is the shape of the bill. Mine is flat whether I work two hours or twelve. Amp's is flat too until the included minutes run out, and orb minutes grow with hours and with parallel threads. And parallel threads are the point of orbs. They run several agents at once, each in its own machine. My box runs them side by side in one.
What broke after the first post
The August post had a table of everything that broke during setup. These came later, from daily use. Several are Laravel-specific, and a few would hit anyone working on a remote machine.
| Symptom | Cause | Fix |
|---|---|---|
| Livewire buttons did nothing on a tunnelled app. No error, state never saved | Laravel generated http:// URLs behind the tunnel, and Livewire's update request never landed |
Force the scheme and root URL in AppServiceProvider |
| A queued job died on a config key that was right there in the file | The worker booted before the edit and still held the old config | Restart the queue unit after any change, or run queue:listen |
| Telegram couldn't deliver webhooks to the bot | Cloudflare Access guards every path, so Telegram got the login page | Long polling with getUpdates from a systemd unit, no inbound route |
| Search Console data on one site froze for a week, silently | A deploy's rsync --delete removed a production-only credential under storage/app/private |
Exclude storage/app/private/ wholesale in the rsync exclude file |
deploy.sh said "Cannot connect to server" while the site stayed up |
The production box's Tailscale key had expired six days earlier, and deploy.sh connected over Tailscale |
Disable key expiry on servers |
deploy.sh hung, then timed out on port 22 |
I closed public SSH on a production box, but the dev box's SSH alias still pointed at the public IP | Update every client alias the day a box is hardened |
cloudflared put a second domain's DNS record inside the wrong zone |
The cert.pem from cloudflared tunnel login is scoped to the zone picked at login |
Add the CNAME by hand in the other zone |
| Every request to Reddit returned 403 "Blocked" | Reddit blocks requests from the hosting provider's IP range | No direct fix. A Reddit front-end mirror loaded in headless Chromium works from the box |
| Playwright couldn't find its browser inside Claude Code's shell | The agent's shells don't source /etc/environment, so PLAYWRIGHT_BROWSERS_PATH is unset |
Set the variable on the command itself |
The Livewire one is a trusted-proxy trap
TLS ends at Cloudflare, and the tunnel hands Caddy plain HTTP. Caddy's reverse proxy, which php_fastcgi builds on, sets X-Forwarded-Proto to the scheme it saw and ignores incoming values unless the sender is a trusted proxy. So PHP receives X-Forwarded-Proto: http.
My vhosts also pass HTTPS=on to PHP-FPM, and that fixed mixed-content errors everywhere else. But this app had trustProxies(at: '*') in bootstrap/app.php, so Laravel believed the forwarded header over the server variable and generated http:// URLs. Livewire's update endpoint was one of them, so from an HTTPS page its update requests went to an http:// URL and never landed. Nothing logged an error.
The fix that's running now, on Laravel 13.30 with Livewire 4:
// app/Providers/AppServiceProvider.php
public function boot(): void
{
// TLS ends at Cloudflare and the tunnel hands Caddy plain http. Every
// generated URL (Livewire update endpoint included) must still be https,
// or Always-Use-HTTPS turns Livewire's POST into a dead GET.
if (str_starts_with((string) config('app.url'), 'https://')) {
\Illuminate\Support\Facades\URL::forceScheme('https');
\Illuminate\Support\Facades\URL::forceRootUrl(config('app.url'));
}
}
The guard on app.url keeps a plain http://localhost setup working.
The queue worker one gets worse on a dev box
In production, the deploy restarts the workers. On a dev box nothing deploys. You edit config/services.php, the worker running under systemd keeps the config it booted with, and the next job reads the new key as null. Mine died like this:
array_map(): Argument #2 ($array) must be of type array, null given
The queue docs explain why. Workers "store the booted application state in memory" and "will not notice changes in your code base after they have been started." There are two ways out. Restart the unit after every code or config change, which is what I do now. Or run queue:listen on the dev box, which reloads your code for each job and is, in the docs' words, "significantly less efficient."
The trust question, seven weeks in
The August FAQ covered production deploy keys on a box with an agent on it. What's new is isolation between projects.
When the Telegram bot needed its own agent login and its own credentials, sharing root's was wrong and sharing www-data's wasn't much better. It got a dedicated system user, its own PHP-FPM pool on its own socket, its own queue unit and its own cron line. And it's deliberately kept out of the www-data group, because the other projects' .env files are group-readable.
That's isolation an orb gives you for free. Each thread is its own machine, and only the diff comes out. On a shared box, you build isolation per project, by hand, after you notice you need it.
The maintenance nobody demos
While I was collecting the numbers above, the box reported a pending reboot. It's running kernel 6.12.101. Three kernel updates have landed since it booted, and the newest, 6.12.111, is installed and waiting. I found the same trap on a production server in September, where unattended upgrades had installed five kernels that never ran. Uptime looks great on a status page. It also means the running kernel is whatever booted last.
Then there's the Tailscale key row in the table. The default node key expiry is 180 days, and when it passes, "connections to/from the given endpoint will stop working." On a server with SSH locked to the tailnet, that's admin access gone while the site keeps serving. The production boxes I hardened in September have expiry disabled now, and so does the dev box.
None of this is hard. But it's mine to remember. With orbs and Laravel Cloud, patching and access are the provider's job.
Where each setup wins
Taylor's setup wins on these:
- Isolation per task, and several agents running in parallel
- No drift, because nothing exists outside the repository
- No box to patch, reboot or keep reachable
- Git-based deploys, which remove the whole
rsync --deleteclass of failure
The box wins on these:
- A flat €5 whatever the hours
- State that outlives a thread, like production data copies, a bot that polls all day and workers that are always up
- Root, any language, any tool, at no extra cost
- Staging on real data behind a login, without a per-environment price
If your work is one or two apps already on Laravel Cloud, Taylor's setup removes failure classes I spent seven weeks fixing by hand. If you run several small apps on your own VPSes, with SQLite and long-running processes, one persistent box is cheaper and fits that shape better. I'm keeping the box.
FAQ
Can you run Laravel queue workers on a remote dev box?
Yes, with one habit. Workers keep the application state they booted with, so restart the worker's unit after any code or config change. If you'd rather not think about it, run php artisan queue:listen on the dev box instead. It reloads code for every job at a performance cost that rarely matters in development.
How much RAM does a dev box running Claude Code need?
On mine, two Claude Code instances use 330 to 355 MB each, and the whole box uses 1.3 GB of 4 GB with a tmux session per project. A 2 GB swap file covers spikes from composer install and npm run build. 4 GB is comfortable for a few agents at once.
Why do Livewire actions fail silently behind Cloudflare Tunnel?
Usually because Laravel generates http:// URLs. With trustProxies(at: '*'), Laravel trusts X-Forwarded-Proto, and a local proxy behind the tunnel may report http. Livewire then sends its updates from an https:// page to an http:// URL, and they never reach the component. Forcing the scheme and root URL in AppServiceProvider::boot() fixes it.
Are Amp Orbs cheaper than a VPS for Laravel development?
Not on the price list. Amp's Individual tier is $20 a month with 45,000 orb minutes (750 hours at the 4 GB size), against €5 for a 4 GB VPS. Orbs buy isolation, parallel agents and zero maintenance. A VPS buys persistent state and a flat bill. I haven't used Orbs, so this comes from Amp's published pricing.
The real bill
Money was never the expensive part. Attention was. Orbs take most of that off your plate for $20 a month instead of €5, and Laravel Cloud takes the rest on its own bill. Whether that's worth it depends on one question. Do your projects need state that outlives a thread? Mine do.
About Hafiz
Senior Full Stack Developer. I build production software with Laravel, Filament, Vue, and AI integrations, and write about the real decisions behind shipping it.
Get in touch →Get web development tips via email
Join 50+ developers • No spam • Unsubscribe anytime
Related Articles
How I Turned a €5 VPS Into a Dev Workstation I Can Use From Anywhere
One cheap VPS is the workstation. Laptop and phone are windows onto it. The full...
Laravel Cloud Managed Queues vs Horizon: What You Give Up and What You Get
An honest comparison of Laravel Cloud Managed Queues and Horizon: autoscaling, p...
Laravel Cloud vs Forge vs Hetzner: What I'd Actually Pick at Each Stage
Three options, three very different trade-offs. Here is what Laravel Cloud, Forg...