Self-hosting on a small VPS: a RAM budget that works
Small servers rarely run out of CPU. They run out of memory, and the Linux OOM killer then picks a process to shoot, usually your database. Here's how to see where your RAM goes, how to cap it, and how to survive a spike. Not sure how much you need in the first place? The VPS size calculator adds it up for you.
1. See what's really used
free -h docker stats --no-stream
Look at the available column in free, not free. Linux fills spare RAM with disk cache and gives it back on demand, so a "full" server can be perfectly healthy. docker stats shows each container's real usage, which is where the surprises are.
2. Give every container a ceiling
One leaky container shouldn't take the whole box down. In Compose:
services:
app:
image: example/app
mem_limit: 512m
restart: unless-stopped
If the app goes over, Docker kills and restarts just that container instead of the kernel picking a victim. Set the limit to about 1.5x what docker stats shows at a normal busy time.
2b. Prefer SQLite where the app allows it
A separate PostgreSQL container is often the single biggest line item on a small server. Vaultwarden, Uptime Kuma, Forgejo and Miniflux-sized workloads run fine on SQLite or a shared database. If you need Postgres, run one instance and give each app its own database in it instead of one container per app.
3. Add swap as a safety net
Swap isn't extra RAM. It's a place for memory that's rarely touched, plus a buffer that turns a sudden crash into a slowdown. Many VPS images ship without any:
swapon --show
Empty output means none. Add a file:
sudo fallocate -l 2G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Then tell the kernel to prefer RAM and only swap when it has to:
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swappiness.conf sudo sysctl --system
If a server is swapping constantly (watch the si and so columns in vmstat 5), swap is hiding a real shortage. Buy more RAM instead.
4. Keep the logs from eating the disk
Docker's default log driver has no size limit, and a chatty container can fill a small disk. Cap it for all containers in /etc/docker/daemon.json:
{
"log-driver": "local",
"log-opts": { "max-size": "10m", "max-file": "3" }
}
sudo systemctl restart docker
The new default only applies to containers created after the change, so recreate existing ones with docker compose up -d --force-recreate.
5. Things that eat RAM and don't look like it
- Search and indexing features (Nextcloud previews, Immich machine learning). Turn them off or run them elsewhere.
- JVM apps (Minecraft, Jenkins, Elasticsearch). They grab a heap up front. Set
-Xmxon purpose. - Too many "tiny" services. Ten containers at 100 MB each is 1 GB before the OS.
When to stop tuning
If you're past about 80% of RAM at normal load, or tuning is taking more of your time than the extra memory would cost, resize. Compare plans on the VPS voucher page, and remember the codes there take a month off the first bill. Once it's stable, set up backups.