# 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