Skip to content
Infotech

Self-hosting and deployment

For Infotech's technical team: how Infotech School runs on a server, how changes reach production, the settings that matter, and backups. School staff never need this page. The full step-by-step guide is docs/SCHOOL_DEPLOYMENT_GUIDE.md in the repository; the files it uses are in deploy/.

How it fits together

One Ubuntu server runs everything. nginx on the host serves two domains over HTTPS:

DomainServes
school.infotech.centerThe web app (static files in /opt/school/current-web)
schoolapi.infotech.centerThe API, forwarded to 127.0.0.1:18081

The rest runs in the Docker project school (/opt/school/compose.yaml): api (Java 21), postgres (17), redis (7), clamav (virus scanning of every upload) and backup, plus optional minio and prometheus. Only ports 22, 80 and 443 are open; the API, Prometheus and the databases are reachable only from the server itself or Docker's private network. No source code lives on the server.

Server requirements

UseCPURAMDisk
Pilot, up to about 20 schools2 vCPU4 GB40 GB SSD
School and HRM on one server4 vCPU8 GB80 GB SSD
50 schools or more4–8 vCPU16 GB160 GB SSD

Use Ubuntu 24.04 LTS (22.04 also works), keep the server on UTC (the app handles each school's time zone), and pick a region near Bangladesh such as Singapore or Mumbai.

First-time setup

  1. Server basics: enable ufw (22, 80, 443), fail2ban and unattended upgrades; SSH keys only.
  2. DNS: A records school and schoolapi pointing to the server. With Cloudflare, keep them DNS-only until the certificates are issued.
  3. Setup script: copy the deploy folder to the server and run bash /root/school-deploy/scripts/server-setup.sh you@infotech.center. It installs Docker, nginx and certbot, creates the deploy user with a key used only by GitHub Actions, writes /opt/school/.env with random passwords, a new token-signing key and the encryption key, installs the nginx sites with Let's Encrypt certificates, and prints four values for GitHub. Safe to run again; it never overwrites secrets. For other domains set SCHOOL_WEB_HOST and SCHOOL_API_HOST before running it, and change API_HOST / WEB_HOST in .github/workflows/ci-cd.yml.
  4. GitHub secrets: add SSH_HOST, SSH_USER (deploy), SSH_PRIVATE_KEY and SSH_KNOWN_HOSTS. Optionally protect the production environment with required reviewers.
  5. First deploy: Actions › CI/CD › Run workflow on main.
  6. Check: /actuator/health answers UP, /.well-known/jwks.json lists a key, /actuator/prometheus answers 404 from outside.

The first deploy seeds the Infotech super admin (admin@infotech.center, school code infotech) and the demo-school accounts with the password SCHOOL_SEED_PASSWORD. Change these passwords and turn on two-step verification before real schools join.

Deploying with the CI pipeline

Every push to main runs four jobs; pull requests only test.

JobDoes
BackendAll tests on a real PostgreSQL, API contract check, builds app.jar
WebType check, lint, tests, formatting, production build
MobileAnalyze, tests, translations check; on main a test Android APK
DeployUploads the release to /opt/school/releases/<release>/, runs deploy.sh, smoke test over HTTPS

scripts/deploy.sh <release> sets SCHOOL_RELEASE in .env, starts the containers and waits up to 5 minutes for the API to be ready (database migrations run at start-up). When healthy it switches current-web to the new web files and keeps the last 5 releases. When not, it prints the API log and goes back to the previous release, so the old version keeps running.

Common commands, in /opt/school:

TaskCommand
API logsdocker compose logs -f --tail=200 api
Apply a changed .env./scripts/deploy.sh "$(grep ^SCHOOL_RELEASE= .env | cut -d= -f2)"
Roll back./scripts/deploy.sh <older release folder>

Environment settings

All settings and secrets are in /opt/school/.env (mode 600, never in git). The important ones:

VariableMeaning
SCHOOL_API_HOST, SCHOOL_WEB_HOSTThe two domains
SCHOOL_FIELD_ENCRYPTION_KEYEncrypts 2-step secrets, integration secrets and every uploaded file. Never change it; if lost, that data cannot be read, even from backups
SCHOOL_JWT_PRIVATE_KEY, SCHOOL_JWT_KEY_IDSign the sign-in tokens. Rotate only with the overlap steps in the guide, or everyone is signed out
SCHOOL_DB_OWNER_PASSWORD, SCHOOL_DB_APP_PASSWORDUsed when the database is first created; changing them later needs a change in PostgreSQL too
SCHOOL_SELF_SIGNUP_ENABLEDtrue lets schools register themselves
SCHOOL_MAIL_*SMTP for invitations and password resets. Empty: emails are only logged
SCHOOL_SMS_*The SMS gateway. log writes codes to the API log only; set up a real gateway before adding guardians
SCHOOL_PUSH_FCM_CREDENTIALS_BASE64Firebase key for push notifications; empty means every alert goes by SMS
SCHOOL_STORAGE_TYPElocal (Docker volume) or s3 (MinIO, R2 or S3). Decide before schools upload files

Note: Save the encryption key, the JWT key and the database passwords in the company password manager on the day you set up the server.

Backups

Every night at 21:30 UTC (03:30 in Dhaka) the backup container writes a database dump and a tarball of uploaded files to the school_backups volume and keeps them for SCHOOL_BACKUP_KEEP_DAYS (14). A backup on the same disk does not survive a lost server, so copy it off every night (cron on another machine, or rclone to S3/R2), and keep the encryption key with it.

  • Test a restore once a month: ./scripts/restore.sh <dump> restores into a separate test database and prints the number of schools and users.
  • Real restore: ./scripts/restore.sh <dump> --replace stops the API, replaces the database and starts it again (you must type replace).
  • Lost server: set up a new one, put the saved .env values in place before the first deploy, deploy, copy a backup into school_backups and restore with --replace.

Monitoring

Put /actuator/health on an uptime monitor. COMPOSE_PROFILES=monitoring adds Prometheus (SSH tunnel to port 9091). The console's Server status page shows memory, disk, load and the database (see Console overview).

The Server status page in the Infotech consoleThe Server status page in the Infotech console

Example

Moving from the pilot server. After 25 schools join, the team buys a 4 vCPU / 16 GB server, runs the setup script there with the same .env values, deploys, stops the old API, restores a fresh dump with --replace, copies the files volume, switches DNS and updates SSH_HOST and SSH_KNOWN_HOSTS in GitHub.

Last updated 30 Oct 2026