Skip to content
On this page

Run your own cloud

Deploy the Convex backend, Clerk sign-in, website, and GitHub App behind your own Vectis builds.

The hosted service at vectis.kerd.dev is one deployment of the code in this repository. You can run the same stack yourself, for a fork or for private development. A deployment has four parts:

Part What it does
Convex Accounts, machines, repository connections, GitHub webhooks, runner leases, and remote commands
Clerk Sign-in for the connect page
Cloudflare Worker Serves the website, the docs, and /connect, and forwards GitHub browser callbacks to Convex
GitHub App Repository access and just-in-time runner registration

Vectis builds come in two flavors. Development builds (every source checkout) read .env.local, use ~/.vectis-dev, and are named Vectis Dev. Production builds are packaged with --flavor production, read .env.production.local at package time, and use ~/.vectis. Keep one set of Convex, Clerk, Worker, and GitHub App per flavor; never point a development build at production data.

.env.example lists every variable. Copy it to .env.local or .env.production.local and fill it in as you go. Commands below use development; add --prod to Convex commands and use .env.production.local for production.

1. Convex

Create a Convex project and a development deployment. This writes CONVEX_DEPLOYMENT, CONVEX_URL, and CONVEX_SITE_URL to .env.local:

pnpm exec convex dev --once

For production, deploy with pnpm exec convex deploy and copy the production URLs into .env.production.local.

Create the key that signs short-lived machine tokens, store it on the deployment, and delete the local copy. Load the file first so the commands can use its values:

set -a; . ./.env.local; set +a
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 -out machine.pem
pnpm exec convex env set VECTIS_MACHINE_PRIVATE_KEY -- "$(cat machine.pem)"
rm machine.pem
pnpm exec convex env set VECTIS_MACHINE_ISSUER "$CONVEX_SITE_URL"
pnpm exec convex env set VECTIS_WEB_URL "$VECTIS_WEB_URL"
pnpm exec convex env set VECTIS_GITHUB_APP_NAME "Vectis"

VECTIS_WEB_URL is the HTTPS origin of your Worker (step 3), without a trailing slash.

2. Clerk

  1. Create a Clerk application. Development uses its development instance; production needs a production instance on your own domain, which Clerk walks you through (DNS records for clerk.<your-domain>).
  2. In Integrations, activate Convex. This creates the convex JWT template that Convex verifies.
  3. Copy the publishable key into CLERK_PUBLISHABLE_KEY, and set the issuer on Convex:
pnpm exec convex env set CLERK_JWT_ISSUER_DOMAIN https://your-app.clerk.accounts.dev
  1. Add a webhook in Clerk that sends user.deleted to ${CONVEX_SITE_URL}/clerk/webhook, then store its signing secret:
pnpm exec convex env set CLERK_WEBHOOK_SECRET whsec_your_signing_secret

Without it, deleting an account inside Clerk would leave its machines and repository connections behind. Vectis verifies every delivery signature and ignores other events.

For production, also turn on bot sign-up protection. Anyone can create an account on a public deployment.

3. Cloudflare Worker

The Worker in apps/web serves the static site and forwards /api/github/* browser callbacks to CONVEX_SITE_URL. Sign in with pnpm --filter @vectis/web exec wrangler login, then deploy:

pnpm site:deploy:dev   # the "development" environment on workers.dev
pnpm site:deploy       # the "production" environment on its custom domain

The production route in apps/web/wrangler.jsonc is vectis.kerd.dev; change it to your own domain in a fork. The deploy script passes CONVEX_SITE_URL to the Worker, so the configuration file holds no deployment URLs. To keep a development deployment private, follow Private development environment.

4. GitHub App

Vectis creates its GitHub App through GitHub’s manifest flow, so the App’s private key, client secret, and webhook secret are written straight into Convex and never touch your machine. The Worker from step 3 must already serve VECTIS_WEB_URL, because GitHub returns to it. Run this from the repository root; for production, load .env.production.local instead and add --prod to convex run:

set -a; . ./.env.local; set +a
state=$(openssl rand -hex 32)
digest=$(printf %s "$state" | shasum -a 256 | cut -d " " -f 1)
owner=your-github-login
pnpm exec convex run githubAppSetup:create \
  "{\"stateDigest\":\"$digest\",\"ownerId\":$(gh api "users/$owner" --jq .id),\"ownerLogin\":\"$owner\",\"organization\":false}"
open "$VECTIS_WEB_URL/api/github/app/setup?state=$state"

Set "organization": true when the owner is an organization. The setup link expires after an hour and works once. A deployment holds exactly one App.

For a development deployment, set VECTIS_GITHUB_APP_NAME to something like Vectis [DEV] and VECTIS_GITHUB_APP_PUBLIC to false before creating it, so only the owner account can install it.

After GitHub creates the App, open its settings to upload a logo and add privacy policy and support URLs. GitHub delivers webhooks directly to CONVEX_SITE_URL, which verifies every delivery signature.

5. Build clients

Clients connect to the deployment they were built for. With the variables in place:

pnpm package:cli --output /absolute/path/to/new-package --helpers /absolute/path/to/native-binaries --flavor production

A production package reads CONVEX_URL and VECTIS_WEB_URL from the environment or .env.production.local and bakes them in. Released apps ignore those variables at run time, so a stray CONVEX_URL in a user’s shell cannot redirect them.

Keeping deployments compatible

Released apps keep running against the backend after you deploy a newer one. Deploy Convex changes that older clients can still use, and remove old behavior only after the releases that depend on it are gone.