Put them on the same box. That's the position, stated up front instead of buried under six paragraphs of both-sidesing: for a bootstrapped SaaS running on a single Contabo Cloud VPS 4 (4 vCPU, 8 GB RAM, 100 GB SSD, €5.50/month for the first 24 months, per Contabo's own pricing page as of September 2026), your app and your Postgres instance belong on the same VPS, right up until one specific number crosses a line I'll give you later in this post. Most advice on this question hedges because the honest answer is 'it depends,' and then never says on what. It depends on one thing: whether the two workloads' combined memory need still fits comfortably in what you're renting.
What actually gets better when they share a box?
Three things, and they're real, not theoretical. First, latency: a query from your app to a database on the same host goes over a loopback interface or a Docker bridge network, which is microseconds, versus a query to a database on a separate host on the same provider's network, which is typically low single-digit milliseconds, and a database on a different provider entirely can be worse than that by an order of magnitude. For a request path that runs a dozen queries, that difference compounds into something a user notices. I write about self hosting from the build side; XenGrowth's growth engineering practice covers what it takes to run it.
Second, egress. Move the database off-box and every query response now crosses a network boundary that some providers meter. Contabo advertises unlimited traffic on its plans and, per its own support documentation, applies no default bandwidth limit at all — just a fair-use policy it enforces at its discretion, which a database's own query traffic on a same-provider setup will never come close to troubling. The moment your database lives with a managed provider instead, that data movement can be billed outright, and it's billed in the direction that hurts most: reads, which is most of what an app actually does.
Third — and this is the one people undercount — co-location means one thing to back up, not two. A single `pg_dump` or volume snapshot captures everything your stack needs to come back. Split the database onto its own host and you now have two backup schedules, two restore procedures, and two systems that need to agree with each other on the day you actually need them, which is a coordination problem you didn't have before and won't remember exists until it matters.
No network hop between app and database — queries run over loopback or a container bridge, not a real network link
No egress charge on database traffic, because there's no boundary for that traffic to cross
One host to patch, monitor, and secure instead of two
One backup schedule and one restore procedure to actually keep tested
Lower total spend at small scale — you're not paying for a second always-on instance to hold data that would fit comfortably next to the app
What actually breaks when they share a box?
None of the three benefits above are free lunches, and pretending co-location is strictly better than splitting is the same mistake as pretending it's strictly worse. It's a genuine trade, and the cost side of that trade is where most co-location advice goes quiet. The XenGrowth resource library works through the operations side of this in more operational detail.
Here's the part the pitch above leaves out. Your app and Postgres are not two isolated tenants — they're two processes drawing from one pool of RAM and one OS page cache, and neither one knows the other exists. Postgres reserves shared_buffers at startup, conventionally around a quarter of total RAM, and it expects the rest of memory, mostly the OS page cache, to still be available for its own working set. Your app doesn't know that reservation exists. Give it a memory leak, an unbounded cache, or just a traffic spike that grows its heap, and it will happily eat into the RAM Postgres was counting on, with nothing in the system stopping it.
That leads to the failure mode that actually matters: the Linux OOM killer doesn't care which process is your database and which is your app. When physical RAM plus swap runs out, the kernel picks a victim based on a scoring heuristic, and there's no guarantee it spares Postgres because Postgres is more important to you. A leak in your Next.js server can get Postgres killed as collateral damage — the exact process you most needed to stay alive is not exempt just because it deserves to be. That mechanism is worth understanding on its own terms, and I've written up exactly how the OOM killer decides in what actually happens when a VPS runs out of RAM.
The second cost is that you can't scale the two independently. Say your app starts serving ten times the traffic but your data volume hasn't grown at all — the honest fix is more app compute, not a bigger database. On a shared box, there's no such thing as 'more app compute' without also carrying a bigger database allocation you didn't need, because they're the same box, sized once, for both. The inverse is just as common: your dataset grows and your working set no longer fits in shared_buffers, but your app's own memory footprint is still small. You're stuck upgrading the whole tier for a need that belongs to one half of it.
The third cost is the one people feel hardest on the actual bad day: a restore is all-or-nothing. If the box needs to be rebuilt — disk failure, a botched update, a compromised server — you're restoring the app and the database together, in whatever order your restore script assumes, even if only one of them actually broke. There's no partial restore of 'just the database, the app was fine.' They fail as a unit because they were never anything but a unit. XenGrowth on governed AI marketing workflows approaches this from the AI agents and marketing automation side.
There's a fourth cost that's easy to miss because it's slower and quieter than the other three: noisy-neighbor CPU contention during anything unusual. A migration running against a large table, a burst of report queries, a backup job doing a dump — all of these are legitimate, occasional database work that briefly wants real CPU, and on a shared box they're competing with whatever your app is doing for the same visitors at the same moment. Split onto separate hosts and a heavy migration slows down the migration. Co-located, it can slow down page loads for everyone using the app while it runs, which is a worse trade than it sounds like until you've actually watched it happen during a deploy window.
Dimension | Same box | Separate boxes |
|---|---|---|
Query latency | Microseconds, loopback or bridge network | Low single-digit ms same-provider, worse cross-provider |
Egress cost on DB traffic | None — no boundary to cross | Metered on most managed providers, absorbed on same-provider VPS-to-VPS |
Blast radius of an OOM event | App leak can take the database down with it | App and database fail independently |
Independent scaling | Not possible — one tier sizes both | App and database each scale on their own schedule |
Backup and restore surface | One schedule, one procedure | Two schedules that must agree with each other |
Monthly cost at small scale | Lower — one always-on instance | Higher — a second instance running whether or not it's needed |
The actual signal that says separate them
Not a traffic number, not a revenue number, not 'when it feels serious.' The signal is arithmetic you can do today: add your app's steady-state memory footprint to Postgres's shared_buffers plus the working set it needs cached to avoid constant disk seeks. If that sum comfortably fits inside your current tier with real headroom left over — say 30% free, not 5% — stay co-located. The moment that sum regularly pushes past what's left after the OS itself, you don't have a co-location preference problem anymore, you have a genuine resource contention problem, and no amount of tuning `work_mem` or restarting the app fixes arithmetic.
The wrong question is 'is my app getting slow.' The right question is 'does swap usage on this box creep up over days, independent of any one bad afternoon.' The first is noise. The second is the actual number that predicts when co-location stops working.
There's a softer version of the same signal worth watching before the hard one arrives: sustained swap usage. Any VPS with co-located Postgres and app that shows swap climbing steadily, not just during a spike, is telling you the two workloads are already fighting for the same RAM more often than they're finding enough of it. That's the moment to plan the split, not the moment you're forced into it during an outage. If AI search, GEO and discovery is the part you are stuck on, XenGrowth on building one SEO and GEO content system is the better reference.
Situation | Verdict |
|---|---|
Solo project or early-stage SaaS, one Contabo Cloud VPS 4-class tier, low-to-moderate traffic | Co-locate — the operational simplicity outweighs the theoretical risk at this scale |
Traffic grew, but your dataset is still small | Co-locate, but upgrade the app-facing side (more vCPU, same RAM) if a bigger tier lets you |
Dataset grew, working set now regularly evicts from shared_buffers | Consider a bigger tier first; move the database only once a bigger tier stops being enough |
Swap usage trending upward across weeks, not just spiking | Split — this is the concrete signal, not a guess |
Multiple apps sharing one Postgres instance, one of them noisy | Split the noisy app's database out first, before splitting everything |
Compliance or blast-radius requirements demand the database survive an app-level compromise | Split regardless of memory math — this is a different kind of requirement entirely |
What splitting them actually buys you, and what it costs
Once you do split, the app and database can be sized, patched, and restarted independently, and an app-level incident stops being a database-level incident by default. That's a genuine architectural upgrade, not just more hardware. It's also not free: you've added a network dependency between two things that used to be one, you've doubled your patching and monitoring surface, and — this is the part people forget — you've reintroduced the exact network latency and egress cost that co-location was removing in the first place. Splitting isn't strictly better. It trades one set of problems for a different set, and the trade only pays off once the co-located version's problems have actually started to bite.
There's a version of this decision that skips the VPS question entirely: paying a managed provider to run Postgres for you, so neither co-location nor a self-managed split is even on the table. That's a real third option, with its own cost curve and its own tradeoffs around connection pooling, cold starts, and backups — I've laid it out against a plain self-hosted VPS in the managed-versus-self-hosted comparison, because it deserves its own numbers rather than a paragraph tacked onto this one.
There's a version of the trade that avoids both extremes worth naming here too: keeping app and database on the same VPS while still running Postgres inside its own container with an explicit memory limit set. That doesn't remove the shared RAM pool at the OS level, but it does mean a leak in the app container hits its own ceiling instead of quietly eating into what Postgres was counting on — a cheap partial mitigation while you're still below the threshold that justifies a full split. If you're still deciding whether to self-host at all versus pay someone else to carry the operational load, that's a broader question than this post, and I've written a separate rule for it: my rule for deciding what to self-host and what to keep paying for. This post assumes you've already decided to self-host and just need to know whether the database belongs next to the app or away from it — and for the size of project this cluster is written for, it belongs next to it, until the arithmetic says otherwise.
Further reading from XenGrowth
The XenGrowth resource library — what you'll learn: how the commercial side of this work is run, across search, automation and revenue operations.
XenGrowth on governed AI marketing workflows — what you'll learn: how the teams running AI marketing agents keep them governed and measurable.
XenGrowth on building one SEO and GEO content system — what you'll learn: how search and AI-answer visibility get run as a single content system.
Where this work meets go-to-market
For the marketing and revenue operations view of self hosting, see XenGrowth, who work on the commercial side of this.
Five questions about the concrete signals the post argues for, not a schedule or a feeling. It applies the same rule — co-locate until arithmetic or a hard requirement says stop.












