Skip to content
DDelTech MUNDocs

Architecture

How a request travels from the edge to the database, and what talks to what.

Next.js 16 App Router, React 19, Prisma 7 on PostgreSQL 17, NextAuth v5, Tailwind v4. One application, run as a single Docker container per environment on AWS Lightsail, behind Caddy.

The request path

LIGHTSAIL BOX · DOCKER COMPOSEBrowserHTTPSCaddyTLS, Host routingapp containerNext.js standalonePostgres 17loopback onlySSE realtime busin the app processS3media, backupsResend / SESemail

GitHub Actions builds the image, ships it over SSH and switches the container.

Each environment is one box. Caddy terminates TLS, one Next.js container serves every route, and Postgres is only reachable from inside the box.

Inside the container a request goes: src/proxy.ts for the five protected prefixes, then the route group's layout.tsx guard, then the page or a colocated actions.ts, then Prisma.

The two environments

ProductionStaging
Hostwww.deltechmun.in (apex redirects to www) and docs.deltechmun.intest.deltechmun.in, noindex
Boxmun-prod, Lightsail Sydneymun-staging, Lightsail Sydney
Branchmainstaging
Databasemun_prod on its own boxmun_staging on its own box
Mediadeltechmun-media-prod (S3, ap-south-1)deltechmun-media-staging

They share nothing at runtime. Each GitHub Environment holds only its own box's address and host key, so a staging deploy cannot reach production.

Five things that will surprise you

Exactly one app container per environment, permanently. The quiz cache (src/lib/quiz-cache.ts) and the realtime bus (src/lib/realtime/bus.ts) both live in process memory. A second container would split them. Scaling out needs a shared cache handler and an external bus first.

Middleware is called proxy.ts. Next 16 renamed it. Its matcher covers only /admin, /write, /dashboard, /account and /recruitment.

The standalone server does not know its own URL. Its request.url is the bind address, http://0.0.0.0:3000. The image pins AUTH_URL and sets AUTH_TRUST_HOST; never build an absolute redirect from req.nextUrl. See authentication.

There is no test framework. Thirty-six standalone node:assert scripts plus a strings gate, run by npm run check. See testing.

No component may contain a hardcoded string, and no source file an em dash. Both are enforced in CI. See the strings rule.

What the app depends on

DependencyUsed forIf it is down
Postgres (container on the box)All relational dataHard dependency. /api/health returns 503.
S3Blog images, team photos, recruitment documents, hourly database backupsUploads fail with a typed "not configured" error; existing pages still render.
Resend or SESAll outbound email, chosen by EMAIL_TRANSPORTFailures are logged to EmailLog; the action still succeeds.
RazorpayCard, netbanking and UPI paymentsFalls back to UPI QR or a static link by configuration.
GroqImport column suggestionsOptional.
GitHub ActionsDeploys, scheduled cron calls, health checksThe site keeps running; nothing new ships and crons do not fire.

Realtime used to be Supabase. It is now /api/realtime, Server-Sent Events from the app process itself. See the quiz system.

The infrastructure runbook

This site explains how the code works. Operating the boxes (creating one, restoring a backup, growing to 2 GB, rotating keys) is in docs/AWS.md in the repository.

Where to go next