Files
rutster/deploy/compose.yaml
Aaron D. Lee 754614bd56
Some checks failed
CI / fmt (push) Successful in 1m1s
CI / clippy (push) Successful in 1m52s
CI / deny (push) Has been cancelled
CI / test (1.85) (push) Has started running
CI / test (stable) (push) Has been cancelled
CI / sim-bench (stable) (push) Has been cancelled
CI / twilio-live (manual only) (push) Has been cancelled
CI / image-build (4 images) (push) Has been cancelled
CI / smoke (all-in-one TLS sim call) (push) Has been cancelled
CI / smoke (T2 compose four-service) (push) Has been cancelled
CI / smoke (caddy reload during live call, zero drops) (push) Has been cancelled
fix(deploy-epoch): un-spoofable smoke gates + honest docs (deploy-epoch-fix) (#29)
Co-authored-by: Aaron D. Lee <himself@adlee.work>
Co-committed-by: Aaron D. Lee <himself@adlee.work>
2026-07-09 23:54:57 +00:00

121 lines
4.7 KiB
YAML

# 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
# T6: the Caddyfile upstream is env-substituted {$RUTSTER_UPSTREAM:127.0.0.1:8080};
# compose overrides to reach the engine service on the shared `rutster-net`
# network via DNS name. Without this, Caddy proxies to its own loopback
# (its own container's :8080 = nothing) and every /healthz returns 502.
environment:
RUTSTER_UPSTREAM: engine:8080
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
# T8: production default for the EventSink (spec §5.6 / valkey_sink.rs:45).
# Overridable via .env; the smoke writes redis://valkey:6379/ into .env
# (same value — both reach the engine service-name form).
environment:
RUTSTER_VALKEY_URL: redis://valkey:6379/
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