Skip to main content

Deployment

Lumio uses release-driven deployment for its container images: they are built and pushed only when a GitHub Release is published, or by manual dispatch -- there are no automatic image builds on branch pushes. The workflow lives in .github/workflows/release-images.yml. Other surfaces (docs, extensions, npm packages) have their own pipelines, covered under Other Deployment Pipelines.

Release Strategy

Every deploy starts with a GitHub Release. The release tag encodes which app to deploy and the version:

{app}@{version}

The workflow parses the tag, determines the target app, runs the verification gate, builds the image, pushes it to the container registry, and signs it.

Setting the Release Version

The product version is bumped by hand in the feature PR that changes app code, in the YYYY.M.PATCH format (e.g. 2026.8.14). The Rust workspace, all TS apps, all shared/*, and the private root package.json share one version and move in lockstep with the API — set every manifest to the same value directly (npm has no version inheritance).

After editing the manifests, run the mandatory, in-order post-bump regeneration chain and commit the generated artifacts alongside the manifest edits:

  1. cargo check --workspace — refreshes Cargo.lock.
  2. just schemas — regenerates apps/api/openapi.json + apps/api/schema.graphql (both embed the version).
  3. just bazel-vendor — regenerates vendor/ from the new Cargo.lock.
  4. Commit Cargo.lock, vendor/, apps/api/openapi.json, and apps/api/schema.graphql.
  5. just verify-release, then confirm git status is clean.

just verify-all does not detect schema or vendor drift — run just verify-release before opening the PR. Independent packages (packages/*, extensions/system/*) are not product-versioned; bump each one's own semver in the same PR that changes it.

How to Deploy

  1. Go to GitHub Releases for the repository.
  2. Click Draft a new release.
  3. Create a tag following the {app}@{version} format.
  4. Mark the release as a pre-release if the image should receive beta tags instead of latest.
  5. Publish the release.

Tag examples:

api@2026.5.3
twitch-bot@2026.5.1
web@2026.5.2
discord-bot@2026.5.1

Image Tags

The workflow generates multiple tags per image based on whether the release is marked as a pre-release.

Production (stable release)

TagExampleDescription
{version}2026.5.3Exact version
{major.minor}2026.5Floating minor tag
latestlatestMost recent stable release
{short_sha}a1b2c3d7-character commit SHA
{full_sha}a1b2c3d...Full 40-character commit SHA

Pre-release (beta)

TagExampleDescription
{version}2026.5.3-beta.1Exact pre-release version
betabetaFloating beta tag
{short_sha}a1b2c3d7-character commit SHA
{full_sha}a1b2c3d...Full 40-character commit SHA

A version string containing beta, alpha, or rc is also treated as a pre-release regardless of the GitHub Release checkbox.

Verification Gate

Before anything is published, a Verify job must pass:

bazel test //crates/... --build_tag_filters=-manual
bazel build //apps/... --build_tag_filters=-manual

The build step is a pre-push gate: the release fails before publishing if any app binary does not compile. -manual skips the container _push targets, which are build-tagged manual.

Deployable Apps

Nine Rust binaries are built with Bazel (bazel run //apps/{app}:{app}_push) via rules_img:

AppTag Prefix
apiapi@
automation-workerautomation-worker@
bot-module-workerbot-module-worker@
twitch-bottwitch-bot@
youtube-botyoutube-bot@
kick-botkick-bot@
trovo-bottrovo-bot@
discord-botdiscord-bot@
innertube-proxyinnertube-proxy@

Three Next.js apps are built from Dockerfiles (docker/{app}.Dockerfile):

AppTag Prefix
webweb@
adminadmin@
idid@

The innertube-proxy image is deployed to a trusted-IP host rather than a shared cloud edge — see InnerTube Proxy for the placement requirement.

Bulk Deploys

Three special tag prefixes deploy multiple apps at once:

Tag PrefixDeploys
all@{version}All 12 apps (9 Rust + 3 Next.js)
all-rust@{version}All 9 Rust apps
all-web@{version}All 3 Next.js apps

Example: publishing a release tagged all@2026.5.3 builds and pushes every app image.

Manual Dispatch

For emergency deploys or rebuilds without creating a release, the workflow supports workflow_dispatch with two inputs:

InputDescription
appWhich app to deploy — a dropdown of all, all-rust, all-web, or any single app from the tables above
tagThe image tag to apply (required)

This is useful when the release flow is unavailable or when retagging an existing build.

Signing and Provenance

Every pushed image is signed and attested before the job finishes. Signing resolves the image's immutable digest first (via its unique full-SHA tag) and operates on that digest, never on a floating tag:

  • Signature — keyless cosign sign using the workflow's ambient GitHub OIDC token (the job holds id-token: write). No long-lived signing key exists.
  • Provenancecosign attest --type slsaprovenance1 attaches a SLSA v1.0 provenance predicate recording the source repository, git ref and commit, the triggering event, the builder workflow ref, and the run's invocation URL.

Schema Artifacts

Releases that include the API (api, all, or all-rust) additionally generate and attach the API contract to the GitHub Release:

bazel run //apps/api:generate-openapi -- openapi.json
bazel run //apps/api:generate-schema -- schema.graphql

Both files are uploaded to the release with --clobber, so each API release carries the exact OpenAPI and GraphQL SDL it shipped.

Container Registry

All images are pushed to GitHub Container Registry (GHCR):

ghcr.io/{org}/lumio/{app}

For example:

ghcr.io/zaflun/lumio/api:2026.5.3
ghcr.io/zaflun/lumio/twitch-bot:latest
ghcr.io/zaflun/lumio/web:beta

Authentication uses the GITHUB_TOKEN provided by GitHub Actions with packages: write permission.

Environments

Two different promotion models are in use, and they must not be conflated.

Container apps (API, workers, bots, web, admin, id)

These are promoted by release channel, not by branch. release-images.yml never runs on a branch push — only on a published GitHub Release or a manual dispatch. The channel is decided by the pre-release flag:

Release kindFloating tagMeaning
Pre-releasebetaPre-release channel
Full releaselatestFull release channel

Consumers pin an exact {version} or follow beta / latest. Which environment a given image lands in is decided by the deployment target that pulls it, and is convention-based rather than enforced by this workflow.

Docs family (docs, developer-docs, extension-apps)

Only these deploy on branch and tag pushes, and their branch-to-environment mapping is enforced in CI:

Branch / TagEnvironment
nextStaging
mainProduction preview
Tag 20* (e.g. 2026.5.3)Production

See the Installation guide for the full domain table per environment.

Other Deployment Pipelines

Container images are only one of several release paths. The rest are driven by their own workflows:

SurfaceWorkflowTrigger
Docs sitedeploy-docs.ymlPush to next / main, tag 20*
Developer docsdeploy-developer-docs.ymlPush to next / main, tag 20*
Extension supervisor + runtimedeploy-extension-apps.ymlPush to next / main or tag 20*, restricted to changes under apps/extension-supervisor/ or apps/extension-runtime/; also manual
System extensionssystem-extensions.ymlTag ext-{name}@{version} (e.g. ext-chat-box@1.0.3); also manual, per extension or all
Public npm packagespublish-npm-all.yml, publish-npm.ymlManual only

The npm workflow publishes the five public @zaflun/* packages in dependency order and supports a dry_run input that builds and tests without publishing. Those packages carry their own independent semver rather than the product version — see npm Publishing.

Security Audit

A nightly security audit runs via .github/workflows/security-audit.yml (cron: 0 3 * * * UTC). It scans both dependency ecosystems and reports to Discord:

CheckToolScope
Rust dependenciescargo auditAll advisories
npm dependenciespnpm auditHigh and critical severity

Alert flow:

  • Individual vulnerability alerts fire immediately via Discord webhook (DISCORD_SECURITY_WEBHOOK_URL).
  • A daily summary always posts -- either "All clear" (green) or "Action required" (yellow) with vulnerability counts.

The workflow also supports manual workflow_dispatch for on-demand scans.