Skip to content

Tenant Architecture

Each tenant is an isolated Medusa instance with its own:

  • Docker containers (backend + storefront + Redis)
  • PostgreSQL database (magic_b2b_{tenant})
  • Sales channel and publishable API key
  • Branding (colors, logo, favicon)
  • Domain and Nginx routing

They all share:

  • The same source codebase (from magic_development)
  • The same PostgreSQL server instance
  • The same PIM product data (read-only mount)
  • The same product images (/mnt/data/pim_data/)

The storefront detects the brand from the domain and applies per-brand configuration:

interface BrandConfig {
slug: string // "brinxx", "moxz", etc.
domain: string // Primary domain
type: 'owner' | 'brand' | 'whitelabel'
logo: string // Logo image path
primaryColor: string // Brand accent color
salesChannelId: string // Medusa sales channel
publishableKey: string // Medusa API key
// ... contact info, social, SEO
}
BrandDomainPrimary ColorType
Brinxxbrinxx.magiceverse.online#CF0D65 (pink)owner
Magiceversedevelopment.magiceverse.online#8338B6 (purple)owner
Defaultdefault.magiceverse.online#4caf50 (green)owner
MOXZmoxz.magiceverse.online#CF0D65 (pink)brand
Promotionalzpromotionalz.magiceverse.online#e91e63 (pink)brand
  1. Exact domain match (highest priority)
  2. Check additionalDomains array
  3. Domain starts with brand slug
  4. Fallback to magiceverse (demo store)

The BrandProvider injects CSS custom properties at runtime:

:root {
--brand-primary: #CF0D65;
--brand-secondary: #1a1a1a;
--brand-accent: #e91e63;
--brand-header-bg: #CF0D65;
--brand-header-text: #ffffff;
--brand-footer-bg: #1a1a1a;
--brand-footer-text: #ffffff;
}

Three built-in themes loaded via NEXT_PUBLIC_THEME:

src/themes/
├── base/variables.css # Base CSS variables
├── brinxx/override.css # Pink/magenta theme
└── default/override.css # Indigo/green theme

Each tenant’s docker-compose.yml follows this pattern:

services:
redis:
image: redis:7-alpine
ports: ["63XX:6379"]
healthcheck: redis-cli ping
backend:
build:
args:
MEDUSA_BACKEND_URL: https://admin-{tenant}.magiceverse.online
ports: ["40XX:3002", "70XX:7992"]
environment:
DATABASE_URL: postgres://...@host.docker.internal:5432/magic_b2b_{tenant}
APLT_DB_NAME: magic_b2b_{tenant}
MEDUSA_BACKEND_URL: https://admin-{tenant}.magiceverse.online
volumes:
- /mnt/data/pim_data:/mnt/data/pim_data:ro
- /mnt/data/magic_omniverse:/mnt/data/magic_omniverse
extra_hosts:
- "host.docker.internal:host-gateway"
storefront:
ports: ["100XX:9002"]
environment:
NEXT_PUBLIC_THEME: {tenant}
NEXT_PUBLIC_MEDUSA_BACKEND_URL: https://admin-{tenant}.magiceverse.online
NEXT_PUBLIC_BASE_URL: https://{tenant}.magiceverse.online

Each tenant whitelists its specific domains. Example for Brinxx:

STORE_CORS=http://localhost:10040,https://brinxx.magiceverse.online,
https://admin-brinxx.magiceverse.online,
https://moxz.magiceverse.online,
https://promotionalz.magiceverse.online
TenantAdmin Login URLDatabase
PIM (Master)https://magic-pimadmin.magiceverse.online/app/loginmagic_pim
Developmenthttps://admin-development.magiceverse.online/app/loginmagic_b2b_development
Defaulthttps://admin-default.magiceverse.online/app/loginmagic_b2b_default
Demohttps://admin-demo.magiceverse.online/app/loginmagic_b2b_demo
Brinxxhttps://admin-brinxx.magiceverse.online/app/loginmagic_b2b_brinxx
Spranzhttps://admin-spranz.magiceverse.online/app/loginmagic_b2b_spranz
Jodasignhttps://admin-jodasign.magiceverse.online/app/loginmagic_b2b_jodasign
Logohorlogehttps://admin-logohorloge.magiceverse.online/app/loginmagic_b2b_logohorloge
De Sluishttps://admin-desluis.magiceverse.online/app/loginmagic_b2b_desluis
Bovisaleshttps://admin-bovisales.magiceverse.online/app/loginmagic_b2b_bovisales
Masterhttps://admin-master.magiceverse.online/app/loginmaster_magic
  1. Each Medusa instance stores user credentials in its own auth_identity / provider_identity tables
  2. The emailpass provider handles email + password login
  3. Password reset tokens are JWT tokens signed with the tenant’s JWT_SECRET
  4. The reset email link points to the same tenant’s admin URL (via MEDUSA_BACKEND_URL)
  5. A user can exist in multiple tenant databases with different passwords
User clicks "Forgot password" on admin-{tenant}.magiceverse.online
→ POST /auth/user/emailpass/reset-password { identifier: email }
→ generateResetPasswordTokenWorkflow creates JWT token
→ Email sent with link to admin-{tenant}/app/reset-password?token=...
→ User clicks link, enters new password
→ POST /auth/user/emailpass/update { password: "..." } (with JWT in header)
→ Password updated in tenant's provider_identity table
→ User logs in at admin-{tenant}/app/login

Each tenant connects to the same PostgreSQL server but uses a separate database:

Connection Pattern:
postgres://postgres:<your-db-password>@host.docker.internal:5432/magic_b2b_{tenant}
Databases:
├── magic_b2b_development
├── magic_b2b_brinxx
├── magic_b2b_default
├── magic_b2b_demo
├── magic_b2b_desluis
├── magic_b2b_jodasign
├── magic_b2b_logohorloge
├── magic_b2b_bovisales
├── magic_b2b_spranz
└── master_magic

Each database contains:

  • Standard Medusa tables (products, carts, orders, customers)
  • APLT tables (quotations, invoices, brands, CMS settings, access control)