# Running the relay next to something else, without letting it near it. # # The relay is untrusted by construction: it routes ciphertext and holds no keys. # That is a statement about what it can *learn*, not a reason to give it reach. So # it gets its own network with nothing else on it, no volumes, and no secrets in the # environment. If it is ever compromised, what the attacker has is a process that # can forward opaque frames — which is what it was already allowed to do. # # It binds to localhost only. TLS and the public hostname belong to whatever reverse # proxy already terminates :443 on the host; two things cannot both hold that port, # and the relay has no business holding it. See README for the Caddy block. services: relay: build: . image: claudinio-relay:local container_name: claudinio-relay restart: unless-stopped environment: BIND: 0.0.0.0:8787 # `info` says who attached and what was dropped, and never says what was # forwarded — the prova real asserts no transcript text reaches this log. RUST_LOG: ${RUST_LOG:-info} ports: # 127.0.0.1 on purpose. Exposing 8787 to the world would bypass the proxy # doing TLS, and a WebSocket relay reachable over plain HTTP is a relay whose # frames can be counted and timed by anyone on the path. - "127.0.0.1:8787:8787" networks: - relay # In memory by design, so the container needs nothing writable. read_only: true tmpfs: - /tmp security_opt: - no-new-privileges:true mem_limit: 256m healthcheck: # /healthz answers without touching the routing table, so a hung forward # cannot make an unhealthy relay look healthy. test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/8787 && printf 'GET /healthz HTTP/1.0\\r\\n\\r\\n' >&3 && head -1 <&3 | grep -q 200"] interval: 30s timeout: 5s retries: 3 start_period: 5s networks: relay: # Not attached to any other stack's network. The relay has nothing to say to a # database and must not be able to reach one. driver: bridge