Document the 6vCPU/8GB Tier 3 sizing plan

Settled stack: 3x Mattermost, 1x Traccar (down from 2x to buy back RAM),
2x each of ntfy/mealie/wordpress/actualbudget/audiobookshelf/emby
(music-only + everything)/filebrowser/fmd/homebox/joplin/rustdesk/
vaultwarden, 1x each of asterisk/security-dashboard/sms-inbound (all
three are singleton-by-design, no multi-instance support exists for
them). changedetection and magicmirror x6 dropped — the former never got
the full multi-instance retrofit, the latter's existing pattern caps at
3 instances.

Comes out to ~6.0GB of 8GB (~25% headroom) with RustDesk's relay for
screen sharing, or ~6.4GB (~20% headroom) with MeshCentral instead.
This commit is contained in:
Claude
2026-08-10 00:09:49 +00:00
parent 9e06ed4b83
commit 2fb2a2980d
+112
View File
@@ -210,3 +210,115 @@ uncertainty, and they're stacked
close enough to the ceiling that it's worth confirming. If real usage runs close enough to the ceiling that it's worth confirming. If real usage runs
higher than estimated, the two Mattermost instances (~1.2GB combined) are higher than estimated, the two Mattermost instances (~1.2GB combined) are
the single biggest lever to reconsider. the single biggest lever to reconsider.
## Tier 3 — 6 vCores / 8GB RAM / 240GB NVMe
Example: IONOS VPS L+, $21/mo.
## What I was planning for the 6vCPU / 8GB / 240GB box
A much larger multi-instance spread than the Tier 2 box, sized against this
repo's multi-instance retrofit (`services/*.sh` instance-selection pattern,
see `CLAUDE.md`'s "Multi-instance services" section) plus its port-collision
scanning (`CLAUDE.md`'s "Port collision avoidance" section). The instance
counts below aren't arbitrary — a few of them ran into real limits in the
codebase as it exists today, not just RAM ceilings.
**Not actually possible as "2x" — singleton-by-design, no multi-instance
support exists:**
- `asterisk` — one PBX per box. Fixed SIP port 5060, one AMI, one
DigitalOcean-layout detection, one extension plan. A second instance
would fight the first over the same SIP/RTP ports; there's no
instance-suffix logic to make that not collide.
- `security-dashboard` — runs natively (not Docker), reads *one* Asterisk's
security log and talks to *one* CrowdSec via `cscli`. There's no second
log/CrowdSec on the box to point a second copy at.
- `sms-inbound` — also native, tied to one Asterisk + one pstn-trunk's
DID-ownership mapping. Same story as security-dashboard.
All three stay at **1x** on this box regardless of how the rest of the
stack is sized.
**Dropped from this plan, not attempted:**
- `changedetection` × 2 — only got the port-collision-scanning pass this
session, not the full multi-instance retrofit (no directory-suffix
logic), so a second install would collide with the first's
`~/docker/changedetection`. It also runs a dedicated Playwright/Chrome
sidecar per instance (~300-500MB each) — expensive for what would be a
head-to-head with retrofitting it.
- `magicmirror` × 6 — its existing multi-instance pattern (a different,
earlier design than the rest of the repo: upfront instance count, not
incremental) is hardcoded to 1-3 instances. Six would need a code change,
not just a bigger box, and 6 dashboard instances (~900MB) was a lot of
RAM for the value versus the rest of this stack.
**Screen-sharing tool — RustDesk over MeshCentral.** Both `services/rustdesk.sh`
and `services/meshcentral.sh` have real multi-instance support. RustDesk's
relay (`hbbs`/`hbbr`) is a lightweight Rust binary that doesn't decode video
itself — screen-share traffic goes peer-to-peer between clients, the relay
just proxies — so it costs ~40MB per instance versus MeshCentral's full
Node.js app per instance (~150-250MB). Chose RustDesk for the RAM headroom;
MeshCentral remains the pick if agent-based remote management (not just
screen viewing) matters more than the RAM difference.
**Everything else scales the same way it does on the Tier 2 box** — same
shared `coturn`, same NetBird base install, same dedicated-per-instance
database pattern (Traccar's own datastore, Joplin/WordPress's dedicated
Postgres/MariaDB per instance, per CLAUDE.md's backup-isolation reasoning).
`traccar` settled at **1x** (not 2x) specifically to buy back RAM — JVM
apps don't shrink well when duplicated (~425MB is mostly fixed heap
overhead, not data-proportional), so a second instance was the highest-RAM,
lowest-value item once Mattermost stayed at 3x.
**Emby** stays music-only-plus-everything (2 instances, one restricted to a
Music library per CLAUDE.md's per-user-access pattern, one unrestricted) —
idle RAM only; if the "everything" instance serves remote clients that
can't direct-play, transcoding adds real CPU plus ~200-400MB per active
session, not counted in the idle table below.
**No automatic swapfile on this box.** `ensure_swapfile()` only offers one
when RAM ≤4096MB — an 8GB box doesn't qualify, so unlike every Tier 1/2 box
in this doc, this one has no swap unless it's added by hand.
## Final RAM budget for the 6vCPU/8GB box (settled baseline, idle)
| Service | Count | ~RAM |
|---|---|---|
| OS + Docker baseline (~45-50 containers across all instances) | — | ~500MB |
| Caddy | — | ~30MB |
| CrowdSec | — | ~150MB |
| coturn (shared) | — | ~40MB |
| NetBird client | — | ~35MB |
| Asterisk | 1 (singleton) | ~100MB |
| security-dashboard + sms-inbound | 1 each (singleton) | ~95MB |
| Mattermost (app+Postgres each) | 3 | ~1800MB |
| Traccar (JVM) | 1 | ~425MB |
| ntfy | 2 | ~100MB |
| Mealie | 2 | ~350MB |
| WordPress (dedicated MariaDB/site) | 2 sites | ~400MB |
| Actual Budget | 2 | ~140MB |
| Audiobookshelf | 2 | ~400MB |
| Emby (music-only + everything) | 2 | ~450MB idle* |
| Filebrowser | 2 | ~80MB |
| FMD | 2 | ~100MB |
| Homebox | 2 | ~250MB |
| Joplin (app + dedicated Postgres each) | 2 | ~400MB |
| RustDesk relay | 2 | ~80MB |
| Vaultwarden | 2 | ~80MB |
| **Total** | | **~6.0GB** |
\* idle only — see the Emby note above for active-transcode cost, not
included here.
Leaves roughly **~2.0GB headroom (~25%)** out of 8GB — right at the bottom
of the ideal 25-30% range from the sizing rules of thumb above. Swapping
`rustdesk` for `meshcentral` narrows that to ~1.6GB (~20%) — still
workable, just tighter. CPU isn't expected to bind here (no
transcoding/AI/gaming load in this mix per the "How to size" rule of
thumb above), but 3 concurrent Mattermost calls, active Asterisk calls,
and a RustDesk/MeshCentral session all at once is the realistic worst
case worth watching, not the idle numbers in this table. Deploy
incrementally and check `free -h` / `docker stats` against this table the
same way as the Tier 2 box — if real usage runs higher than estimated,
Mattermost (~1.8GB across 3 instances) is again the single biggest lever
to reconsider.