Measured on demo-felhom (N100, 16 GB): 61 apps deployed and removed through the product, sampled every 5 s; heavy apps with one real action (Immich 200 photos, Jellyfin transcode, Paperless OCR, Nextcloud 500 MB, ...). sizing.json for the estimator (tools/build_sizing.py), SIZING.md with the proposed minimum machine, METHOD.md. Power is RAPL CPU-package only (no meter). R-993 asks the operator to approve the minimum machine; R-994 lists catalog limits/requests that are too low; R-995 records leftover unnamed volumes after removal. R-231's dated check measured: dooplex-backup.service success, journal green.
6.5 KiB
Method — sizing bench, 2026-10-13
Bench: demo-felhom — Intel N100 (4 cores), 16 GB RAM, 512 GB SATA SSD + 1 TB HDD, Proxmox VE; guest 9201
(3 cores, 12 GiB) running controller 0.309.0. Tier 0 (runbooks/target-selection.md). Baselines: felhom.eu
c2a6cf7a58, app-catalog 8ee687e544, felhom-controller 1b2f74c52f.
Sources, in the spec's order
| # | Source | What it is | Where |
|---|---|---|---|
| 1 | telemetry | hub App Telemetry, last 30 days, one row per 15-min report; app = sum of its containers. Used when one box has ≥ 7 days | evidence/telemetry-30d.json (from a hub DB copy with -wal, freshness proven: newest host report 2 min old) |
| 2 | onboarding | measured peaks from earlier onboarding/bench sessions; per run, the app's containers summed, anon basis only | evidence/onboarding-peaks.json (collected by a read-only helper; every figure cites its file:line) |
| 3 | measured | this run, on the N100 | evidence/<batch>/ |
| 4 | unmeasured | catalog mem_request / mem_limit |
.felhom.yml |
Telemetry under 7 days is used only when nothing better exists (short-telemetry). When peaks from two sources differ by
more than 50 %, both are kept (evidence/apps-table.json) and the higher goes into sizing.json.
Telemetry is mostly demo and test boxes at rest (demo-hp, tester-1, demo-felhom): it shows idle use, not household
use. Its peak is the highest of 1-minute docker stats samples, not the true peak. Its CPU is docker's per-core
percent; divided by 4 here to approximate the N100 scale (other CPUs, approximate).
The bench measurement
- Deploy through the product: the endpoints the dashboard calls (
GET /api/stacks/<n>/deploy-fields,POST /api/stacks/<n>/deploy), the box's own login + CSRF. The data location: the box has no registered drive, and the deploy page then says „Nincs regisztrált adattároló — add meg kézzel az útvonalat”; we typed/mnt/sizing-bench(the guest system disk — the controller container sees only/mnt). Removed through the product (stop,removewith data and backups); the product keeps data it cannot resolve (R-442), so the folder was then deleted by name. Every removal checked: no stack, no container, no volume (evidence/*/run.log). - Sampling every 5 s from each container's cgroup v2 files in the guest (
tools/guest_sampler.sh), and on the host (tools/host_sampler.sh): memory, RAPL package energy, CPU temperature. - Memory, two numbers:
used=memory.current − inactive_file(whatdocker statsand the fleet telemetry show) andanon(the app's own heap).sizing.jsonusesused. The kernel's own high-water mark (memory.peak, includes page cache) is recorded too — it is the figure closest to what a container limit is measured against. - Windows: work = from the app's deploy to the end of its rest; rest = the last 15 minutes, nothing deploying. Typical = average of rest; peak = highest 5-s sample of work.
- Batches: heavy apps alone (deploy → one real action → 15 min rest). Light apps 6–7 per batch, deployed one after another, then 15 min rest together. Per-container numbers are exact either way; host power in a batch is shared.
- CPU:
usage_usecdeltas, summed per app, as % of the whole N100 (4 cores = 100 %).cpu_peak_1m= the busiest 60 seconds. Classes: light — rest < 1 % and busiest minute < 15 %; heavy-in-bursts — busiest minute ≥ 50 % (two cores fully busy for a minute); medium — between. - The floor: 15 min with only the platform running. One app (opengist, ~23 MB) was already deployed there and was left alone; it is excluded from the platform figures.
Actions (content: generated or CC0; no personal data — tools/make_content.py)
| App | Action |
|---|---|
| Immich | first user; upload 200 photos (140 generated 12 MP, 60 CC0 portrait paintings from The Met — faces without living people); wait until every job queue is empty |
| Jellyfin | startup wizard; library of three generated 1080p H.264 films; scan; one forced transcode (H.264 3 Mbit/s, CPU — no GPU passed through) |
| Paperless-ngx | 20 image-only A4 scans at 300 dpi through the API; wait until all are OCR'd |
| Nextcloud | first start; 50 × 10 MiB over WebDAV |
| Home Assistant | first start; onboarding (first user) |
| Calibre-Web Automated | 20 generated EPUBs into the ingest folder |
| RomM | 15 dummy files copied into the library; the scan was not triggered (it needs a websocket client) |
| Mealie, Docmost, AdventureLog | first start (inside their batch) |
Power
RAPL package energy on the host throughout (evidence/*/host.tsv): CPU package only — not wall power. The wall
reading is the operator's STOP (§ power in SIZING.md).
Limits
- One box, one run per app, 15 min at rest — a household's long tail (a nightly backup, a library of 50,000 photos, several users) is not in these numbers. The Immich import is 200 photos, not a family archive.
- The batch neighbours share CPU during first start; a burst can be slower (and lower) than alone.
- Apps that need an account or key to do work (Usenet, the *arr set, Plex sign-in) were measured at rest only.
- The bench path is on the guest system disk; on a real box the data sits on a separate drive — no effect on memory.
Mixing bases — a known softness
Onboarding peaks are anon (the app's own heap); telemetry and this bench are used (anon + active file cache).
used is usually the larger. Where onboarding is the chosen source, its peak can read low — the >50 % rule then lifts it
to the bench figure (e.g. Immich, Grimmory, Grafana); within 50 % it does not.
Teardown — three layers
- Machine (guest 9201): all 61 bench deploys removed through the product, each checked (no stack, container or
labelled volume). After the queue the guest still held 8 unnamed Docker volumes made by the bench (4–72 KB:
Immich 1, RomM 2, PrivateBin 3, OnlyOffice 2) and 3 empty import folders the deploys created (
import/paperless,import/calibre,import/grimmoryon the system drive). Recorded (evidence/leftover-volumes-after-bench.txt), then removed by name — no prune./root/sizingand the 1.8 GB of content deleted. End state = start state: 5 containers (platform + opengist), 3 volumes, 12 images, opengist's own backup folder only. - Host (felhom-pve): samplers stopped,
/root/sizingdeleted. Nothing else was touched. - Hub: read only (a DB copy). The box reported its own telemetry for the bench apps, as any box does; no hub write.