Skip to content

Multi-Tenant Application Domain Configuration Architecture

Multi-Tenant Application Domain Configuration Architecture

Section titled “Multi-Tenant Application Domain Configuration Architecture”

In the SiteSwarm multi-tenant monorepo, dozens of independent client web applications coexist. Each client web application represents an autonomous business entity with its own branding, deployment lifecycle, database instances, and custom domains.

To prevent cross-tenant configuration blast radius and avoid accidental domain collisions between clients (e.g. accidentally assigning Client A’s domain to Client B), all custom domain and routing configurations are strictly colocated at the application level.

There is no central or global domain registry table in SiteSwarm. Instead:

  1. Each application defines its own environment names and custom domain routes inside its own wrangler.jsonc.
  2. Each application defines its persistent infrastructure (D1 databases, custom domain DNS records, SSL certificates) inside its colocated apps/<app>/infra/ Terraform module.
  3. CI/CD workflows dynamically discover each app’s domain and routing configuration directly from that app’s wrangler.jsonc at deployment time.

2. Platform Domain Standard (jacobmiller22.com)

Section titled “2. Platform Domain Standard (jacobmiller22.com)”

Until a client application provisions its own dedicated branded domain (e.g. greenleafbakery.com), client applications default to subdomains under the active platform domain: jacobmiller22.com.

Cloudflare Universal SSL Single-Level Invariant

Section titled “Cloudflare Universal SSL Single-Level Invariant”

Cloudflare’s default Universal SSL certificate covers the root domain (example.com) and a single level of wildcard (*.example.com). It does not cover multi-level subdomains (such as staging.bakery.example.com) without purchasing Cloudflare Advanced Certificate Manager ($10/month).

To guarantee 100% out-of-the-box SSL certificate coverage with zero additional monthly costs, SiteSwarm mandates single-level subdomains under jacobmiller22.com:

Environment Subdomain Convention Example URL SSL Coverage
Production <app>.<domain> https://bakery.jacobmiller22.com *.jacobmiller22.com (Universal SSL)
Staging <app>-staging.<domain> https://bakery-staging.jacobmiller22.com *.jacobmiller22.com (Universal SSL)
PR Preview <app>-preview-pr-<N>.<domain> https://bakery-preview-pr-86.jacobmiller22.com *.jacobmiller22.com (Universal SSL)
PR Preview (workers.dev) <worker-name>.<workers-subdomain>.workers.dev https://bakery-preview-pr-86.jacobmillerdev.workers.dev *.workers.dev (Native Cloudflare)

3. Application-Level Configuration Specification

Section titled “3. Application-Level Configuration Specification”

3.1 Runtime Specification (apps/<app>/wrangler.jsonc)

Section titled “3.1 Runtime Specification (apps/<app>/wrangler.jsonc)”

Each app specifies its runtime environment isolation and routes under env:

{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "green-leaf-bakery",
"compatibility_date": "2026-09-29",
"compatibility_flags": ["nodejs_compat"],
"d1_databases": [
{
"binding": "DB",
"database_name": "green-leaf-bakery-prod-d1",
"database_id": "<prod-uuid>"
}
],
"env": {
"preview": {
"vars": {
"ENVIRONMENT": "preview"
},
"d1_databases": [
{
"binding": "DB",
"database_name": "green-leaf-bakery-d1",
"database_id": "<preview-uuid>"
}
]
},
"staging": {
"name": "green-leaf-bakery-staging",
"vars": {
"ENVIRONMENT": "staging"
},
"d1_databases": [
{
"binding": "DB",
"database_name": "green-leaf-bakery-staging-d1",
"database_id": "<staging-uuid>"
}
],
"routes": [
{
"pattern": "bakery-staging.jacobmiller22.com",
"custom_domain": true
}
]
},
"production": {
"name": "green-leaf-bakery",
"vars": {
"ENVIRONMENT": "production"
},
"d1_databases": [
{
"binding": "DB",
"database_name": "green-leaf-bakery-prod-d1",
"database_id": "<prod-uuid>"
}
],
"routes": [
{
"pattern": "bakery.jacobmiller22.com",
"custom_domain": true
}
]
}
}
}

3.2 Infrastructure Specification (apps/<app>/infra/)

Section titled “3.2 Infrastructure Specification (apps/<app>/infra/)”

The colocated Terraform module exposes variables for staging and production domains:

apps/bakery/infra/variables.tf
variable "staging_domain" {
description = "Custom domain hostname for Staging"
type = string
default = "bakery-staging.jacobmiller22.com"
}
variable "production_domain" {
description = "Custom domain hostname for Production"
type = string
default = "bakery.jacobmiller22.com"
}

4. Runbook: Assigning a Dedicated Client Domain

Section titled “4. Runbook: Assigning a Dedicated Client Domain”

When a client purchases their own domain (e.g. greenleafbakery.com) and delegates nameservers to Cloudflare:

  1. Step 1: Update App Configuration (apps/<app>/wrangler.jsonc): Change the pattern in env.staging and env.production:
    "env": {
    "staging": {
    "routes": [{ "pattern": "staging.greenleafbakery.com", "custom_domain": true }]
    },
    "production": {
    "routes": [{ "pattern": "greenleafbakery.com", "custom_domain": true }]
    }
    }
  2. Step 2: Update App Infrastructure (apps/<app>/infra/variables.tf): Update staging_domain and production_domain defaults or terraform tfvars.
  3. Step 3: Verification: Submit a PR. Ephemeral preview deploys will test routing, merge to main deploys to the new staging domain, and automated health checks verify HTTP 200 and version hash equivalence before opening the gate.