Environments, branches and releases
This page is for developers and the person who releases SBDMS. It explains the environments that exist, which branch feeds each, and the exact steps of a release. The source of truth for the branch rules is docs/BRANCHING.md in the repository.
The environments
Section titled “The environments”| Environment | Who uses it | What it runs |
|---|---|---|
Production (sbdms.flywingsitsolutions.com) |
Health workers, admins, families | The staging branch, released by hand: frontend, backend, Caddy and this guide as Docker containers on the production VM. |
| QC (alpha) | QA and the client’s testers | The consolidate/jun branch. A separate Compose project on the same VM with its own ports and database. It carries a full translation dictionary that production does not. |
| Mobile backend | The Flutter app in sb-mobile/ |
The mobile-backend branch on a different host. The mobile app never talks to the production API. |
| Local | You | Whatever you have checked out. See Running it locally. |
The QC database was copied from production once and has drifted since. It holds real family data; treat it as sensitive.
Branch to environment map
Section titled “Branch to environment map”| Branch | Worktree folder | Environment | Purpose |
|---|---|---|---|
staging |
swarnabindu-prod |
Production (manual release) | Integration branch for production work |
main |
swarnabindu-prod |
none | Released history only. Fast-forwarded from staging after validation |
consolidate/jun |
swarnabindu-alpha |
QC | QA environment |
mobile-backend |
swarnabindu |
Mobile backend | Backend the Flutter app talks to |
All four are git worktrees of one repository. A fix written on one branch is cherry-picked to the others, not rewritten.
Release steps
Section titled “Release steps”-
Land the change on
staging(never onmain). -
Port the same change to
consolidate/junandmobile-backendwithgit cherry-pick. -
Deploy QC first and check it there. QC is what QA looks at, and it has the translation layer, so a template that calls
$store.i18n.t(...)works on QC and throws on production. Check shared components on both. -
Look at what you are about to release:
git log --oneline main..staging. A fast-forward releases everything queued onstaging, not just your fix. Read the list out before you continue. -
Fast-forward
main:Terminal window git switch main && git merge --ff-only staging && git push origin main -
Release production. Either run the GitHub Actions workflow:
Terminal window gh workflow run deploy.yml --ref main -f confirm=deployor, while hosted runners are unreliable, build on the VM directly from the
stagingworktree:Terminal window scripts/deploy-vm.sh prod both -
Verify with evidence, not with a green tick: the backend
healthzanswers, the served commit hash on the landing page has changed, and the behaviour you shipped works in a browser against production.
The CI workflow and its gates
Section titled “The CI workflow and its gates”.github/workflows/deploy.yml is the documented release path. It has no push trigger. It runs only on workflow_dispatch, and only when two gates pass:
- The repository variable
CI_DEPLOY_ENABLEDmust betrue. This is a kill switch; while it is unset every job skips silently. - The
confirminput must be the literal worddeploy, so a stray click does nothing.
When it runs it builds sb-be:latest from backend/ and sb-fe:latest from frontend/, pushes both to GitHub Container Registry, then connects to the production VM over SSH and runs docker compose pull && docker compose up -d in the infra repository’s folder. The SSH step sets script_stop: true so the first failing command fails the job. Why: without it, a bad cd once left production frozen on an old build for five weeks while every run reported success.
The QC deploy workflow lives on consolidate/jun, not on staging.
The VM-direct deploy script
Section titled “The VM-direct deploy script”scripts/deploy-vm.sh <prod|alpha> [frontend|backend|both|docs] exists because hosted GitHub runners for this repository have queued for 40 minutes or failed to start at all. It does the workflow’s job from your machine:
- Refuses to run if your working tree is dirty. Commit first.
- Tars
frontend/,backend/ordocs-site/(withoutnode_modules,bin,obj,dist) and streams it over SSH into a source folder on the VM. That folder is deleted first, so the image can only contain what you shipped. Why: tar extraction never removes files a branch has deleted, and production once served a page no branch contained. - Runs
docker buildon the VM with the right tag and, for the frontend, the rightPUBLIC_API_URL. - Runs
docker compose up -din the right compose folder and prunes old images.
Run it from the worktree whose code you want deployed: prod from swarnabindu-prod on staging, alpha from swarnabindu-alpha on consolidate/jun.
Per-environment frontend builds
Section titled “Per-environment frontend builds”Astro reads PUBLIC_API_URL at build time and bakes it into the JavaScript. Production is built with the relative value /api, so the browser calls whichever host served the page and Caddy routes it. QC has no proxy in front, so it is built with an absolute URL to its own backend port.
So one frontend image cannot serve two environments. Never retag sb-fe across environments, and never build it without the build argument (the default points at localhost). A wrongly built frontend still renders every page; only the data calls fail, so the site looks up while nothing loads. The backend image has no such coupling and is safe to retag.
Where things live on the production stack
Section titled “Where things live on the production stack”Paths are relative to the infra repository’s checkout on the VM; hosts and credentials are deliberately not written here.
| Thing | Location |
|---|---|
| Compose file and Caddyfile | Repository root and its caddy/ folder, bind-mounted into containers. |
| SQLite database | apps/sb/data/app.db, mounted into the backend as /app/data. This one file is the whole system; copy it before any risky operation. |
| Backend logs | apps/sb/logs/, mounted as /app/logs. One compact-JSON file per day, rolling at 256 MB. Grep by TraceId. Container output is also visible through Dozzle. |
| Caddy TLS state | Docker volumes caddy_data and caddy_config. |
Never sed -i the bind-mounted Caddyfile (a new inode breaks the mount). Write a temp file, cat it over the original, validate, then reload.
Backups
Section titled “Backups”A daily snapshot of the production database is taken at 20:30 Nepal time with SQLite’s online backup API (sqlite3 .backup), which is atomic and does not interrupt the app. The compressed copy goes to cloud storage and to a db_backups branch of the infra repository. That branch is kept off main so the deploy script’s git pull never drags snapshots onto the VM; never merge it. The job currently runs from a maintainer’s machine, a known gap tracked for migration to the VM. Proof that backups work is a recent dated file, not the existence of the job.
How this guide is built and published
Section titled “How this guide is built and published”This site is the docs-site/ folder of the same repository: a Starlight (Astro) project with plain Markdown pages under src/content/docs/.
scripts/deploy-vm.sh prod docsshipsdocs-site/to the VM and runs its Dockerfile there.- The Dockerfile installs dependencies, runs
npm run check:words(the build fails if a banned word appears anywhere insrc/orpublic/), runsastro build, and copies the static output into a small Caddy image taggedsb-docs:latest. - The infra repository’s compose file runs that image as the
docsservice. - The edge Caddy terminates TLS for
sbdocs.flywingsitsolutions.comand proxies to thedocscontainer.
There is no QC copy of the guide; it describes what production does today.