Deployment¶
The pipeline in one sentence¶
GitHub Actions builds Docker images and pushes them to Docker Hub; Portainer pulls them into a Docker Swarm; only the staging backend redeploys automatically, and everything else is a human clicking a button.
GitHub Actions¶
Five workflows, all on ubuntu-latest.
| Workflow | Trigger | Builds | Deploys |
|---|---|---|---|
backend developer.yml |
push to develop, or manual |
./backend → towitlinks:voip-backend-<tag\|staging> |
Yes — Portainer webhook PORTAINER_WEBHOOK_VOIP_BACKEND_DEV |
backend.yml |
manual only | ./backend → voip-backend-<tag>, default latest |
No — push only |
landing developer.yml |
push to develop, or manual |
repository root → voip-landing-<tag\|staging> |
No |
landing.yml |
manual only | repository root → voip-landing-<tag>, default latest |
No |
android.yml |
manual only | nothing locally | SSH to 154.53.40.104, runs ./build_apk/VoIP/rebuild_android.sh |
Images go to ${DOCKER_USERNAME}/towitlinks:<tag>. Both backend workflows write
backend/towitlinknotify-firebase-adminsdk-auth.json from the FIREBASE_SERVICE_ACCOUNT
secret at build time (backend.yml:38); that path is gitignored.
Secrets consumed: DOCKER_USERNAME, DOCKER_PASSWORD (and the older
DOCKERHUB_USERNAME / DOCKERHUB_TOKEN), FIREBASE_SERVICE_ACCOUNT,
PORTAINER_WEBHOOK_VOIP_BACKEND_DEV, SSH_PRIV_KEY, SSH_PUB_KEY, USER_IP_16.
The web console has no CI
frontend developer.yml and frontend.yml were deleted in commit b036a2d7. They
built ./frontend, injected STAGING_FRONTEND_ENV / FRONTEND_ENV and called
PORTAINER_WEBHOOK_VOIP_FRONTEND_DEV followed by PORTAINER_WEBHOOK_VOIP_NGINX_DEV.
Those four secrets still exist in the repository settings but nothing uses them. Console
deployments are entirely manual today.
No workflow runs tests, linting or type checking. Nothing gates a merge.
Docker images¶
| Image | Built from | Contents |
|---|---|---|
voip-backend-* |
backend/Dockerfile |
Python 3.11, uvicorn on 8000, entrypoint.sh loading Swarm secrets |
voip-landing-* |
root Dockerfile |
Node 18 build of landing/, served by pm2 on 3000 |
| console (production) | frontend/Dockerfile |
Static bundle only; entrypoint.sh rsyncs it into the /voip-frontend volume |
| console (staging) | frontend/Dockerfile.staging |
nginx 1.27 Alpine serving the bundle with frontend_staging.conf |
voip_apk_builder |
frontend/Dockerfile.build_apk |
Android SDK + Gradle, used on the build host |
Because the landing Dockerfile lives at the repository root, its build context is the
entire monorepo. It also copies frontend/ into the builder stage without using it
(Dockerfile:14-15).
Host-side scripts¶
These predate the Swarm and Portainer setup. All of them assume they are run from a parent
directory containing a VoIP checkout, and all begin with git pull.
| Script | What it does |
|---|---|
rebuild_and_run.sh |
Full stack: rebuild and restart the backend compose project, run migrate, then the frontend, then docker system prune -f |
rebuild_and_run_BE.sh |
The same, scoped to backend/ |
rebuild_and_run_FE.sh |
The same, scoped to frontend/ |
rebuild_and_run_landing.sh |
The same, scoped to the repository root compose file (the landing) |
rebuild_and_run_staging.sh |
Identical to rebuild_and_run.sh but against docker-compose-staging.yml (:16) |
rebuild_android.sh |
The APK and AAB build, see Mobile builds |
deploy.sh |
Legacy: pnpm-builds the console, restarts the backend compose project, migrates, then greps the logs for error. Uses Compose v1 container names |
Prefer the Portainer path for anything that already has one. Reach for these scripts when a host needs a build that CI does not produce.
Deploying a change today¶
Backend to staging — merge into develop. The workflow builds
voip-backend-staging and fires the Portainer webhook. Nothing else to do.
Backend to production — run backend.yml manually to publish voip-backend-latest,
then redeploy the service in Portainer. Migrations run from entrypoint.sh on container
start.
Console to either environment — build and ship by hand. There is no workflow. On
staging, rebuild_and_run_staging.sh is the shortest path; in production, rebuild the image
and let the entrypoint refresh the /voip-frontend volume.
Landing — run landing.yml manually, then redeploy voip-landing-page.
Mobile — run android.yml. See Mobile builds.
Documentation¶
This site deploys itself. .github/workflows/docs.yml builds docs/ with
mkdocs-material and publishes to the Cloudflare Pages project voip-docs on every push to
main that touches docs/**, mkdocs.yml or the workflow itself. It requires the
CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID repository secrets.
Preview locally with:
uvx --with mkdocs-material mkdocs serve