NutsNews VPS Same-Host Staging Capacity Budget
Section titled “NutsNews VPS Same-Host Staging Capacity Budget”The production VPS can host a tightly bounded staging application, but this does not authorize a staging deployment. Staging remains disabled until the later data-isolation, credential, route, deployment, and qualification work is reviewed and applied.
Decision
Section titled “Decision”Go for same-host staging with the fixed budget below. The decision is based
on a read-only SSH audit at 2026-07-13T15:24:06Z, not on inventory values
alone. The host had four vCPUs, 9.36 GB available memory, no zram use, 74.2 GB
(69.1 GiB) free root disk, and 9.66 million free inodes. No OOM event was found in the
preceding seven days.
This decision is deliberately conservative because the host retains only short current samples and swap history; it does not retain CPU or memory peak series. Do not treat the current low use as a measured peak.
Read-Only Evidence Commands
Section titled “Read-Only Evidence Commands”The audit connected as nutsnews_ops with BatchMode=yes and used existing
passwordless sudo only for read operations. The relevant commands were:
lscpu; uptime; vmstat 1 5; free -b; swapon --show --bytes; zramctldf -B1 -T; df -isudo docker system df --format '{{json .}}'sudo docker ps -asudo docker inspect <each-container>sudo docker stats --no-streamsudo du -sB1 /var/lib/docker /var/lib/containerd /var/log /opt/nutsnewssudo journalctl -k --since '7 days ago' --no-pagersudo journalctl -u docker.service --since '7 days ago' --no-pagersudo journalctl -p err..alert --since '7 days ago' --no-pagersystemctl show docker.service alloy.service nutsnews-restic-backup.serviceThe Docker output reported 3.57 GB of images (1.63 GB reclaimable), 1.98 GB of
build cache (1.85 GB reclaimable), three running healthy containers, and a
zero-byte app cache volume. Container JSON logs totalled 0.74 MB; journald used
187.6 MB. The seven-day kernel OOM count was zero. The single broad disk-pattern
match was the normal boot option errors=remount-ro, not ENOSPC. Docker log
messages were release/restart and resolver warnings; priority-error messages
were almost entirely internet SSH probes, not capacity faults.
Fixed Capacity Contract
Section titled “Fixed Capacity Contract”| Resource | Observed production/platform use | Production safety reserve | Staging budget | Remaining after staging budget |
|---|---|---|---|---|
| CPU | 0.20 one-minute load; 1.7% host sample; app 0.01%, Caddy 4.01%, Ops auth 0.02% | 3 vCPUs | 1 vCPU maximum; CPU shares 256 | At least 3 vCPUs remain outside the staging cap |
| Memory | 1.06 GB host used / 9.36 GB available; app 161 MiB, Caddy 14 MiB, Ops auth 16 MiB; Alloy process RSS 402 MiB | 4 GiB | 512 MiB limit; 256 MiB reservation | 5.2 GiB after reserve and staging ceiling, before normal reclaimable cache |
| PIDs | app 12 / 256, Caddy 9 / 128, Ops auth 1 / 128 | Production app stays at 256 | 128 maximum | Separate staging process cap; production allowance unchanged |
| Root disk | 7.34 GiB used / 69.1 GiB free; Docker images 3.57 GB, build cache 1.98 GB, volumes 0 B | 20 GiB free | 1 GiB image/cache preflight budget plus 30 MiB logs | At least 48.1 GiB after reserve and preflight budget |
| Inodes | 251,014 used / 9,658,746 free | 1,000,000 free | No portable per-volume inode quota; check before deploy | More than 8.6 million outside reserve at audit time |
| Swap/zram | 0 B / 1.5 GiB | zram is transient-spike protection, not planned capacity | Do not plan to consume it | 1.5 GiB unused at audit time |
The Docker Engine runs Compose on cgroup v2. cpus, mem_limit,
mem_reservation, pids_limit, and per-service json-file log options are
enforced by Docker. cpu_shares is a lower scheduling weight, not an absolute
CPU reservation; the one-vCPU staging cap preserves the three-vCPU reserve by
construction.
Required Staging Safeguards
Section titled “Required Staging Safeguards”The reviewed infrastructure configuration fixes the staging service at:
- 1 vCPU maximum and CPU shares 256.
- 512 MiB memory maximum and 256 MiB memory reservation.
- 128 PIDs.
json-filelogs at 10 MiB per file with three retained files: 30 MiB maximum per staging container.- Two qualification workers, at most five requests per second, 300 requests total, and five minutes maximum duration.
- A 24-hour qualification TTL. The later staging deployment workflow must scale the staging service down and remove its cache volume at expiry.
Never run destructive load tests, chaos tests, full ZAP scans, unbounded crawlers, or other unbounded traffic against this shared host.
Disk and Write Limitation
Section titled “Disk and Write Limitation”Docker’s local volume driver on this ext4/overlayfs host does not offer a portable hard per-volume quota. Do not claim that the 1 GiB image/cache budget is a cgroup-enforced quota. The enforceable write guard is the service’s 30 MiB log cap; the deploy workflow must additionally preflight Docker/root disk and inode headroom, and expiry cleanup must remove the staging cache volume.
No on-host image builds are allowed for staging. Pull only the reviewed digest. At the audit, Docker held eleven reclaimable prior app images (1.63 GB) and 1.85 GB of reclaimable build cache; those remain an operational cleanup item, not capacity assigned to staging.
Source Configuration Versus Runtime
Section titled “Source Configuration Versus Runtime”The source configuration defaults Grafana Alloy to disabled. The audited host
had alloy.service active, with systemd memory current about 127 MiB and
process RSS about 402 MiB. This is a source/runtime divergence, not evidence
of a manual change. Count the larger observed value in capacity planning and
reconcile the service only through a later protected apply.
Production containers were healthy and had no restarts. The app used 161 MiB of its existing 768 MiB cap; Caddy and Ops auth used 14 MiB and 16 MiB of their 128 MiB caps. Backups were configured, successful, and fresh; their units have CPU/IO weight 50 but no memory ceiling, so the 4 GiB reserve includes backup burst room.
Production Safeguards Applied
Section titled “Production Safeguards Applied”The reviewed production safeguards were applied through the protected workflow
on 2026-07-13T16:55Z, with Vercel sync disabled, Cloudflare DDNS disabled,
and Grafana Alloy explicitly enabled to preserve the active host service. The
workflow completed successfully: run 29268383268
reported ok=156, changed=11, failed=0.
This was a production-only reconciliation. It did not deploy or enable staging, send staging traffic, or change the staging route, credentials, or release identity.
The post-apply read-only check at 2026-07-13T17:01:48Z confirmed that the
app had been recreated once and was healthy with these enforced Docker values:
- CPU cap
4000000000nanocpus (4 vCPUs), CPU shares1024. - Memory limit
805306368bytes (768 MiB), reservation536870912bytes (512 MiB), and PID limit256. json-filelogging,max-size=10m,max-file=3.
The same sample showed 0.65 one-minute load, 9,472,016,384 bytes available
memory, zero zram use, 74,190,782,464 bytes (69.1 GiB) free root disk, and
9,658,763 free inodes. The app used 61.6 MiB and 12 PIDs; Caddy used 14.9 MiB
and 10 PIDs; Ops auth used 15.5 MiB and one PID. Alloy remained active and
enabled. There were no kernel OOM events or system priority-error entries
since the apply. Docker recorded one expected restart canceled warning while
the managed old app container was stopped for recreation; the replacement
container was healthy with restart count zero.
Relevant post-apply read-only commands were:
uptime; free -b; swapon --show --bytesdf -B1 --output=size,used,avail,pcent /; df -iP /systemctl is-active alloy.service; systemctl is-enabled alloy.servicesudo docker inspect nutsnews-appsudo docker ps; sudo docker stats --no-stream; sudo docker system dfsudo find /var/lib/docker/containers -name '*-json.log' -printf '%f %s\n'sudo journalctl -k --since '2026-07-13 16:55:00 UTC' --no-pagersudo journalctl -p err..alert --since '2026-07-13 16:55:00 UTC' --no-pagersudo journalctl -u docker --since '2026-07-13 16:55:00 UTC' --no-pagerApply and Verify Later
Section titled “Apply and Verify Later”Do not enable staging merely because this document or a CI check is green. After the relevant staging deployment PR is approved and applied, use read-only SSH during one bounded qualification run:
uptime; free -h; swapon --show; df -h; df -isudo docker stats --no-streamsudo docker inspect nutsnews-app nutsnews-app-stagingsudo docker system dfsudo journalctl -k --since '15 minutes ago' --no-pagersudo journalctl -u docker.service --since '15 minutes ago' --no-pagerVerify production remains healthy, production memory and PID use stay below their current limits, root disk/inodes remain above reserve, staging stays at its hard limits, and there are no new OOM, ENOSPC, Docker, or application error signals attributable to the qualification run. Inspect Caddy request logs only for the bounded test window and confirm the traffic stays within the test budget.
