My main server runs a dozen-plus applications: Laravel apps spanning several PHP versions, a couple of Next.js sites (including this one), a fully Dockerised SaaS stack with Postgres and Redis, MariaDB, a self-hosted chat server, and assorted cron-driven odds and ends. One VPS. No Kubernetes, no managed platform, no per-app hosting bills.
This is deeply unfashionable, so let me make the case for it — and be honest about where it stops working.
The economics are hard to argue with
A capable VPS costs less per month than one mid-tier managed-platform app. Multiply by a dozen apps — side projects, client work in various stages of life, my own products — and the platform route would cost more than my office rent. The trade is that you become the platform team. For a solo engineer who already knows Linux, that trade is mostly won upfront: the marginal cost of app #13 is a server block and a systemd unit.
There's a second benefit nobody mentions: running your own box is compound education. Every queue worker that dies, every full disk, every misbehaving proxy makes you better at diagnosing the same problems in client systems. I bill for that skill; the VPS is where it gets sharpened for free.
The layout that keeps it sane
The failure mode of shared hosting-by-hand is one giant /var/www owned by root where everything can touch everything. The structure that works:
- One Linux user per site. Each app lives in its own home directory, runs as its own user, and can't write outside it. A compromised WordPress plugin (client work brings surprises) is contained to one account, not the box.
- One PHP-FPM pool per app, pinned to its PHP version. Legacy client apps stay on the version they were built for; new work gets the latest. FPM pools give each app its own memory budget and process limits, so one app's traffic spike can't starve the rest.
- nginx as the only front door. Every app is just an upstream — a PHP socket, a Node port on localhost, a Docker-published port. TLS, headers, and rate limits live in one place.
- Docker only where it earns it. My newest product runs as a Compose stack — API, queue, scheduler, WebSockets, Postgres, Redis — because it needs versions and isolation the host shouldn't provide globally. Older apps don't get retrofitted into containers for ideology. Docker is a tool here, not a religion.
The three rules that prevent 3am pages
Budget memory like money. Every long-running process — FPM pools, Node servers, queue workers, databases — gets a ceiling, and the ceilings must sum to less than RAM. The default failure on a shared box isn't CPU; it's the OOM killer executing your most important process to save your least important one.
Everything long-running gets a supervisor. systemd, Supervisor, pm2, Docker restart policies — I don't care which, but nothing runs from a screen session. If it can't survive a reboot unattended, it isn't deployed.
Deploys must be one command. Mine are a git push — CI connects over SSH, builds as the site's own user, restarts the right process, and health-checks the result. The moment deploys involve remembering steps, one of the dozen apps will rot.
When you genuinely shouldn't do this
- A client's revenue-critical production app doesn't belong on your shared box, mixed with your experiments. Sell them their own server or a managed platform; isolation is what they're paying for.
- Anything with compliance requirements — the audit story for "my personal VPS" is a short and unhappy one.
- When the team is more than you. This setup scales to exactly one operator who has the whole map in their head. The second engineer deserves infrastructure-as-code.
The point isn't that a single VPS is the right architecture. It's that matching infrastructure to actual needs is the skill — and for a portfolio of small apps run by one person who knows their tools, the boring answer has been winning for years.