Should Postgres Share the App's Box or Get Its Own?
Put the database on the same VPS as the app, or give it a separate box? Here is how to decide, when colocation wins, and the signals it is time to split.
Start with the database on the same box as your app, and split it out only when you have a concrete reason. Colocation is simpler, faster on latency, and cheaper, which is exactly what a new or small product needs. A separate database box earns its cost when the two workloads fight over memory, when you want to scale or back up the database independently, or when isolation for security or blast-radius reasons matters. For most side projects and early products, one box is the right call, and splitting later is straightforward.
Why colocation is the right default
When Postgres and your app run on the same VPS, they talk over the loopback interface. That is a fraction of a millisecond per query, versus a network round trip to a separate host. For an app that makes many queries per request, that latency difference adds up in real page-load terms. Colocation is genuinely faster for the common case.
It is also simpler and cheaper. One box to provision, one box to secure, one box to back up, one bill. Your Compose file has the app and the database side by side on a private network, and there is no cross-host networking to configure or firewall to poke holes in. For a product that is finding its footing, that operational simplicity is worth more than the theoretical benefits of separation. This is the same reason I argue Docker Compose is enough for a side project: fewer moving parts until you have a reason for more.
When should I put the database on its own box?
Split the database out when one of these becomes true, not before:
- Memory contention: Postgres and the app are both hungry for RAM and starving each other, so you are swapping under normal load. Giving the database dedicated memory fixes the specific bottleneck cleanly. This is often the real trigger behind when to upgrade your VPS.
- Independent scaling: the app needs more CPU but the database is fine, or vice versa. Separate boxes let you size each to its own demand instead of buying one oversized machine to satisfy the greedier of the two.
- Blast radius: you want a compromised or crashing app to be unable to touch the database host directly. Separation is a real security and reliability boundary.
- Backup and maintenance windows: you want to snapshot, upgrade, or tune the database without touching the app, and vice versa.
If none of these apply, you do not have a reason to split yet. Splitting for a future you might have is how simple products acquire complexity they never needed.
The colocation risks to manage
Running them together does introduce shared-fate risks, and you should manage them rather than ignore them. The big one is resource contention: a runaway app process should not be able to starve the database of memory and take it down. Cap each container's memory so neither can consume the whole box. Tune Postgres for the RAM you have actually allotted it, not the box total, so it does not assume memory the app is using.
The other risk is that a full box loss takes both down at once. That is why backups have to leave the machine. Whether colocated or separate, your database backups go to object storage off the box, tested with real restores, the way I cover in own your backups when you self-host. Colocation is a fine default precisely because these risks are cheap to mitigate with resource limits and off-box backups.
Splitting later is not a rewrite
People over-provision early because they fear that separating the database later will be painful. It is not. Because the app talks to Postgres over a connection string, moving the database to its own host is mostly: stand up a new box, restore the database onto it, update the connection string, and point the app at the private network address of the new host. Your application code does not change. The tradeoff between owning versus offloading the database entirely is a separate question I cover in own your database or use managed, but moving between your own boxes is simple.
So start colocated. Run the app and Postgres on one HostSSH box, cap their memory, back the data off the box, and ship. Watch for memory contention and independent scaling needs, and when a real one shows up, split the database onto its own VPS in an afternoon. Decide on evidence, not on a fear of a migration that turns out to be easy.