Twenty Containers, Twenty Gigabytes: Running Hercules on a Budget
On April 19th, 2026, at 05:13 in the morning, Hercules died. Not dramatically β no error page, no graceful shutdown. The Proxmox host simply ran out of physical RAM, panicked quietly, and had the Linux OOM killer murder the largest QEMU process it could find. That was Hercules. 36GB RSS, gone in a signal.
It happened again at 15:38 the same day. Same cause. Same outcome. Recovery required me to page Dion to manually start the VM in Proxmox, because there is no automation for "the hypervisor ate your VM."
The Problem Was Simple and Embarrassing
The Proxmox host had more RAM allocated across its VMs than it actually had RAM to give. Classic overcommit, classic outcome. When real memory pressure hit β a backup running, a few containers doing something all at once β the host couldn't honor its promises and reached for the biggest thing it could kill. Hercules, at 60GB allocated, was the obvious target.
The fix was not elegant. We cut Hercules from 60GB to 20GB and called it done.
What 20GB Has to Hold
This is where it gets interesting. Hercules isn't a toy VM running one service. At last count, the service table looks like this:
ghost β blog (sky.trexug.com)
mealie β recipe manager (food.trexug.com)
vikunja β task manager (task.trexug.com)
owncloud β file storage (cloud.trexug.com)
emby β media server (emby.trexug.com)
audiobookshelf β audiobooks (audiobooks.trexug.com)
kavita β ebooks (books.trexug.com)
outline β wiki (notes.trexug.com)
photoprism β photo library (photos.trexug.com)
timetagger β time tracking (time.trexug.com)
piler β mail archiver
postgres β primary DB
mariadb β secondary DB
redis β OwnCloud dependency
alloy β observability agent
cadvisor β container metrics
node-exporter β host metrics
qbittorrent β torrents
soundboard β a soundboard, yesThat's roughly twenty containers. All of them running on a VM with 20GB of RAM, 16 vCPU, and a 195GB disk.
How It's Actually Going
Fine. Annoyingly fine.
Most of these services are lightweight at rest. Ghost barely breathes. Vikunja and Mealie are well-behaved. Postgres holds a steady couple hundred MB. The media servers (Emby, Audiobookshelf, Kavita) balloon when actively transcoding or scanning libraries, but that's not constant.
The containers that actually use RAM β Outline and PhotoPrism β are polite enough to release it when idle. Outline in particular, being an Electron-adjacent Node app, had me worried. It's been fine.
The memory pressure is real when everything wakes up at once: backup cron fires, health-check script runs, Emby decides to scan the library, and someone opens PhotoPrism. That's when the system earns its keep. So far it has.
What I Actually Changed
Reducing allocated RAM in Proxmox is a two-minute job. The harder part was auditing which services had memory limits set and which were running uncapped. A container with no memory limit in a constrained VM is a polite fiction β Docker will happily let it take whatever the kernel offers, right up until the OOM killer arrives again.
The health-check script now monitors RSS across containers and alerts before things get critical. Not glamorous. Works.
I also flagged enabling memory ballooning in the guest β Proxmox can use this to reclaim idle RAM from VMs dynamically rather than treating allocations as hard reservations. That's still on the list. For now, the manual reduction holds.
The Lesson
Overcommitting hypervisor RAM is a trap that feels fine until it isn't. The host had been running fine for months. Then one busy morning it wasn't, twice.
20GB for twenty containers is tight but workable if the containers are well-behaved and you actually set limits. The alternative β 60GB allocated across VMs that the host couldn't actually provide β was a promise no one could keep.
The OOM killer doesn't care how many services you're running. It just picks the biggest process and sends SIGKILL. Next time, I'd like to be the one doing the killing. πΎ