Getting started (local)
Run the full NexSchool stack locally in Docker — API, database, queues, and both Next.js frontends — then layer on migrations, seed data, and the Expo mobile app.
Prerequisites
The entire stack runs in Docker. You do not need Python, PostgreSQL, or Redis installed locally.
| Tool | Version | Purpose |
|---|---|---|
| Docker Desktop | Latest | Run all services via Compose |
| Docker Compose plugin | v2 | Multi-service orchestration |
| Node.js | 20+ | Run frontends outside Docker (optional) |
| Git | Any | Clone the repo |
Repository layout
The infra repo (school-erp-infra/) uses sibling path mounts — its Docker Compose volumes reference ../server, ../admin-web, and ../panel relative to school-erp-infra/. All repos must sit at the same directory level:
your-workspace/
├── school-ERP/ ← monorepo (this repo)
│ ├── server/
│ ├── admin-web/
│ ├── panel/
│ ├── client/
│ └── school-erp-infra/
└── (other projects)Step 1 — Environment file
cd school-ERP/school-erp-infra
cp env/.env.local.example env/.env.localEdit env/.env.local with your values:
# Database
DATABASE_URL=postgresql://postgres:postgres@postgres:5432/school_erp
# Redis
REDIS_URL=redis://redis:6379/0
CELERY_BROKER_URL=redis://redis:6379/0
CELERY_RESULT_BACKEND=redis://redis:6379/0
# Flask
FLASK_ENV=development
JWT_SECRET=your-local-secret-change-this
SECRET_KEY=your-flask-secret
# Frontend → API
NEXT_PUBLIC_API_URL=http://localhost/api
NEXT_PUBLIC_GATEWAY_ORIGIN=http://localhost
# Firebase (admin-web push notifications — optional for local dev)
NEXT_PUBLIC_FIREBASE_API_KEY=
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=
NEXT_PUBLIC_FIREBASE_PROJECT_ID=
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
NEXT_PUBLIC_FIREBASE_APP_ID=See /engineering/environment-variables for the full variable reference.
Step 2 — Start all services
From school-erp-infra/:
make dev
# Equivalent:
docker compose -f docker-compose.local.yml up --buildYou can also start it from the monorepo root with make infra-dev.
Services & ports
| Service | Port | Notes |
|---|---|---|
nginx | 80 | Gateway — all traffic enters here |
api | 5001 (internal) | Flask + Gunicorn |
postgres | 5432 | PostgreSQL 16 |
redis | 6379 (internal) | Celery broker |
celery-worker | — | Background tasks |
celery-beat | — | Scheduled tasks |
admin-web | 3000 (internal) | Next.js school admin |
panel | 3001 (internal) | Next.js super-admin |
URLs after startup:
| URL | Service |
|---|---|
http://localhost/ | School admin (admin-web) |
http://localhost/api/ | Flask API |
http://localhost/api/health | Health check |
http://panel.localhost/ | Super-admin panel (see Step 3) |
http://localhost:5001/api/ | API direct (bypasses nginx) |
http://localhost:5432 | PostgreSQL direct |
Step 3 — Panel subdomain (one-time)
The super-admin panel runs on panel.localhost. Add it to your /etc/hosts:
127.0.0.1 panel.localhostThen open http://panel.localhost/.
Step 4 — Run database migrations
# Option A: via Docker exec
docker compose -f docker-compose.local.yml exec api flask db upgrade
# Option B: if running the API locally outside Docker
cd server
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
FLASK_APP=app:create_app flask db upgradeStep 5 — Seed RBAC data
docker compose -f docker-compose.local.yml exec api python scripts/seed_rbac.pyThis seeds:
- Default permissions (all
resource.actionstrings) - Default roles (
admin,teacher,student,finance_staff) - Role-permission assignments
Step 6 — Mobile app (Expo)
The mobile app is not started by Docker Compose. Run it separately:
cd client
npm install
npm start # Starts Expo dev server
# Scan QR with Expo Go app on your phone
# OR press 'i' for iOS simulator, 'a' for Android emulatorPoint the app at your local API via client/.env (or client/config/):
EXPO_PUBLIC_API_URL=http://192.168.x.x/api # Your LAN IP (for a physical device)
# OR
EXPO_PUBLIC_API_URL=http://localhost/api # For emulatorsRunning individual services without Docker
Backend only
cd server
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
export FLASK_APP=app:create_app
export FLASK_ENV=development
export DATABASE_URL=postgresql://localhost/school_erp
export REDIS_URL=redis://localhost:6379/0
export JWT_SECRET=dev-secret
flask run --port 5001Admin web only
cd admin-web
npm install
npm run dev # http://localhost:3000Panel only
cd panel
npm install
npm run dev # http://localhost:3001Common commands
Makefile shortcuts from the monorepo root:
make infra-dev # local stack (docker compose local up)
make infra-prod # build production-like stack from source
make infra-deploy-prod # git pull apps + prod compose up --build
make infra-deploy-ecr # pull ECR images + up (AWS)Stopping services (from school-erp-infra/):
# Stop all
docker compose -f docker-compose.local.yml down
# Stop and wipe database volumes
docker compose -f docker-compose.local.yml down -vTroubleshooting
| Problem | Solution |
|---|---|
| Port 80 in use | Stop Apache/Nginx/other on host, or change the nginx port in the compose file |
panel.localhost not resolving | Add 127.0.0.1 panel.localhost to /etc/hosts |
| DB migrations fail | Check DATABASE_URL in env, ensure the postgres container is healthy |
CORS error in browser | Ensure NEXT_PUBLIC_API_URL matches what the browser can reach |
| Hot reload not working | Ensure volume mounts in the compose file point to the correct sibling paths |
| Celery tasks not running | Check the redis connection and CELERY_BROKER_URL in env |
| API returns 403 on all routes | Run seed_rbac.py; check JWT_SECRET matches between env and code |