Skip to content
vxvx
GitHubBlueskydev.toRSS

Deploy a relay

A distributed run needs a relay: CI runners take no inbound connection, so the run and its workers all dial out to it. You host your own. It runs on Cloudflare’s free plan, or on Bun or Docker anywhere your CI can reach.

  1. Deploy the relay.

    Deploy to Cloudflare

    The button copies the relay into a new repository of yours and deploys it. It asks for one var, ALLOW (step 2). The relay’s URL is the Worker’s, such as https://vx-dte-server.<you>.workers.dev.

  2. Say who may use it: ALLOW. One entry per workflow, issuer:owner/repo:workflow, space separated. List both the workflow that runs vx run and the workers’ workflow; every other token is refused.

    wrangler.jsonc
    "vars": {
    "ALLOW": "https://token.actions.githubusercontent.com:acme/app:ci.yml https://token.actions.githubusercontent.com:acme/app:vx-worker.yml"
    }
    • GitHub Actions: https://token.actions.githubusercontent.com:acme/app:ci.yml
    • GitLab: https://gitlab.com:group/project:.gitlab-ci.yml

    On Bun and Docker each entry is one --allow. GitLab names a project by its full path, group/subgroup/project.

  3. Check it answers. A request with no token is refused by name:

    Terminal window
    curl https://relay.example.com/v1/runs/$(uuidgen)/socket
    {"error":"no bearer token"}

    A Cloudflare relay with an empty ALLOW says so instead: this relay's ALLOW is empty: it admits no run.

  4. Name it in dte().

    .vx/workspace.ts
    dte({
    relay: 'https://relay.example.com',
    workers: { small: { max: 8, cpu: 4, memory: '16GB' } },
    adapter: github(),
    })

    Each side proves itself with its job’s OIDC token for the relay’s URL, so no secret is stored. On GitHub Actions both workflows need permissions: { id-token: write }; on GitLab the orchestrator and worker jobs ask for one: id_tokens: { VX_DTE_TOKEN: { aud: https://relay.example.com } }.


  • The relay carries orders and status, never files: outputs go through your remote cache.
  • On Cloudflare each run is one Durable Object, and the relay fits the free plan: websocket messages count 20 to one Durable Object request, against 100,000 a day, and it writes about two rows a worker. A deploy keeps the runs in flight: each side dials back. On Bun and Docker runs live in memory unless --state <file> keeps them, so a restart keeps the runs it was carrying.
  • The wire is Switchboard v1, an open protocol: any server that speaks it can stand in for this relay.
  • AUDIENCE (Cloudflare) is unset by default: tokens must name the URL the relay is reached at. Set it only when they name another one.

See @vzn/vx-dte-server for every option and the API to mount it in a server of your own.

Turborepo, Nx and other product names are trademarks of their owners. vx is not affiliated with or endorsed by them.