Self-Hosting on a VPS: The Stack We Run for Client Projects
Every project needs a home. Managed platforms get you running in an afternoon and charge you for it every month, while a raw VPS gives you full control and demands that you know what you're doing. For most of the web apps we build, a single well-configured VPS is the sweet spot: predictable cost, full access to logs and databases, and no platform limits hiding behind a pricing page. But "just get a server" is not a plan — so here's the stack we standardize on and the reasoning behind each piece.
The standard stack
We keep the stack boring and identical across projects, because identical means the second server takes an hour, not a day:
- Ubuntu LTS or Debian stable — whichever the team knows cold. No exotic distros, no rolling releases on production.
- Docker + Compose for every application workload. The app, its workers, and its scheduler run in containers; we never install a language runtime directly on the host.
- Nginx as the edge — TLS termination, HTTP/2, gzip or brotli, and clean separation between "internet" and "the containers". Certificates from Let's Encrypt, renewed automatically.
- PostgreSQL in a container with a bind-mounted volume for data and a separate backup user with minimal rights.
- Redis for cache and queues — one instance, separate logical databases, never exposed to the internet.
- Non-root everywhere. Services run as unprivileged users, SSH is key-only, the firewall allows exactly three ports: 22, 80, 443.
What we deliberately don't do
Restraint matters more than tooling on a small server:
- No Kubernetes. Compose handles restart policies, health checks, and multi-container wiring for anything up to a handful of services. Kubernetes on a single VPS is operational overhead without a payoff.
- No panel software. Control panels add attack surface and hide the actual configuration. Everything lives in a
compose.ymland a few config files, all in version control. - No hand-edited state. If a change isn't in the repository, it doesn't exist. A server you can rebuild from the repo plus a backup is a server you can't lose.
Sizing and density: what fits on one box
A typical SaaS MVP — app container, queue workers, scheduler, PostgreSQL, Redis, Nginx — runs comfortably on 2 vCPUs and 4 GB of RAM. Adding a second or third small client project to the same box is usually fine: containers are isolated, traffic is modest, and the monitoring tells you when density stops being free.
The line we watch is not CPU — it's memory and blast radius. When one box hosts several unrelated clients, a memory-hungry deployment can starve a neighbor, and a misconfigured cron job can fill the disk for everyone. The rules we apply:
- Every container gets an explicit memory limit.
- Disk usage is monitored with alerts before the disk is full, not after.
- Separate unrelated clients onto separate boxes as soon as one of them becomes a "real" business — the cost of a small VPS is nothing next to the cost of a shared incident.
The boring habits that keep it alive
A VPS is only as reliable as the routine attached to it. Ours fits on a sticky note:
- Unattended security updates enabled on the host; container images rebuilt weekly, not "when someone remembers".
- Automated database dumps shipped off the server the moment they're created — a backup stored on the same disk as the database is not a backup.
- Log rotation configured from day one. An unrotated log file is the most common way a perfectly healthy server dies of a full disk.
- A health-check endpoint per app, wired into uptime monitoring, so a dead container is paged before a client notices.
When to move off the box
Self-hosting has a ceiling, and it's important to recognize it honestly. The signs that a workload has outgrown a single VPS: backups take longer than the maintenance window, a single deployment visibly affects other tenants, or the app needs more than one machine for availability reasons. At that point the answer is usually not "more servers run by hand" — it's moving the database to a managed service first (backups, failover, and point-in-time recovery are the parts you should never assemble yourself), and then splitting app tiers across boxes with a load balancer.
For everything below that line, a disciplined VPS setup is cheaper, faster to change, and completely under your control. The stack isn't clever — and that's exactly why it works.