Lessons from Deploying Applications on Small VPS Instances
Memory, swap, disk and backups behave differently on a one or two gigabyte server. A checklist for keeping small machines boring and predictable.
3 min read
- vps
- linux
- self-hosting
- operations
On this page
A one or two gigabyte VPS can run a surprising amount of software. It can also fall over in ways that never happen on a laptop with plenty of headroom. Small machines punish the same few oversights again and again: no swap, logs that grow forever, builds that need more memory than the server has, and backups nobody has tested.
These are the habits that keep a small server boring.
Know your memory budget
Start by looking at what is actually free once your services are running:
free -h
ps -eo pid,comm,rss --sort=-rss | head -n 10When memory runs out, the kernel's out-of-memory killer picks a process and ends it. On a small server that is often your database or your application. Two things make this less likely.
Add a swap file
Swap is slow, but it turns a sudden crash into a slowdown you can notice and fix:
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/fstabThen tell the kernel to prefer RAM and only use swap under pressure:
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swappiness.conf
sudo sysctl --systemGive every service a ceiling
A memory limit means one misbehaving service gets restarted instead of taking the whole machine with it. With Docker Compose:
services:
app:
image: ghcr.io/example/app:latest
restart: unless-stopped
deploy:
resources:
limits:
memory: 512MFor a service managed by systemd, MemoryMax= does the same job:
[Unit]
Description=Example application
After=network-online.target
Wants=network-online.target
[Service]
User=app
WorkingDirectory=/opt/app
ExecStart=/opt/app/bin/server
Restart=on-failure
RestartSec=5
MemoryMax=512M
[Install]
WantedBy=multi-user.targetBuild somewhere else
Compiling a modern web application can need more memory than the server it will run on. Build in CI or on your own machine, ship an image or an artifact, and let the server only run things. Deploys get faster and a build can never knock the live site over.
Stop logs from filling the disk
Disks on small plans are small, and logs grow quietly. Cap the system journal with a drop-in file, which survives package upgrades better than editing the main config:
[Journal]
SystemMaxUse=200Msudo systemctl restart systemd-journaldDocker keeps container logs forever unless told otherwise. Rotate them in /etc/docker/daemon.json, then restart Docker. The setting applies to containers created after the change:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}Old images add up too. docker system prune reclaims them; read what it will delete before you confirm.
Patch automatically, reboot deliberately
Security updates should not wait for you to remember them:
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgradesKernel updates still need a reboot. Pick a quiet time and do it on purpose, rather than discovering months later that the server never restarted.
Back up, then prove it
A backup only counts once you have restored from it. Whatever tool you use, schedule the backup, send it off the machine, and practise a restore into a scratch directory now and then. Write down the restore steps while you still remember them, because you will need them on a bad day.
Watch from the outside
A process that has stopped cannot tell you it stopped. Use an external uptime check that alerts you when the site does not answer, plus a simple disk check:
#!/usr/bin/env bash
set -euo pipefail
usage=$(df --output=pcent / | tail -n 1 | tr -dc '0-9')
if [ "$usage" -ge 85 ]; then
echo "Disk usage on / is ${usage}%" >&2
exit 1
fiRun it from cron or a systemd timer and route failures to wherever you will actually see them.
The short version
| Risk | Habit |
|---|---|
| Out of memory | Swap file, low swappiness, memory limits per service |
| Heavy builds | Build in CI, deploy artifacts |
| Full disk | Capped journal, rotated container logs, pruned images |
| Unpatched system | Unattended security upgrades, planned reboots |
| Lost data | Off-site backups and a restore you have practised |
| Silent failures | External uptime checks and simple alerts |
Small servers are not fragile by nature. They are just honest about limits that bigger machines let you ignore.
End
New articles arrive in the RSS feed. Questions or corrections are welcome on the contact page.