From 4714ca4da7f04474146238cce50ee8bf95213a90 Mon Sep 17 00:00:00 2001 From: "Aaron D. Lee" Date: Sun, 5 Jul 2026 22:52:32 -0400 Subject: [PATCH] =?UTF-8?q?feat(deploy):=20compose.yaml=20=E2=80=94=20T2?= =?UTF-8?q?=20reference=20deployment=20(deploy-B=20=C2=A72.2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four services per spec §2.2: * caddy — image rutster-edge; ports 80/443; volume caddy-data:/data (Caddy ACME state — loss risks LE 5/week duplicate-cert lockout per TLS brief §5 risk 5; mounts ./Caddyfile for edit-without- rebuild). * engine — image rutster-engine; ports 8080 + 49152-49407/udp (media UDP direct, never proxied — spec §2.1); stop_grace_period 660s paired with RUTSTER_DRAIN_DEADLINE_SECS=600 (grace exceeds drain invariant, spec §2.2/§8). * brain — image rutster-brain; network_mode: "service:engine" so the loopback-only tap posture survives unchanged (resolve_tap_url rejects non-loopback until step 6 per spec §2.2). Quoted in YAML — `service:engine` would parse as a mapping otherwise. * valkey — upstream valkey/valkey:8.0 (BSD-3); volume valkey-data:/data; dark on day one except slice-E ValkeyEventSink consumer. Bring-your-own-proxy: comment out the caddy service; the engine keeps plaintext :8080; tuned-timeout snippets land in slice-G reverse-proxies.md. Signed-off-by: Aaron D. Lee --- deploy/compose.yaml | 109 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 deploy/compose.yaml diff --git a/deploy/compose.yaml b/deploy/compose.yaml new file mode 100644 index 0000000..912d5fc --- /dev/null +++ b/deploy/compose.yaml @@ -0,0 +1,109 @@ +# deploy/compose.yaml — T2 modular reference deployment (spec §2.2 / ADR-0011). +# +# Four services: caddy (rutster-edge), engine (rutster-engine), brain +# (rutster-brain, network_mode: "service:engine" so the loopback tap posture +# survives unchanged — resolve_tap_url rejects non-loopback until step 6), +# valkey (upstream valkey/valkey). +# +# stop_grace_period 660s paired with RUTSTER_DRAIN_DEADLINE_SECS=600 — +# grace EXCEEDS drain or the orchestrator kills calls the engine was +# gracefully draining (spec §2.2 / §8). 60s slack accounts for a +# legitimately-long in-flight call (599s) + shutdown round-trip. +# +# Bring your own proxy: comment out the `caddy` service; the engine keeps +# its plaintext :8080 listener; tuned-timeout configs for nginx/HAProxy/ +# Traefik ship in docs/deploy/reverse-proxies.md (slice G). +# +# Bring-up: +# cp .env.example .env +# $EDITOR .env # set DOMAIN, ACME_EMAIL, MEDIA_ADVERTISED_IP, TWILIO_* +# docker compose up -d +# curl -fsS https://$RUTSTER_DOMAIN/healthz && echo OK + +services: + caddy: + # The custom xcaddy build with curated DNS plugins (cloudflare/ + # route53/porkbun/hetzner/desec — duckdns excluded per spec §3.2). + # In production, pulls from the git.adlee.work/alee/rutster-edge + # registry (slice F). For local dev / CI without registry auth, build + # from the Dockerfile: + # build: + # context: .. + # dockerfile: deploy/Dockerfile + # target: rutster-edge + image: rutster-edge:latest + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - caddy-data:/data + - ./Caddyfile:/etc/caddy/Caddyfile:ro + env_file: + - .env + depends_on: + - engine + networks: + - rutster-net + + engine: + # The FOB: axum signaling + media thread + trunk WS + /metrics. + # Plaintext :8080 behind Caddy (slice C's rustls will terminate + # in-process TLS — same listener, operator-choice). + image: rutster-engine:latest + restart: unless-stopped + # 660s grace > 600s drain (RUTSTER_DRAIN_DEADLINE_SECS) — invariant. + stop_grace_period: 660s + ports: + # Signaling + trunk WS behind Caddy (Caddy proxies :443 -> engine:8080). + # Comment out for production hardening (Caddy is the only entry). + - "8080:8080" + # WebRTC media UDP direct — never proxied (spec §2.1). + - "49152-49407:49152-49407/udp" + volumes: + - ./Caddyfile:/etc/rutster/Caddyfile:ro # copied in image already; mount for edit-without-rebuild + env_file: + - .env + networks: + - rutster-net + + brain: + # Brain shares engine's network namespace — the FOB reaches the brain's + # tap port via 127.0.0.1:8082 (loopback-only posture; resolve_tap_url + # rejects non-loopback URLs until step 6 per spec §2.2). The brain + # graduates to its own netns + wss:// tap auth with step 6. + image: rutster-brain:latest + restart: unless-stopped + # CRITICAL: quoted — YAML would otherwise parse `service:engine` as a + # `service: engine` mapping (key:value). The quotes make it a string. + network_mode: "service:engine" + env_file: + - .env + depends_on: + - engine + + valkey: + # Upstream valkey image (BSD-3; aggregation-clean vs GPL-3 per ADR-0004). + # Dark on day one except EventSink (slice E lands ValkeyEventSink + # against this service). The operator contract (volumes, ports, upgrade + # shape) is stable from v1 per spec §2.1 — the spend ledger (ADR-0009) + # + fleet directory land later without changing this service. + image: valkey/valkey:8.0 + restart: unless-stopped + command: ["valkey-server", "--dir", "/data", "--save", "60", "1", "--appendonly", "yes"] + volumes: + - valkey-data:/data + networks: + - rutster-net + +volumes: + # /data — Caddy's cert/ACME state. Loss risks the Let's Encrypt + # duplicate-cert lockout (5/week — total inbound outage class). + # See docs/deploy/certificates.md (slice G) + TLS brief §5 risk 5. + caddy-data: + # /var/lib/valkey — stream/state persistence (ADR-0005). + valkey-data: + +networks: + rutster-net: + driver: bridge