Production releases (k3s)
The k3s target deploys one coordinated release. It replaces
per-image auto-updating. .github/workflows/production-release.yml is the only
publisher for this target. It builds four candidate images for one commit (api,
migrations, ui and the public site), then pins all four digests in
infra/k3s/overlays/prod/kustomization.yaml in a single commit. ArgoCD syncs that
commit.
The per-image approach shipped the UI before the API image it needed existed. It
also produced a stream of automatic update commits. Full detail lives in
infra/k3s/README.md.
How a release runs
Section titled “How a release runs”- Your push to
mainwaits, up to 45 minutes, for every applicable CI workflow at the same commit. Any missing, cancelled, failed or timed-out check blocks the release. - The workflow builds
boringstack-api,boringstack-migrations,boringstack-uiandboringstack-siteascandidate-<sha>images. Nothing moveslatestorsha-*for this target. The site image takes its build-time values frominfra/k3s/overlays/prod/site-build.env, so a change there rebuilds it. - Only after all four builds succeed does it pin the digests in one commit. A
newer
maincommit supersedes an older release. - Argo syncs in wave order: infrastructure, then the migration Job (wave -1), then the API (wave 0), then the UI and the public site (wave 1) once the API is healthy.
A successful workflow means the digests were promoted. It does not mean Argo has finished deploying. Check Synced and Healthy, then smoke-test sign-in and the main screens.
Activation
Section titled “Activation”- Fresh fork: nothing to remove.
- If an older Argo Application still has
argocd-image-updaterannotations, remove them first. Otherwise a per-image updater can overwrite the coordinated digests. - Set the repository variable
PRODUCTION_RELEASE_MODE=coordinated. Until it is set, the release job is skipped and no image is built. - Allow the release bot to push the promotion commit to
main. Do not disable branch protection to make a release pass.
Runtime and database TLS
Section titled “Runtime and database TLS”The API and migrations images run with a read-only root filesystem. Only /tmp is
writable. The API’s CMD runs bun --no-install, so it never installs packages at
startup.
Postgres TLS follows one policy for the runtime and the migration Job. The CA comes
from the CloudNativePG-generated boringstack-db-ca secret and is injected as
DATABASE_SSL_CA. The policy lives in apps/api/src/lib/postgres/connection.ts. Keep
it in DATABASE_SSL_*, not in sslmode on the URL.
Migrations and seeds
Section titled “Migrations and seeds”The migration Job runs db:migrate only. It runs on every sync, so it must never
seed. db:seed upserts reference data and would revert admin edits. Run the first
admin bootstrap as a deliberate one-off. The steps are in
infra/k3s/README.md.
Its db:seed is idempotent: it creates the admin only when that user does not
exist.
Rollback
Section titled “Rollback”Restore all four digests from one earlier promotion in a reviewed commit, then sync the whole Application. The site image follows the same rule. It has no separate rollback path. Do not selectively sync Deployments. Migrations do not reverse automatically, so confirm the older API works with the current schema first.
Related
Section titled “Related”- Kubernetes (k3s) infra reference
- Provisioning with k3s
- Secrets backends
- Image updates (Compose / WUD), the Compose-path equivalent. Compose keeps its own
latestpublishers. - Public site, the site image’s configuration and runtime.