Skip to main content

Infrastructure

SyncAD runs on AWS with Docker-based deployments and GitHub Actions for CI/CD.

AWS Services

ServicePurpose
RDS PostgreSQLCentral DB + per-school DBs. db.t3.medium minimum, Multi-AZ in production
S3File storage: student photos, announcement attachments, library book covers
SESTransactional email: OTP delivery, fee receipts, announcements
SNSSMS + Push: OTP via SNS → SMS, push notifications → FCM
Route53DNS management for school subdomains ({slug}.syncad.in)
ACMSSL certificates via Let's Encrypt (wildcard *.syncad.in)
ECRContainer registry for API Docker images
ECS FargateContainer hosting for NestJS API (no EC2 management)
CloudFrontCDN for static assets (Next.js UIs)
ElastiCache RedisSocket.IO adapter, OTP storage, session cache
CodeBuildCI/CD build pipeline
CodeDeployStaged deployments to ECS

Database Connectivity

All PostgreSQL connections use TLS:

-- RDS parameter group
ssl = on
ssl_ca = /rds-combined-ca-bundle.pem

Connections are pooled via pg-pool (max 10 connections per school DB per API instance).

Storage — S3 Presigned URLs

Files are never uploaded through the API directly. Instead:

// 1. Client requests a presigned upload URL
POST /school-admin/upload/presign
{ "fileName": "student-photo.jpg", "contentType": "image/jpeg" }

// Response
{
"uploadUrl": "https://s3.amazonaws.com/bucket/key?X-Amz-Signature=...",
"publicUrl": "https://cdn.syncad.in/students/photo.jpg"
}

// 2. Client uploads directly to S3 using presigned URL
PUT https://s3.amazonaws.com/bucket/key?X-Amz-Signature=...
Content-Type: image/jpeg

<binary data>

Email — SES

// AWS SES for OTP and notifications
await sesClient.sendEmail({
Source: 'noreply@syncad.in',
Destination: { ToAddresses: [email] },
Message: {
Subject: { Data: 'Your SyncAD OTP' },
Body: { Html: { Data: `<p>Your OTP is: <b>${otp}</b></p>` } },
},
});

Push Notifications — SNS → FCM

// Send push notification to parent device
await snsClient.publish({
TargetArn: deviceEndpoint.EndpointArn,
Message: JSON.stringify({
default: 'New announcement from school',
GCM: JSON.stringify({
notification: {
title: 'SyncAD',
body: 'New announcement: Mid-term exams schedule',
click_action: 'OPEN_ACTIVITY',
},
data: { type: 'ANNOUNCEMENT', id: announcementId },
}),
}),
MessageStructure: 'json',
});

Docker

API Dockerfile

# apps/api/Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build --filter api

FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/apps/api/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3001
CMD ["node", "dist/main.js"]

docker-compose (local dev)

# docker-compose.yml (in syncad root)
services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_PASSWORD: password
ports:
- '5432:5432'
volumes:
- postgres_data:/var/lib/postgresql/data

redis:
image: redis:7-alpine
ports:
- '6379:6379'

volumes:
postgres_data:

GitHub Actions CI/CD

# .github/workflows/deploy-api.yml
name: Deploy API

on:
push:
branches: [main, dev]
paths: ['apps/api/**']

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v3
with: { version: 8 }

- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm

- run: pnpm install --frozen-lockfile
- run: pnpm build --filter api
- run: pnpm test --filter api

- name: Build and push Docker image
run: |
aws ecr get-login-password | docker login --username AWS --password-stdin $ECR_REGISTRY
docker build -t $ECR_REGISTRY/syncad-api:$GITHUB_SHA -f apps/api/Dockerfile .
docker push $ECR_REGISTRY/syncad-api:$GITHUB_SHA

- name: Deploy to ECS
run: |
aws ecs update-service --cluster syncad --service api --force-new-deployment

Environment Variables

VariableDescriptionExample
DATABASE_URLCentral DB connection stringpostgresql://user:pass@rds:5432/syncad_central
JWT_SECRETJWT signing secret (256-bit)your-secret-here
JWT_EXPIRES_INAccess token expiry15m
REFRESH_TOKEN_EXPIRES_INRefresh token expiry7d
AWS_ACCESS_KEY_IDAWS credentials
AWS_SECRET_ACCESS_KEYAWS credentials
AWS_REGIONAWS regionap-south-1
REDIS_URLRedis connectionredis://redis:6379
SES_FROM_EMAILVerified sender emailnoreply@syncad.in
S3_BUCKETAssets bucket namesyncad-assets
S3_CDN_URLCloudFront CDN URLhttps://cdn.syncad.in

Deployment Environments

EnvironmentTriggerURL
localpnpm devlocalhost:3001
devpush to dev branchdev-api.metaonus.in
stagingpush to main branchstaging-api.metaonus.in
productiongit tag v*.*.*api.metaonus.in