Skip to content

Multi-Tenant Architecture

FireBreak's multi-tenant architecture provides complete isolation for enterprise clients while sharing the same codebase and infrastructure.

Overview

Multi-tenancy in FireBreak allows:

  • Multiple clients to use the same platform
  • Complete data isolation between tenants
  • Per-tenant branding and customization
  • Independent scaling and resource limits
  • Tenant-specific feature flags

Architecture Diagram

graph TB
    subgraph "User Layer"
        U1[Tenant A User]
        U2[Tenant B User]
        U3[Tenant C User]
    end

    subgraph "Routing Layer"
        R[DNS Routing<br/>subdomain.firebreakrisk.com]
    end

    subgraph "Frontend Layer"
        FA[Tenant A Frontend<br/>acme.firebreakrisk.com]
        FB[Tenant B Frontend<br/>wildguard.firebreakrisk.com]
        FC[Tenant C Frontend<br/>guardian.firebreakrisk.com]
    end

    subgraph "Backend Layer"
        API[Shared Backend API<br/>api.firebreakrisk.com]
    end

    subgraph "Storage Layer - Isolated"
        DA[(Tenant A Database)]
        DB[(Tenant B Database)]
        DC[(Tenant C Database)]

        SA[Tenant A R2 Bucket]
        SB[Tenant B R2 Bucket]
        SC[Tenant C R2 Bucket]
    end

    U1 --> R
    U2 --> R
    U3 --> R

    R --> FA
    R --> FB
    R --> FC

    FA --> API
    FB --> API
    FC --> API

    API --> DA
    API --> DB
    API --> DC

    API --> SA
    API --> SB
    API --> SC

Tenant Isolation Levels

Level 1: Subdomain Isolation

Each tenant gets a unique subdomain:

acme.firebreakrisk.com       → Tenant A
wildguard.firebreakrisk.com  → Tenant B
guardian.firebreakrisk.com   → Tenant C

Level 2: Frontend Isolation

Each tenant has a dedicated frontend deployment with:

  • Custom branding (colors, logos, fonts)
  • Tenant-specific UI components
  • White-label experience
  • Independent CSS/JS bundles

Level 3: Data Isolation

Complete data separation:

  • Separate D1 Databases: Each tenant has dedicated database
  • Separate R2 Buckets: Isolated photo/video storage
  • Separate KV Namespaces: Tenant-specific caching
  • Tenant ID in all queries: Runtime validation

Level 4: Resource Isolation

Per-tenant limits:

  • API rate limiting
  • Storage quotas
  • Concurrent request limits
  • CPU/memory constraints

Tenant Configuration

Tenant Config File

{
  "tenant_id": "acme-insurance",
  "name": "ACME Insurance",
  "subdomain": "acme",
  "status": "active",
  "created_at": "2025-01-15",

  "branding": {
    "primary_color": "#FF6600",
    "secondary_color": "#003366",
    "logo_url": "https://r2.firebreakrisk.com/acme/logo.png",
    "favicon_url": "https://r2.firebreakrisk.com/acme/favicon.ico",
    "font_family": "Inter, sans-serif"
  },

  "features": {
    "multi_model_consensus": true,
    "video_analysis": true,
    "custom_reports": true,
    "api_access": true
  },

  "limits": {
    "max_users": 100,
    "max_assessments_per_month": 1000,
    "max_storage_gb": 50,
    "api_rate_limit_per_minute": 100
  },

  "storage": {
    "d1_database_id": "tenant-acme-db",
    "r2_bucket": "firestorm-photos-acme",
    "kv_namespace_id": "tenant-acme-cache"
  },

  "contact": {
    "admin_email": "admin@acme-insurance.com",
    "billing_email": "billing@acme-insurance.com",
    "support_email": "support@acme-insurance.com"
  }
}

Tenant Registry

All tenants registered in tenants/registry.json:

{
  "tenants": [
    {
      "id": "acme-insurance",
      "subdomain": "acme",
      "status": "active"
    },
    {
      "id": "wildguard-insurance",
      "subdomain": "wildguard",
      "status": "active"
    },
    {
      "id": "property-guardian",
      "subdomain": "guardian",
      "status": "trial"
    }
  ]
}

Request Flow

1. User Request

User accesses: https://acme.firebreakrisk.com/

2. DNS Routing

DNS resolves to: Cloudflare Pages
Pages Worker extracts tenant: "acme"

3. Frontend Serving

Cloudflare Pages serves tenant-specific frontend:
  - Load acme branding
  - Inject tenant ID in API calls
  - Apply custom CSS theme

4. API Request

// Frontend makes API call
fetch('https://api.firebreakrisk.com/api/v1/analyze', {
  headers: {
    'X-Tenant-ID': 'acme-insurance',
    'Authorization': 'Bearer ...'
  }
})

5. Backend Processing

# Backend validates tenant
async def handle_request(request, env):
    tenant_id = request.headers.get('X-Tenant-ID')

    # Validate tenant exists
    tenant = await get_tenant(tenant_id, env)
    if not tenant:
        return Response('Invalid tenant', status=403)

    # Use tenant-specific database
    db = env.get_database(tenant.database_id)

    # Process request with tenant context
    result = await process_with_tenant(request, tenant, db)
    return result

Tenant Provisioning

Automated Provisioning

# Using CLI tool
cd infrastructure/scripts
./provision-tenant.sh acme-insurance

# Steps performed:
# 1. Create D1 database
# 2. Create R2 bucket
# 3. Create KV namespace
# 4. Generate tenant config
# 5. Deploy frontend with branding
# 6. Set up DNS records
# 7. Send welcome email

Manual Provisioning

See Tenant Provisioning Guide for detailed steps.

Security Considerations

Tenant ID Validation

def validate_tenant_access(request, tenant_id, env):
    """Ensure user can only access their tenant's data"""

    # Extract tenant from request
    request_tenant = request.headers.get('X-Tenant-ID')

    # Validate match
    if request_tenant != tenant_id:
        raise ForbiddenError("Tenant ID mismatch")

    # Validate tenant exists and is active
    tenant = get_tenant(tenant_id, env)
    if not tenant or tenant.status != 'active':
        raise ForbiddenError("Invalid or inactive tenant")

    return tenant

Data Isolation Checks

async def get_assessment(assessment_id, tenant_id, env):
    """Fetch assessment with tenant validation"""

    # Get tenant database
    db = env.get_database(f"tenant-{tenant_id}-db")

    # Query with tenant_id in WHERE clause
    result = await db.execute(
        "SELECT * FROM assessments WHERE id = ? AND tenant_id = ?",
        [assessment_id, tenant_id]
    )

    return result.first()

Rate Limiting Per Tenant

async def check_rate_limit(tenant_id, env):
    """Check if tenant has exceeded rate limit"""

    # Get tenant config
    tenant = get_tenant(tenant_id, env)
    limit = tenant.limits.api_rate_limit_per_minute

    # Check current usage
    key = f"rate:{tenant_id}:{current_minute}"
    current = await env.CACHE.get(key) or 0

    if current >= limit:
        raise RateLimitError(f"Exceeded {limit} requests per minute")

    # Increment counter
    await env.CACHE.put(key, current + 1, expiration=60)

Tenant Lifecycle

stateDiagram-v2
    [*] --> Trial: New Signup
    Trial --> Active: Payment Confirmed
    Trial --> Suspended: Trial Expired
    Active --> Suspended: Payment Failed
    Suspended --> Active: Payment Resumed
    Active --> Cancelled: User Cancels
    Suspended --> Cancelled: Grace Period Expired
    Cancelled --> [*]: Data Retention Period Ends

Status Definitions

  • Trial: 30-day free trial, limited features
  • Active: Paid subscription, full features
  • Suspended: Payment issue, read-only access
  • Cancelled: Subscription ended, data retention for 90 days

Scaling Considerations

Horizontal Scaling

  • Workers: Auto-scale per request
  • Databases: D1 auto-scales reads
  • Storage: R2 unlimited capacity
  • CDN: Cloudflare global network

Performance Optimization

  • Per-tenant caching: KV namespace per tenant
  • CDN caching: Cloudflare cache per subdomain
  • Database indexing: Tenant-specific indexes
  • Lazy loading: Load branding on-demand

Monitoring & Analytics

Per-Tenant Metrics

// Track metrics per tenant
await env.ANALYTICS.write({
  tenant_id: 'acme-insurance',
  metric: 'api_request',
  endpoint: '/api/v1/analyze',
  duration_ms: 245,
  status: 200,
  timestamp: new Date()
})

Tenant Dashboard

Each tenant can view:

  • API usage statistics
  • Storage consumption
  • Active users
  • Assessment counts
  • Error rates

Migration & Onboarding

Migrating Existing Data

# Import existing data for new tenant
./scripts/import-tenant-data.sh \
  --tenant acme-insurance \
  --data-file acme_export.json \
  --validate

Tenant Onboarding Checklist

  • Create tenant configuration
  • Provision infrastructure (D1, R2, KV)
  • Set up DNS records
  • Deploy branded frontend
  • Import existing data (if applicable)
  • Set up user accounts
  • Configure API keys
  • Test end-to-end workflows
  • Enable monitoring
  • Send welcome email and documentation

Reference Documentation

For detailed implementation:

See Also