Skip to main content

Deployment

How each component of the Qubital system is built, packaged, and deployed.

Component map​

ComponentRuntimeRegistryDeploy trigger
Go backend (API server + eventsync worker)SevallaGHCR (ghcr.io/<org>/qubital-backend-prod)Manual (workflow_dispatch)
office-router WorkerCloudflare Workers—wrangler deploy
upload-workerCloudflare Workers—wrangler deploy
Recording pipeline (livekit-listener + video-upload-job)Google Cloud RunGCP Artifact RegistryManual (workflow_dispatch)

Go backend — Sevalla via GHCR​

The backend is containerised and deployed on Sevalla. Sevalla pulls the Docker image from GHCR. The image is built and pushed by the build-push-prod.yaml workflow.

Release flow​

  1. Trigger build-push-prod.yaml via workflow_dispatch with a semver version string (e.g. 1.2.3)
  2. Workflow validates the version is strictly greater than all versions already published to GHCR (prevents downgrades)
  3. Tests run (full suite + race + smoke + lint) — build does not start if any step fails
  4. Docker image built for linux/amd64, scanned with Trivy (blocks on HIGH/CRITICAL)
  5. Image pushed to GHCR with three tags: <version>, sha-<git-sha>, and optionally latest
  6. SBOM and provenance attestations attached to the image in GHCR
  7. Sevalla pulls the new image on the next deploy (configured in Sevalla dashboard)

Image naming​

ghcr.io/<org>/qubital-backend-prod:<version>
ghcr.io/<org>/qubital-backend-prod:sha-<git-sha>
ghcr.io/<org>/qubital-backend-prod:latest # only if tag_latest input is true

The org name is normalised to lowercase — GHCR rejects uppercase paths.


Cloudflare Workers​

Both Cloudflare Workers (office-router and upload-worker) are deployed via the Wrangler CLI. There is no automated CI/CD pipeline for them — deployments are manual.

# From the qubital-workers repo root
pnpm deploy --env production # office-router or upload-worker

Each worker has staging and production environments defined in its wrangler.jsonc.


Recording pipeline — Google Cloud Run​

The recording pipeline lives in qubital-workers/pkg/google-cloud-uploader/ and is deployed to Google Cloud Run via the build-publish.yaml workflow (workflow_dispatch).

One Docker image is built and deployed as two distinct components:

ComponentTypeEntry pointPurpose
livekit-listenerCloud Run Service (always-on)dist/service.jsReceives egress_ended webhooks from LiveKit, triggers the upload job
video-upload-jobCloud Run Job (on-demand)dist/job.jsFetches recording from R2, uploads to YouTube, logs to Google Sheets, notifies Slack

Both run under the video-sa GCP service account. The image is pushed to GCP Artifact Registry before deployment.

Recording pipeline flow​

LiveKit Cloud
│ egress_ended webhook
▼
livekit-listener (Cloud Run Service)
│ triggers
▼
video-upload-job (Cloud Run Job)
│ fetches file from Cloudflare R2 (temporary recording storage)
│ uploads to YouTube as unlisted video
│ logs entry to Google Sheets
│ sends link to Slack