Skip to content

Resources & scaling

Everything here is optional — an app runs as a single always-on instance with no limits by default.

homeport.yaml
resources:
memory: 512M # hard cap; throttled at 90%, OOM-killed at 100%
cpu: 150% # 150% = 1.5 cores

These become systemd cgroup directives (MemoryMax / MemoryHigh / CPUQuota) — the same kernel mechanism Docker uses for --memory / --cpus. Omit the block for no limits. homeport stats shows the cap next to live usage.

homeport.yaml
idle: true
idle_timeout: 5m # default 5m

For low-traffic apps. systemd holds the app’s port and starts the binary on the first request, then stops it after idle_timeout of no traffic — zero RAM while asleep, so a box can hold far more mostly-idle apps. Implemented with systemd socket activation + systemd-socket-proxyd, so it works for any binary.

The first request after idle pays the cold-start (fast for Go, ~1s for a big JS-framework binary).

homeport.yaml
replicas: 3

Runs N instances (systemd template units), load-balanced by Caddy (least_conn

  • passive health checks that pull a dead replica out). Deploys become rolling and zero-downtime: instances restart one at a time, health-checked, while the others keep serving.

Works for public and internal apps alike — a public app is balanced behind its domain; an internal app is balanced on loopback, so callers keep using 127.0.0.1:<port> but get HA and more cores. Size replicas to the box’s cores; they mainly help single-process runtimes (Node/Bun) use more cores. Mutually exclusive with idle.

A single-instance public app deploys blue/green by default — no config. The new release starts on a private port while the old one keeps serving; once it passes its health check, Caddy flips to it and the old instance retires. Traffic never drops; an unhealthy release never gets the flip (verified: 0/400 requests dropped across a live flip).

Two instances coexist for a few seconds during each deploy — fine for stateless web apps, but a singleton (holds an exclusive lock, or a scheduler that must never double-run) can’t tolerate it:

homeport.yaml
strategy: recreate # restart in place (brief downtime, never 2 at once)

Multi-instance apps already deploy rolling; internal and path-mounted single-instance apps restart in place (give them replicas: 2 for zero-downtime); idle apps use socket activation.

homeport.yaml
autoscale:
min: 1
max: 4
target_cpu: 70 # scale up above, down below; default 70

A systemd timer samples each replica’s CPU every 20s and adjusts the running count between min and max, with hysteresis and a 60s cooldown so it can’t flap. Scales on CPU, not memory — replicas add cores, so CPU is the signal scaling actually relieves. Most useful for single-process runtimes (Node/Bun): idle runs min replicas, load bursts to max. Ceiling is the box’s cores. Mutually exclusive with idle and fixed replicas.