Self-Hosting·7 min read·

Resource Budgeting for Many Containers on One Box

Budget RAM and CPU across a crowded Docker host with explicit per-service limits and visibility so one runaway container never starves the rest.

NB

Netbay Cloud Team

Netbay Engineering

On this page

A homelab fills a machine faster than its specs suggest. Each container loads fine in isolation, but together they compete for the same scarce RAM and CPU, and the first casualty of memory pressure is usually the kernel OOM killer quietly taking out something you care about. Resource budgeting turns that chaos into a plan: you know the ceiling, you give each service a fair share and a hard cap, and you can see, at a glance, who is consuming the budget. The two tools that matter are cgroup limits and the accounting that tells you whether the plan is working.

Divide a fixed budget fairly HOST: 4 CPU / 8 GB RAM proxy 1 CPU app 1.5 CPU db 1 CPU headroom SET LIMITS cap runaway processes WATCH TRENDS catch leaks before OOM

Define the Allocation First

Before adding a single container, sketch the budget on paper: how much CPU and RAM can this host offer, and what does each service reasonably need? A database wanting headroom looks very different from a lightweight dashboard. The plan does not need to be perfect; it needs to exist, because it tells you when to say no to the next shiny container. A common failure is adding services until swap thrashes; a budget makes the trade-off explicit.

Enforce With Compose Limits

Once you have the plan, encode it. Docker Compose lets you set soft reservations and hard limits per service. Reservations guarantee a floor when resources are free; limits are the contract that protects everyone else when a service misbehaves.

yaml
services:
  db:
    image: postgres:16
    mem_limit: 2g
    cpus: "1.0"
    restart: unless-stopped
    volumes:
      - pgdata:/var/lib/postgresql/data
  web:
    image: some/web-app:latest
    mem_limit: 1g
    cpus: "0.5"
    depends_on:
      - db

Postgres is told it may use up to one full CPU and two gigabytes before the cgroup throttles or OOM-kills it, while the web app is capped lower. A runaway memory leak in one container can no longer drag the whole host down.

Limits stop the catastrophe, but they do not tell you your plan is wrong. Sustained high utilization on many services means the budget itself needs revisiting — add memory, drop a container, or give a service more of the pie. Monitor with host-level tools and container stats to see who creeps toward its cap over days, not who is spiking this minute.

bash
docker stats --no-stream --format   'table {{.Name}}	{{.MemUsage}}	{{.CPUPerc}}'

docker stats is the fastest way to answer "what is eating the box right now." The habit is to capture it on a schedule or wire it into your Grafana/Prometheus setup so you can see memory curve upward over weeks — the classic sign of a leak that limits alone will not surface.

The OOM Fallback

Even with limits, a misconfigured container can force the kernel's OOM killer, and the kernel picks a victim that may not be your first choice. Give the kernel a hint by reordering priority where it matters, and always run the important services with restart policies so a kill becomes a restart rather than an outage.

Budget CPU, Not Just Memory

Memory dominates the conversation, but CPU budgets matter just as much on a busy host. A single container spinning at 100% of one core can make every other service feel sluggish, because they are all competing in the same scheduler. The answer is the same accounting with a different unit: give each service a CPU share according to how compute-hungry it is, and leave headroom so interactive work never starves.

yaml
services:
  render:
    image: some/renderer:latest
    cpus: "1.5"
    mem_limit: 2g
  api:
    image: some/api:latest
    cpus: "0.5"
    mem_limit: 1g

Here the comprehension-heavy renderer gets a larger CPU reservation and cap, while the lightweight API is throttled to half a core. When the renderer spikes, the API still gets its share instead of being eaten alive. The discipline of a CPU budget makes multi-tasking predictable: you can add the next container with confidence because you already know what fraction of the machine it may consume.

Leave Honest Headroom

A fully allocated host is a fragile host. Budgetting the last available core or the last half-gigabyte leaves no room for reclaiming a process or absorbing a request burst, and it forces the OOM killer to act far too often. The rule of thumb is to keep a meaningful slice of memory and CPU unallocated — it is the buffer between "busy but fine" and "thrashing." If your budget cannot fit every service with headroom to spare, the honest move is to run fewer things or move the greedy one elsewhere.

Takeaway

A crowded box stays fast and predictable when you set a budget, encode it as per-service limits, and watch the trend lines instead of reacting to crashes. Limits turn one bad container from a host-wide outage into a contained hiccup you can ignore until you fix the root cause. Run that budgeted stack on a Netbay Lucknow VPS with enough RAM for headroom, and scale the plan up when you need it — provision at netbayhosts.in.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance