Skip to content

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.

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 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.

  1. Land the change on staging (never on main).

  2. Port the same change to consolidate/jun and mobile-backend with git cherry-pick.

  3. 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.

  4. Look at what you are about to release: git log --oneline main..staging. A fast-forward releases everything queued on staging, not just your fix. Read the list out before you continue.

  5. Fast-forward main:

    Terminal window
    git switch main && git merge --ff-only staging && git push origin main
  6. Release production. Either run the GitHub Actions workflow:

    Terminal window
    gh workflow run deploy.yml --ref main -f confirm=deploy

    or, while hosted runners are unreliable, build on the VM directly from the staging worktree:

    Terminal window
    scripts/deploy-vm.sh prod both
  7. Verify with evidence, not with a green tick: the backend healthz answers, the served commit hash on the landing page has changed, and the behaviour you shipped works in a browser against production.

.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_ENABLED must be true. This is a kill switch; while it is unset every job skips silently.
  • The confirm input must be the literal word deploy, 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.

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:

  1. Refuses to run if your working tree is dirty. Commit first.
  2. Tars frontend/, backend/ or docs-site/ (without node_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.
  3. Runs docker build on the VM with the right tag and, for the frontend, the right PUBLIC_API_URL.
  4. Runs docker compose up -d in 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.

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.

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.

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.

This site is the docs-site/ folder of the same repository: a Starlight (Astro) project with plain Markdown pages under src/content/docs/.

  1. scripts/deploy-vm.sh prod docs ships docs-site/ to the VM and runs its Dockerfile there.
  2. The Dockerfile installs dependencies, runs npm run check:words (the build fails if a banned word appears anywhere in src/ or public/), runs astro build, and copies the static output into a small Caddy image tagged sb-docs:latest.
  3. The infra repository’s compose file runs that image as the docs service.
  4. The edge Caddy terminates TLS for sbdocs.flywingsitsolutions.com and proxies to the docs container.

There is no QC copy of the guide; it describes what production does today.