Skip to content

Architecture overview

You don’t need to know the internals to run Nudova — but three ideas explain every decision in these docs: it’s headless, it’s modular, and it runs in one of two footprints.

Nudova is a headless commerce API with two separate front ends in front of it — served on three hostnames from one deployment:

Host App Who uses it
your-domain.com Storefront (Next.js) — the shop your customers
admin.your-domain.com Staff admin (Next.js) — orders, catalog, CRM, content your team
api.your-domain.com API (Django) + the deep-ops console at /admin/ the apps, and you

A Caddy reverse proxy sits in front, terminates HTTPS (real Let’s Encrypt certificates, issued automatically), and routes each host to the right app. PostgreSQL is the database; a scheduler runs background jobs; a sidecar takes nightly backups.

Nudova is a single platform core plus a set of swappable domain modules. You install only the domains your business needs — the rest never load.

The platform (always present) is the reusable core every store gets: identity and accounts, users and profiles, files and media, notifications (email/SMS/push/in-app), payments, billing and invoicing, subscriptions and plans, discounts and referrals, an integration/webhook framework, an audit log, tenant configuration, and reporting.

The domains (install what you need):

Commerce

Catalog, cart, checkout, orders — the storefront business, and where the Nudge Framework lives.

CRM

Sales pipeline — accounts, contacts, leads, opportunities. A staff tool, no public storefront.

CMS

Content — pages, blocks, site settings, email templates, media.

Blog · Helpdesk · Kanban

A blogging vertical, a support/knowledge-base vertical, and a project/task board — each self-contained, installed only where wanted.

One environment variable composes the install:

Terminal window
ENABLED_DOMAINS=product,crm # platform + Commerce + CRM
ENABLED_DOMAINS You get
unset all domains (the default)
product platform + Commerce only
product,crm platform + those two
"" (empty) platform only, zero domains

The same image and code run in one of two footprints — a single flag, INFRA_PROFILE, decides the behaviour. You never re-platform to scale; you resize.

Lite (default) Full (opt-in)
Services API + Postgres (+ Caddy) + Redis (Valkey) + Celery worker + beat + websockets
Background jobs inline, strict timeouts async via Celery
Scheduled jobs cron loop (scheduler service) celery-beat (same job code)
Real-time off — the admin polls REST websockets (live feeds)
Sized for a 2 GB VPS a 2 vCPU / 8 GB box
Terminal window
docker compose up -d

The shipped docker-compose.yml is the lite stack.

Start lite for most single-tenant shops at low-to-medium volume. Choose full for high volume, real-time admin feeds, or when inline background work starts adding latency. Moving lite → full later is a one-flag change on the same data and image — see Upgrading → Moving lite → full.

The lite claim — “the whole product runs on a 2 GB VPS” — is a verified number, not a promise. A shipped check boots the lite stack under a realistic load (40 concurrent shoppers, browse/cart/checkout, plus a maximum-size image upload and concurrent server-rendering) and fails if usage crosses the budget or anything is OOM-killed.

Footprint Hard limits Measured peak under load OOM kills
Lite (2 GB box) 1952 MiB ~1037 MiB 0
Full (8 GB box) 5696 MiB ~1635 MiB 0
  • Requirements — the specs, and lite vs full.
  • Store settings & branding — the runtime config that makes one image serve any brand.
  • Upgrading — how a store takes a new version without ever editing core, and how to move lite → full.