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:
cargo check --workspace— refreshesCargo.lock.just schemas— regeneratesapps/api/openapi.json+apps/api/schema.graphql(both embed the version).just bazel-vendor— regeneratesvendor/from the newCargo.lock.- Commit
Cargo.lock,vendor/,apps/api/openapi.json, andapps/api/schema.graphql. just verify-release, then confirmgit statusis 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
- Go to GitHub Releases for the repository.
- Click Draft a new release.
- Create a tag following the
{app}@{version}format. - Mark the release as a pre-release if the image should receive
betatags instead oflatest. - 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)
| Tag | Example | Description |
|---|---|---|
{version} | 2026.5.3 | Exact version |
{major.minor} | 2026.5 | Floating minor tag |
latest | latest | Most recent stable release |
{short_sha} | a1b2c3d | 7-character commit SHA |
{full_sha} | a1b2c3d... | Full 40-character commit SHA |
Pre-release (beta)
| Tag | Example | Description |
|---|---|---|
{version} | 2026.5.3-beta.1 | Exact pre-release version |
beta | beta | Floating beta tag |
{short_sha} | a1b2c3d | 7-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:
| App | Tag Prefix |
|---|---|
api | api@ |
automation-worker | automation-worker@ |
bot-module-worker | bot-module-worker@ |
twitch-bot | twitch-bot@ |
youtube-bot | youtube-bot@ |
kick-bot | kick-bot@ |
trovo-bot | trovo-bot@ |
discord-bot | discord-bot@ |
innertube-proxy | innertube-proxy@ |
Three Next.js apps are built from Dockerfiles (docker/{app}.Dockerfile):
| App | Tag Prefix |
|---|---|
web | web@ |
admin | admin@ |
id | id@ |
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 Prefix | Deploys |
|---|---|
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:
| Input | Description |
|---|---|
app | Which app to deploy — a dropdown of all, all-rust, all-web, or any single app from the tables above |
tag | The 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 signusing the workflow's ambient GitHub OIDC token (the job holdsid-token: write). No long-lived signing key exists. - Provenance —
cosign attest --type slsaprovenance1attaches 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 kind | Floating tag | Meaning |
|---|---|---|
| Pre-release | beta | Pre-release channel |
| Full release | latest | Full 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 / Tag | Environment |
|---|---|
next | Staging |
main | Production 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:
| Surface | Workflow | Trigger |
|---|---|---|
| Docs site | deploy-docs.yml | Push to next / main, tag 20* |
| Developer docs | deploy-developer-docs.yml | Push to next / main, tag 20* |
| Extension supervisor + runtime | deploy-extension-apps.yml | Push to next / main or tag 20*, restricted to changes under apps/extension-supervisor/ or apps/extension-runtime/; also manual |
| System extensions | system-extensions.yml | Tag ext-{name}@{version} (e.g. ext-chat-box@1.0.3); also manual, per extension or all |
| Public npm packages | publish-npm-all.yml, publish-npm.yml | Manual 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:
| Check | Tool | Scope |
|---|---|---|
| Rust dependencies | cargo audit | All advisories |
| npm dependencies | pnpm audit | High 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.