Skip to content

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