Infrastructure Architecture
Application topology, request flows, tenant trust boundaries, and runtime boundaries.
Infrastructure Architecture
ByteBite is a modular monolith in a TypeScript monorepo. All business logic flows through a single Fastify API; storefront and dashboard are separate front-end applications. PostgreSQL is the sole primary database.
Monorepo topology
apps/
api/ Fastify 5 — central backend boundary
storefront/ Next.js 16 — public restaurant sites
dashboard/ React + Vite — operator UI (nginx in production)
docs/ Fumadocs / Next.js — this documentation
packages/
db/ Drizzle schema, migrations, seeds
domain/ Pure domain logic (hostname normalization, tenancy)
auth/ Better Auth (planned wiring)
schemas/ Zod schemas
types/ Shared TypeScript types
config/ Shared configuration
observability/ Sentry + pino loggingRuntime versions are pinned in Dockerfiles and package.json manifests (Node 24 LTS, pnpm 12.3.4). See Stack for the full application stack.
Runtime boundaries
| App | Dev runtime | Production runtime | Port |
|---|---|---|---|
| API | tsx watch | node server.js (tsup bundle) | 3001 |
| Storefront | next dev | Next.js standalone (node apps/storefront/server.js) | 3000 |
| Dashboard | Vite dev server | nginx unprivileged static | 8080 (prod) / 5173 (dev) |
| Docs | next dev | Next.js standalone | 3002 |
Storefront and dashboard never connect to PostgreSQL directly. All data access goes through the API.
External request flow (staging)
Browser
Host: luigi.staging.getbytebite.co
-> DNS (Cloudflare) -> 88.99.0.118
-> Traefik :443 (TLS termination)
-> storefront:3000 (Docker, dokploy-network)For same-origin API calls:
Browser
GET https://luigi.staging.getbytebite.co/api/health
-> Traefik (Host + PathPrefix `/api`, priority 20)
-> StripPrefix `/api`
-> api:3001
-> Fastify sees GET /health
-> Tenant from Host header (luigi.staging.getbytebite.co)Explicit Dokploy Host() routers take precedence over wildcard HostRegexp routers (lower priority). See Traefik.
SSR internal API flow
Storefront server-side rendering cannot use the browser's public hostname when calling the API container. It uses Docker DNS:
Next.js SSR (storefront container)
fetch(API_INTERNAL_URL + path) # default http://api:3001
Header: X-ByteBite-Tenant-Host: luigi.staging.getbytebite.co
Host: api # internal Docker service name
-> Fastify
-> resolveEffectiveHostname()
request Host is "api" (internal identity)
-> honor X-ByteBite-Tenant-Host
-> tenant lookup for luigi.staging.getbytebite.coAPI_INTERNAL_URL is set in compose.deploy.yml and local compose.yml. Browsers use same-origin /api via Traefik instead.
Tenant lookup and trust boundary
Tenant resolution is an exact hostname lookup in restaurant_domains after normalization (packages/domain). The first DNS label is not the tenant key.
| Request context | Tenant authority | X-ByteBite-Tenant-Host |
|---|---|---|
Browser / same-origin /api | Host header from Traefik | Ignored |
Public api.staging.getbytebite.co | Host header | Ignored |
Storefront SSR → http://api:3001 | Internal header (if Host is api) | Honored |
Fastify runs with trustProxy: false. External X-Forwarded-Host is not trusted. The internal header is accepted only when the request Host is one of: api, localhost, 127.0.0.1, ::1.
Implementation: apps/api/src/lib/tenant-host.ts, apps/api/src/plugins/tenant.ts.
Public callers cannot spoof tenant identity by setting X-ByteBite-Tenant-Host. The raw API port is not publicly exposed.
Database relationship
api:3001
-> DATABASE_URL (Dokploy env, internal network)
-> PostgreSQL (Dokploy-managed service)
-> restaurants
-> restaurant_domains (hostname -> restaurant_id)Migrations are explicit (node db/dist/migrate.js); the API does not auto-migrate on startup.
Wildcard staging tenants
Dynamic tenant hostnames (<slug>.staging.getbytebite.co) are routed by Traefik labels in compose.deploy.yml, not by Dokploy's domain UI (which rejects *.staging.getbytebite.co). DNS wildcard exists; a hostname resolving does not mean a restaurant row exists.
Unknown tenants:
- Storefront: renders "Restaurant not found" (currently HTTP 200 — application gap)
- API (tenant-aware routes): HTTP 404
/api/health: HTTP 200 regardless of tenant
Intentionally not used
| Technology | Status |
|---|---|
| Redis | Not present |
| BullMQ | Not present (ADR-016 uses DB-backed scheduler) |
| Kubernetes | Not present |
| GraphQL | Not present |
| Microservices | Not present — modular monolith |
Related
- Tenancy & storefront — application-level tenant model
- ADR-014 Tenant resolution
- Network — Docker networks and port exposure
- Security — trust boundaries and hardening