Multi-tenant SaaS ERP/CRM platform for mobile entertainment companies — DJs, event production, staffing agencies, and equipment-rental operators.
Entertainment Express is a production-grade, multi-tenant Platform-as-a-Service built on Frappe/ERPNext. Every company ("tenant") runs as a fully isolated Frappe site with its own database, provisioned and governed by a dedicated control plane. The platform includes purpose-built client apps for crew, customers, and dispatch, and is delivered to a Kubernetes (K3s) cluster via Helm with automated, idempotent site bootstrapping.
| Domain | What it does |
|---|---|
| Control plane | Self-service tenant onboarding — Signup → approval → Provisioning Job → a brand-new isolated site. Plans, entitlements, and tenant lifecycle management. |
| Booking | Event bookings, holds/reservations with resource locking, line items, and asset assignment. |
| Scheduling & dispatch | Crew assignment, run sheets, equipment checklists, and at-risk-event flagging. |
| Workforce | Crew roster, availability, compliance documents, pay runs, and payout tracking. |
| Service catalog | Service packages, assets, and service-area coverage with travel-fee logic. |
| Billing & payments | Quotes, contracts (e-sign flow), deposits, and Stripe payment/webhook processing. |
| Client apps | Native crew app, customer portal, and dispatcher console. |
Tenancy model — one Frappe site per company. Each tenant's data (employees, clients,
equipment, bookings, billing) lives in a separate MariaDB database. A shared, stateless compute
tier serves every tenant; the requested host name selects the site (dns_multitenant).
┌──────────────────────────────┐
admin.<domain> ─────▶│ Control plane (operator site)│ provisions tenants
└───────────────┬───────────────┘
│ bench new-site + install + migrate
<slug>.app.<domain> ─────────────▼──────────────────────────────┐
┌───────────┐ ┌───────────────┐ ┌───────────────┐ ┌────────────┐│
│ Traefik │──▶│ Frappe (web) │──▶│ MariaDB │ │ Redis ││
│ ingress │ │ gunicorn+RQ │ │ 1 DB / tenant │ │ cache/queue││
└───────────┘ └───────────────┘ └───────────────┘ └────────────┘│
Shared, stateless compute • per-tenant isolation ┘
- Compute (shared, stateless): gunicorn web, Socket.IO realtime, RQ workers (default/short/long), and a singleton scheduler.
- Data (per tenant): one MariaDB database per site; Redis for cache/queue/realtime.
- Provisioning: an approved signup enqueues a long-running
Provisioning Jobthat creates and bootstraps the tenant site — no infrastructure change required (wildcard ingress + wildcard TLS).
entertainment_express/ # Frappe/ERPNext application (the core platform)
control_plane/ # tenant, plan, signup, provisioning-job doctypes + provisioner
booking/ scheduling_dispatch/ hr_workforce/ billing_payments/ service_catalog/
api/ # whitelisted API layer (booking, dispatch, payments, mobile v2, …)
setup/ # install hooks, custom fields, plan seeds
frontend/
crew-app/ # Crew mobile app
customer-portal/ # Customer-facing portal
dispatch-portal/ # Dispatcher operations console
openspec/ # Spec-driven change management (specs + changes)
Dockerfile # Multi-arch bench image (linux/amd64 + linux/arm64)
k8s-deployment.yaml # Reference Kubernetes manifest
smoke_test.py # Post-deploy smoke checks
- Backend: Frappe Framework v15, ERPNext, Python 3.11+, MariaDB 10.11, Redis
- Frontend: TypeScript / React Native client apps
- Payments: Stripe
- Delivery: Docker (multi-arch amd64 + arm64), Kubernetes (K3s), Helm, Traefik ingress, Longhorn storage, Let's Encrypt via Cloudflare DNS-01
Everything needed to run in production lives in this repo: the multi-arch
Dockerfile, the full Kubernetes manifest
k8s-deployment.yaml (namespace entertainment-express),
and a secrets.example.yaml template. Bootstrapping of the
base and control-plane sites runs via the idempotent site-init / admin-site-init
Jobs; per-tenant sites are then created on demand by the control plane.
# 1) Build & push the multi-arch bench image (from the repo root)
docker buildx build --platform linux/amd64,linux/arm64 \
-t <registry>/entertainment-express/bench:<tag> --push -f Dockerfile .
# 2) Namespace + secrets (edit secrets.example.yaml first — never commit real values)
kubectl create namespace entertainment-express --dry-run=client -o yaml | kubectl apply -f -
kubectl -n entertainment-express apply -f secrets.example.yaml
# 3) Deploy
kubectl apply -f k8s-deployment.yamlSecrets are never committed — supply them at deploy time via Kubernetes Secrets (see
secrets.example.yaml). Thebase-domainsecret value must match the ingress hosts ink8s-deployment.yaml.
- Standard Frappe bench workflow (
bench get-app,bench --site <site> migrate, …). - Changes are proposed and tracked spec-first via OpenSpec (
openspec/). - Trunk-based: all changes land on
mainthrough a pull request with linear history.
- Isolation: database-per-tenant; namespace-scoped deployment; wildcard TLS per environment.
- Secrets: provided as Kubernetes Secrets; nothing sensitive is committed to the repo.
- Branch protection on
main: pull-request required, linear history enforced, force-push and deletion blocked, conversation resolution required, and secret-scanning push protection enabled.
This is a single-maintainer, proprietary project. The repository is public for transparency and reference; external pull requests and reviews are not accepted at this time.
Proprietary. Copyright © 2024–present Trec-Tor Consulting (Tobey Rector). All rights
reserved. See entertainment_express/license.txt.