Every few months someone posts a cloud bill screenshot and the internet re-fights the same war: "just get a $20 VPS" versus "your time is worth more than that." Both sides are right, both sides are lying by omission, and I say that as someone who runs production workloads both ways — client projects on managed platforms, my own stack on a VPS with Docker.
The dishonesty isn't in the numbers; it's in what each side leaves off the invoice. The VPS crowd doesn't bill their own hours. The managed crowd forgets the line items that appear after the pricing page — egress, per-seat creep, the backup add-on. So let me do the comparison the way I'd do it for a client: same workload, both architectures, all the costs on the table, including the ones denominated in your evenings.
The workload we're pricing
A realistic small SaaS — the kind I described building in my multi-tenant admin panel post: a Next.js frontend, a NestJS API, PostgreSQL, Redis for sessions and queues, background workers, backups, uptime monitoring, and error tracking. A few thousand users, maybe 50 GB of data, nothing exotic. This is the profile of 90% of the products that ask "should we self-host?"
Option A: one VPS, Docker, everything on it
This is the stack from my Next.js + NestJS Docker deployment post: a single compose file running web, api, Postgres, and Redis behind Caddy, on a Hetzner or DigitalOcean box.
services:
caddy: { image: caddy:2, ports: ["80:80", "443:443"] }
web: { build: ./apps/web }
api: { build: ./apps/api }
worker: { build: ./apps/api, command: node dist/worker.js }
db: { image: postgres:16, volumes: [pgdata:/var/lib/postgresql/data] }
redis: { image: redis:7 }
The monthly bill:
| Item | Cost |
|---|---|
| VPS, 4 vCPU / 8 GB (Hetzner CPX31 class) | ~$16 |
| Object storage for backups (B2/S3, ~100 GB) | ~$1 |
| Uptime monitoring (Healthchecks.io / UptimeRobot free tier) | $0 |
| Error tracking (self-hosted GlitchTip or Sentry free tier) | $0 |
| Domain + email | ~$3 |
| Headroom / second small VPS for staging | ~$5 |
| Total | ~$24/mo |
That number is real. I run more than this for less. And modern tooling has genuinely shrunk the ops burden — Docker Compose, Caddy's automatic TLS, wal-g shipping Postgres backups to object storage, a GitHub Actions deploy over SSH. This is not the sysadmin work of 2012.
Option B: the same thing, managed
Now the equivalent with each concern handed to a platform:
| Item | Service | Cost |
|---|---|---|
| App hosting (web + api + worker) | Railway / Render / Fly.io | ~$25–40 |
| PostgreSQL with backups + HA | Managed PG (Render/RDS class) | ~$25–60 |
| Redis | Upstash / managed Redis | ~$10 |
| Error tracking | Sentry Team | ~$26 |
| Uptime monitoring | Better Stack / Checkly | ~$10 |
| Egress + storage overages | Everyone, eventually | ~$5–20 |
| Total | ~$95–165/mo |
Call it $95/mo on the frugal end against $24. Roughly 4x — and the gap widens with scale, because managed pricing scales per-GB and per-seat while a VPS scales in $15 hardware jumps. At serious scale the multiple gets worse, not better.
So self-hosting wins, right? That's where the honest accounting starts, not ends.
The bill nobody itemizes: your hours
Everything on that VPS is now your job. Concretely, from my own logs of running such a stack:
- Security patching. Unattended-upgrades handles the OS, but you still own Docker image updates, Postgres minor versions, and reading the occasional CVE announcement. Call it 1–2 hours a month, forever.
- Backup discipline. Setting up WAL archiving is an afternoon. Being sure it works means automated restore tests — which you must build, then maintain.
- The 3 a.m. incident. Rare, but it's yours alone. Disk full from unrotated logs, a container OOM-looping, certificate renewal failing. On a managed platform most of this class of failure is somebody's pager in Virginia; on your VPS it's your phone on the nightstand.
- The knowledge tax. Every piece — Caddy config, systemd units, restore runbooks — lives in your head. On a team, that's a bus factor of one unless you document it, which is also hours.
Price those hours honestly. If self-hosting costs you even 3 hours a month and your time is worth $50/hour, your "$24 VPS" costs $174 — you didn't beat the managed bill, you matched it while carrying the pager yourself. If you're a founder whose scarce resource is product time, that's a bad trade. The VPS only "wins" when the hours are cheap to you: you already have the skills, you enjoy the work (real, for many of us — it's how the skills got built), or the ops load amortizes across many projects on one box, which is exactly my situation and why my personal math favors the VPS.
The managed side's hidden invoice
Managed platforms have their own fine print, and it compounds:
- Egress. Free tiers and headline prices assume traffic stays small. Serve real assets or move data between providers and egress quietly becomes a top-three line item.
- Per-seat and per-feature creep. Sentry per member, monitoring per check, the platform's "team" tier to get SSO. Each bump is small; the direction is only up.
- The add-on ratchet. Production-grade Postgres needs the HA add-on, longer backup retention needs the next tier, staging doubles half the bill. The $95 estimate has a way of becoming $200 within a year without any single decision feeling wrong.
- Lock-in. The deeper you integrate proprietary queues, cron, or auth, the more expensive leaving becomes. The price you pay isn't today's bill — it's your weakened negotiating position at renewal time.
The middle path I actually recommend
For most small teams the best answer isn't either pole. It's: self-host the stateless things, pay for the stateful thing.
Run your app containers on a VPS — they're cattle; if the box dies you re-run the deploy script on a new one and you're back in fifteen minutes. But put PostgreSQL on a managed service, because the database is where irreversible mistakes live. Automated backups, PITR, patching, and failover handled by people whose whole job it is — that's the highest-value $25–60 in the stack.
# The escape-hatch test: can you leave in an afternoon?
pg_dump "$MANAGED_DATABASE_URL" -Fc -f snapshot.dump
pg_restore -d "$VPS_DATABASE_URL" --clean --if-exists snapshot.dump
That's the other principle: whatever you choose, keep the exits open. Postgres is Postgres everywhere — a pg_dump moves between RDS, Render, and your own container unchanged. Docker images run anywhere. The things to be wary of are the proprietary services with no self-hosted equivalent. If your architecture passes the "could we migrate in a weekend?" test, the self-host vs managed decision stops being scary, because it's reversible.
A decision framework by stage
| Stage | My recommendation |
|---|---|
| Side project / learning | Self-host everything. The $24 bill matters, the skills are the actual ROI, and downtime costs you nothing. |
| Pre-revenue startup | VPS for apps, managed Postgres. Your hours should go to product; your data should survive your mistakes. |
| Funded / revenue, small team | Mostly managed. You're buying focus and sleep; the delta is a rounding error against one salary. Revisit at scale. |
| Real scale, dedicated ops | The math inverts again — at thousands per month in platform fees, hiring or dedicating an engineer to run infra on bare metal or plain VMs starts paying for itself. |
The pattern: the right answer tracks whose hours are cheapest and what a failure costs, and both change as you grow. The mistake is picking a side as identity — "we're a self-hosting shop" or "we never run servers" — and keeping it after the conditions that justified it are gone.
Wrapping up
The honest numbers for a small SaaS: about $24/mo self-hosted, about $95/mo managed on the cheap end, and a gap between them that is really a bill for your hours and your 3 a.m. attention. Neither side's advocates are wrong — they're just pricing different scarce resources.
My own answer: personal projects live on a VPS because the ops cost amortizes and I enjoy it; client production databases go managed because their disaster is worth more than the delta; and everything I build keeps the escape hatches open in both directions — plain Postgres, plain Docker images, backups I've actually restored. Choose based on whose hours you're spending and what a bad night costs you. Then re-run the math once a year, because the correct answer has a shelf life.