SaaS box — hosted control plane
This is the operator tutorial for the SaaS box — the server that runs Mezzanine as a hosted, multi-tenant product. Each customer gets their own isolated Mezzanine instance on a subdomain, created on demand. For running a single panel for yourself, see the App box guide instead.
What the SaaS box is
A single host running the control plane + Traefik, which together provision and route a fleet of per-tenant Mezzanine instances:
| Piece | Role |
|---|---|
mezzanine-control-plane | Signup, accounts, billing, superadmin/ops console, the provisioner, and tenant lifecycle |
mezzanine-traefik | Wildcard TLS + routes *.mezzanine.cloud to the right tenant containers |
mz-<slug>-frontend / mz-<slug>-backend | One pair per tenant, built from the shared mezzanine-frontend/backend:latest images |
tenant_<slug>_data | One Docker volume per tenant (their SQLite DB + uploads) — isolated, persistent |
The public entry point is the signup site (e.g. try.mezzanine.cloud), served by the control plane.
📷 Screenshot —
try.mezzanine.cloudsignup page
Architecture at a glance
visitor ─▶ try.mezzanine.cloud (control plane: signup/billing/ops) │ provision ▼ <slug>.mezzanine.cloud ─▶ Traefik ─▶ mz-<slug>-frontend ─▶ mz-<slug>-backend │ tenant_<slug>_data (volume)See the system-wide Architecture page for how this sits next to the App box.
1. The tenant signup flow
- A visitor hits the signup site and creates an account (email + password, plan).
- The control plane provisions a tenant: creates the data volume, launches
mz-<slug>-frontend/backendfrom:latest, and registers Traefik labels so<slug>.mezzanine.cloudroutes to them. - The tenant lands in their own isolated Mezzanine panel — same UI as the App box.
📷 Screenshot — signup → “your instance is being provisioned” 📷 Screenshot — a tenant’s freshly provisioned panel
2. The ops / superadmin console
The control plane exposes a superadmin area to run the platform:
- Accounts & instances — every tenant, plan, status (
running/hibernated/suspended), last active. - Lifecycle actions — provision, suspend/resume, hibernate/wake, destroy, back up, export data.
- Orphans — a safety sweep for containers/volumes whose tenant row is gone, with one-click cleanup.
📷 Screenshot — ops console: instances table with lifecycle actions
3. Tenant lifecycle
| State | Meaning | Trigger |
|---|---|---|
| running | Containers up, serving traffic | provision / wake / resume |
| hibernated | Containers stopped to save resources; data volume kept | idle reaper, or manual |
| suspended | Stopped (e.g. non-payment); resume to restore | admin / billing |
| destroyed | Containers and the data volume removed | admin (irreversible) |
Hibernate/wake is automatic: free, idle instances are stopped after an idle window; the next request to a hibernated subdomain hits a wake fallback that starts the containers back up.
📷 Screenshot — a tenant being hibernated / woken in the ops console
4. Billing (env-gated)
Billing is wired to Razorpay and only active when its env keys are set (test or live). Plans gate quotas (instances, resources). Subscription events (created/resumed/cancelled) drive suspend/resume.
📷 Screenshot — billing/plan view
5. Custom domains & data export
- Custom domains — a tenant can map their own domain to their instance; Traefik issues a cert via the custom-cert resolver.
- Data export — an admin (or the tenant) can export an instance’s data for portability/backup.
📷 Screenshot — custom domain setup for a tenant
6. Shipping app updates to tenants
This is the operational heart of running the SaaS. The tenant instances run the same mezzanine-frontend/backend app as the App box, but from this box’s own images — so panel changes must be deployed here too, not just on the App box.
Build the new images
# on the SaaS box, from the synced source at /opt/mezzaninedocker build -f deploy/docker/Dockerfile.backend -t mezzanine-backend:latest .docker build -f deploy/docker/Dockerfile.frontend -t mezzanine-frontend:latest .(The per-tenant docker-compose.yml template needs a tenant DOMAIN, so build the images directly rather than compose build.)
- ✅ New tenants provisioned after this use the new images immediately.
- ⚠️ Existing tenants keep their old image until their containers are recreated (wake only restarts). Recreating must preserve the
tenant_<slug>_datavolume.
📷 Screenshot — terminal: building the two images on the SaaS box
7. Configuration reference
The control plane reads its config from environment variables (set in the SaaS box’s .env):
| Var | Purpose |
|---|---|
CP_PROVISION | on to enable real Docker provisioning |
CP_TENANT_DOMAIN | Base domain for tenant subdomains (e.g. mezzanine.cloud) |
CP_TENANT_NETWORK | Docker network tenants attach to |
CP_CERT_RESOLVER / CP_CUSTOM_CERT_RESOLVER | Traefik cert resolvers (wildcard vs custom domains) |
MZ_IMAGE_FRONTEND / MZ_IMAGE_BACKEND | Tenant image tags (default mezzanine-frontend/backend:latest) |
CP_HOST / CP_PORTAL_URL | The signup/portal host (e.g. try.mezzanine.cloud) |
| Razorpay keys | Enable billing when present |
📷 Screenshot — redacted
.envshowing the CP* keys_
8. Backups & safety
- Per-instance backups + back up all are available from lifecycle ops; back up the
tenant_<slug>_datavolumes regularly. - Account deletion purges all of a tenant’s containers + volume and aborts on failure, so deletes never silently orphan data.
- Run the orphan sweep periodically to catch stray containers/volumes.
📷 Screenshot — ops console: backups + orphan cleanup