ADR-0009 — Ephemeral comparisons as in-process run windows, not container spin-ups¶
- Status: accepted
- Date: 2026-07-02
- Domain: exhibit mechanics · security · resource sizing
Context¶
Both sides of both live comparisons cannot run 24/7 on this host (ADR-0006's constraint, made harder by the resize to 4 GB). Comparisons must therefore be on-demand: a viewer triggers a run, it produces numbers, it stands down. The question is what "spin up" means. The literal reading — the trigger starts and stops Docker containers — would hand a public, unauthenticated web endpoint control over the Docker API. CP8 just spent a checkpoint taking that power away from trusted admin tools via the socket proxy; giving it to an internet-facing button would undo the lab's best security decision.
Decision¶
Comparison services are resident but idle: the streamer container always exists, consuming near-zero CPU outside runs. A trigger opens a bounded run window inside the process — the service starts its tight loop, runs for a fixed duration (max 180 s), and returns to idle. The window deadline is stored in Redis so enforcement survives an API restart: teardown happens even if the trigger path dies mid-window. No public component can start, stop, or create containers.
Consequences¶
- Attack surface of the trigger is one rate-limited application endpoint, not the Docker API.
- Steady-state cost is a small resident container (~10 MB idle) instead of true zero — the price of not owning container lifecycle from a public app. Worth it.
- "Ephemeral" in the exhibit copy means the workload, not the container. The public page's wording reflects this.
- Same pattern reused for CP12's fault auto-heal (deadline in Redis, enforced server-side).
Alternatives considered¶
- Docker spin-up via socket proxy — rejected: even filtered, it grants a public endpoint container-lifecycle power; one policy mistake away from an incident.
- Always-on both sides — rejected: doesn't fit RAM, and erases the idle-cost half of the freshness-vs-cost story.
- Scheduled runs with replayed results — rejected: the viewer no longer causes the run; the bridge from claim to observation weakens.