Skip to content

Current Architecture (main)

The main branch implements a single-tenant-per-infrastructure model. Each client gets their own dedicated buyer portal on Cloudflare Pages and their own Directus CMS container on a shared VPS.

  • Runtime: Astro 5 SSR with @astrojs/node adapter (Node.js standalone)
  • Database: Direct PostgreSQL connection via pg pool + Drizzle ORM
  • Hosting: Cloudflare Pages (one project per client)
  • Auth: Lucia v3 with Drizzle adapter, passwordless OTP (email/WhatsApp)
  • UI: Preact islands for interactive components, Astro components for static parts
  • System: Directus 11 (headless CMS)
  • Deployment: Docker Compose via Dokploy on shared VPS
  • Database: Dedicated PostgreSQL database per client (same VPS instance)
  • Auth: Directus built-in auth + Cloudflare Access for CRM protection
  • File Storage: Directus-managed uploads (disk or R2 via Directus S3 adapter)

Each client requires:

  1. Create Cloudflare Pages project — scripts/cloudflare-pages-setup.mjs
  2. Provision PostgreSQL database — scripts/provision-client-postgres.sh
  3. Deploy Directus via Dokploy — scripts/dokploy-deploy-client.mjs
  4. Configure Cloudflare Tunnel — scripts/cloudflare-tunnel-setup.mjs
  5. Configure Cloudflare Access — scripts/cloudflare-access-setup.mjs
  6. Run schema setup — scripts/setup-directus-schema.mjs

Automation: deploy:client npm script orchestrates the full flow.

Buyer Action ──> Cloudflare Pages ──> Astro SSR (API route) ──> pg pool ──> PostgreSQL
│
v
Directus API read ──> PostgreSQL
(unit/agent/portal data)
  • Each new client requires infrastructure provisioning (~30 min)
  • Multiple Points of failure per client (Directus container, tunnel, Pages project)
  • Directus adds operational overhead (CMS container management)
  • Node.js runtime on Pages Functions has cold-start limitations
  • No built-in multi-tenant data isolation (separate databases)