Deployment¶
This section describes how tenant subdomains are created and served in production, hosting considerations, and the third-party integrations the platform depends on. It contains no secrets or environment-specific addresses.
Domain map¶
| Hostname | Serves | Where |
|---|---|---|
ndakita.com |
the company site | Vercel, ndakita_group_site |
build.ndakita.com |
the Ndakita Build product site | same Vercel deployment, mapped by middleware |
{slug}.build.ndakita.com |
one tenant ERP each | the application server, Frappe DNS multitenancy |
givehaven.ndakita.com |
GiveHaven | Netlify, givehaven-v2 |
email.ndakita.com |
Brevo branded email links | Brevo, not a website |
<base-domain> below is build.ndakita.com. Everything sits under one
registered domain, so a single Search Console domain property covers the lot
and the marketing site, the product site and the customer's own instance all
read as one brand.
Subdomain provisioning¶
Tenant subdomains are served through a single wildcard configuration rather than per-tenant DNS entries.
One-time configuration¶
- Wildcard DNS. A wildcard A record (
*.<base-domain>) and the apex record point to the application server. Every tenant subdomain, including those not yet created, then resolves. - Wildcard TLS certificate. A single certificate for
*.<base-domain>is issued through the DNS-01 challenge and renewed automatically. A wildcard certificate avoids per-certificate issuance limits that would otherwise be reached as the tenant count grows. - DNS-based multitenancy. Frappe is configured for DNS multitenancy so the web server routes each request to the site whose name matches the request host.
Per-tenant steps (automated during provisioning)¶
bench new-site {slug}.<base-domain> ...
bench setup nginx
systemctl reload nginx
Because DNS and TLS are covered by the wildcard configuration, no per-tenant DNS or certificate action is required. The tenant address becomes reachable immediately after the site is created and the web server is reloaded.
The platform service is granted a narrowly scoped privilege to reload the web server only, rather than broad administrative access.
Hosting¶
ERPNext is memory-intensive; each tenant maintains its own database while sharing the bench worker processes. A single mid-sized virtual server (for example, 8 vCPU and 16 GB of memory) is sufficient for the platform service, the metadata database, and an initial set of tenants. The system scales first by increasing server capacity, then by separating the database onto a dedicated host and moving provisioning to background workers.
Provider selection balances cost, latency to the target market, and data residency. Providers with an African region offer lower latency and local data residency, which can be a customer requirement, at a higher cost than value-oriented European or North American providers.
Production readiness checklist¶
- Point the platform database at PostgreSQL and apply migrations.
- Enable real provisioning on the host that runs the bench.
- Configure strong platform secret and API keys, and a restricted list of allowed origins.
- Configure the transactional email sender and the verification link address so sign-up confirmation emails are delivered.
- Configure webhook signing secrets and any callback IP allowlists.
- Enable asynchronous execution: run the Celery worker and the beat scheduler, and set the rate-limit store to the shared (Redis) backend so limits hold across workers.
- Verify the Frappe scheduler is running on each tenant site. This is a
different thing from the platform's Celery beat above, and provisioning does
not currently enable it explicitly. Two customer-facing features depend on
it and both fail silently without it: the daily low-stock alert to site
managers (
ndakita_build.custom.reorder_alert) and the 30-day client document expiry reminder. Nothing errors; the emails simply never arrive. Check withbench --site <slug>.<base-domain> doctor, and enable withbench --site <slug>.<base-domain> enable-scheduler. - Schedule the tenant backup sweep and configure the off-box backup shipping and retention (see below).
- Complete the wildcard DNS, wildcard certificate, DNS multitenancy, and scoped web-server reload configuration.
- Turn on Web Analytics for the marketing-site project in the Vercel
dashboard. There is no environment variable:
<Analytics />inapp/layout.tsxdoes the rest, and it only reports from a Vercel deployment. Custom events need the Pro plan; page views do not. - Set
FORMSPREE_PROJECT_IDon the marketing-site build (Vercel) so lead forms submit; forms are defined in the site'sformspree.jsonand deployed withnpx @formspree/cli deploy(needs the secretFORMSPREE_DEPLOY_KEY, which never leaves the developer machine). - Set
NEXT_PUBLIC_SITE_URLon the marketing-site build. Canonical tags,og:url,og:imageand every cross-host link derive from it. Left unset it falls back tohttps://ndakita.comfor metadata, butcompanyUrl()andbuildUrl()stay relative, and a relative company link rendered on the Build host resolves into the/buildtree and 404s. - Decide whether the apex or
wwwis primary, and make the two agree. Today they disagree:NEXT_PUBLIC_SITE_URLis the apex, so every canonical,og:url,og:imageand sitemap entry nameshttps://ndakita.com, while Vercel's domain configuration 308s the apex tohttps://www.ndakita.com. Nothing is broken for a visitor, but every canonical points at a redirect rather than at the document that serves the page, which is the same class of problem as the site-wide canonical bug fixed in July. Fix it in whichever direction is wanted: set the apex as the primary domain in Vercel, or setNEXT_PUBLIC_SITE_URL=https://www.ndakita.com. Do not fix only one side. - Set
ASSESSMENT_ENDPOINTon the marketing-site build if ERP-assessment leads should go to Apps Script or the platform rather than to theassessmentFormspree form. Unprefixed on purpose; see the security notes. - Set
NEXT_PUBLIC_PREVIEW_DATA=trueon the GiveHaven (givehaven-v2) build for as long as the homes and needs it serves are seeded rather than real, and remove it at launch. It drives the "Preview build — test data" bar at the top of every page. The bar was previously gated onNODE_ENV !== "production", which meant it only ever showed on a developer's own machine: every deployed build is a production build, so the one audience it exists for — anyone openinggivehaven.ndakita.comon a phone — never saw it, and the seeded homes read as real. Whether the data is real is a property of the environment, not of the build mode, so it needs its own flag. Localnext devshows the bar with or without the variable; set it tofalseto suppress it there. - Set
TURNSTILE_SITE_KEYandTURNSTILE_SECRET_KEY(Cloudflare Turnstile) on the marketing site so the lead forms are captcha-protected and bots cannot exhaust the Formspree submission quota; without them the forms still work but unprotected. Set both or neither. With the secret set and no site key reaching the browser the widget never renders,/api/leadrejects every submission as a failed captcha, and lead capture silently drops to zero. Set the unprefixedTURNSTILE_SITE_KEY; the build republishes it asNEXT_PUBLIC_TURNSTILE_SITE_KEY, and setting that prefixed name directly has no effect.
Backups and recovery¶
Every tenant with a live site is backed up on a schedule that runs on the beat scheduler, capturing the database and files and recording each outcome. Local backups on the bench host are shipped off-box for retention; that off-box copy, not the local files, is what satisfies the retention target. Restores and a whole-host rebuild follow a documented procedure, and recovery is validated periodically by restoring into a scratch site. The recovery objectives, the off-box shipping approach, and the exact steps are maintained in the platform repository's disaster-recovery document.
Third-party integrations¶
| Integration | Purpose | Notes |
|---|---|---|
| M-Pesa (Daraja) | Primary payment method for the target market | Provides sandbox and production environments. Production access requires a registered short code and provider approval. |
| Stripe | International card payments | Provides test and live modes. |
| Transactional email | Sign-up verification, welcome messages, receipts | Requires sender-domain authentication (SPF and DKIM). |
All integration credentials are supplied through environment variables or a secrets manager and are never committed to a repository.