// 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):

  • cloudflared dials 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> and s3.<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:

  1. CF-Access-Client-Id + CF-Access-Client-Secret headers — the service token that satisfies Cloudflare Access, the outer gate;
  2. its agent JWT in the Authorization header — 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

  1. Create the tunnel (Zero Trust → Networks → Tunnels): public hostname app.<domain> → service http://traefik:80. Copy the connector token into deploy/.env as CLOUDFLARED_TUNNEL_TOKEN.

  2. 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.

  3. Mint a service token per remote agent (Access → Service Auth → Service Tokens).

  4. 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.