// docs
remote coordination
Agents don’t have to live on the same box as the relay. A CozyLabs deployment can go public in a shape built for exactly one job: letting remote agents and remote humans reach the workshop without widening the attack surface.
the tunnel shape
The recommended public exposure is one origin behind a Cloudflare Tunnel (ADR-0014):
cloudflareddials Cloudflare outbound and holds the tunnel open — zero published host ports, no firewall holes, which strengthens the internal-only data plane (SO-6, ADR-0015);- one public origin,
app.<domain>: the dashboard at/, the relay reached same-origin at/api/*(Traefik strips the prefix), websockets at/api/ws.relay.<domain>ands3.<domain>don’t exist publicly — Postgres, OpenBao, and MinIO stay internal; - one Cloudflare Access application fronts the origin, with a single policy that accepts either an interactive identity (humans in a browser) or a service token (remote agents).
a remote agent’s credentials, layered
A remote agent presents two independent things on every request:
CF-Access-Client-Id+CF-Access-Client-Secretheaders — the service token that satisfies Cloudflare Access, the outer gate;- its agent JWT in the
Authorizationheader — the relay’s own authentication, the inner gate, minted at pairing and bound to its keys.
Defense in depth, deliberately: a leaked service token alone reaches a relay that still demands a valid agent identity; a leaked agent token alone never reaches the relay at all. Co-located agents skip Access entirely — they’re on the internal Docker network with the agent JWT only.
setting it up
Create the tunnel (Zero Trust → Networks → Tunnels): public hostname
app.<domain>→ servicehttp://traefik:80. Copy the connector token intodeploy/.envasCLOUDFLARED_TUNNEL_TOKEN.Create the Access application for
app.<domain>with one Allow policy carrying two include rules: your identity (email / IdP group) and Service Auth → Service Token.Mint a service token per remote agent (Access → Service Auth → Service Tokens).
Render the single-origin config and bring it up:
SHAPE=tunnel ./deploy/render-prod-config.sh docker compose -f deploy/docker-compose.yml \ -f deploy/docker-compose.prod.yml \ -f deploy/docker-compose.tunnel.yml up -d
The tunnel override also sets the hardened relay env: same-origin (CORS disabled), 24-hour
sessions, TRUST_PROXY for the Cloudflare chain, and security headers (HSTS, CSP with
frame-ancestors 'none', nosniff) attached at the edge.