ArchitectureInfrastructure & deploy

Infrastructure & deploy

One-line: the app runs in Docker behind Nginx on AWS EC2 (ap-south-1); GitHub Actions builds images to ECR and deploys via SSM; the marketing site is separate on Vercel.

This page describes production topology as understood in mid-2026. Infra changes often — treat specifics (IPs, security-group ids, migration heads) as a snapshot and verify against the infra repo and AWS before acting.

Runtime topology

                     Spaceship DNS
   nexchool.in, www ─────────────► Vercel (landing-page, TLS by Vercel)
   api / panel / *  ─────────────► AWS EC2  ──► Nginx ──► Docker Compose
                                                            ├── Flask API (gunicorn)
   *.nexchool.in (tenant subdomains) ─► admin-web           ├── Postgres (RDS)
                                                            ├── Redis (cache + broker)
                                                            └── Celery workers
  • Marketing site (nexchool.in, www) is Vercel-only — separate from the app. app.nexchool.in was dropped; the app is reached via tenant subdomains (<slug>.nexchool.in).
  • App + API run on EC2 (account 774493573217, region ap-south-1), fronted by Nginx with TLS. The wildcard * record serves tenant subdomains through admin-web.
  • Database is PostgreSQL on RDS; Redis provides both the cache layer and the Celery broker.

CI/CD

  • GitHub Actions build Docker images and push to ECR, then deploy to EC2.
  • Deploys run via AWS SSM RunCommand (least-privilege role) — not SSH. Port 22 is closed; a manual shell is aws ssm start-session.
  • The mobile client releases on its own cadence (Expo OTA / store), gated by commit tokens ([ota] / [store-release]) — the server/admin-web deploy never triggers a mobile release.
  • Database migrations (Flask-Migrate / Alembic) are applied as part of the coordinated deploy; RDS snapshots are taken before risky ship steps.

Local development

Everything runs via Docker Compose from the infra repo (school-erp-infra/). See the maintained setup docs in the server/ repo for step-by-step. Environment variables for every service are documented alongside the code (.env.example files); never commit real secrets.

Security posture (as hardened)

  • Internet-exposed DB access closed; DB reachable only from the app tier.
  • AWS auth moved from static IAM keys → instance role; the old key deactivated. CI uses OIDC, not long-lived keys.
  • SSH replaced by SSM; port 22 revoked.
  • Secrets were history-swept across repos; use env vars / secrets manager, never source. See the security guardrails principles.

Known operational notes

  • ADMIN_WEB_BASE_URL must be set in prod, or password-reset and one-click login links point at localhost.
  • SSE + gunicorn — real-time notification streams hold a worker thread; on a low worker/thread count this can starve request handling. Size the thread count accordingly (mitigated in prod). See Notifications.
  • Email goes through AWS SES; exiting the SES sandbox is required for arbitrary-recipient prod email.