Multi-Site Static Hosting with Dynamic Vendor Integration: Deploying Across Three Dangerouscentaur Properties
This session involved a significant infrastructure undertaking: deploying vendor pages to three independent Dangerouscentaur sites (sailjada.com, queenofsandiego.com, bssd.org), registering a new domain (morenoferrolaw.com), and building out a professional subdomain with correct legal credentialing. Here's how we handled the technical complexity while keeping deployments safe and reproducible.
The Problem: Vendor Data Across Multiple Sites
The three sites share a vendor database but serve different audiences — wedding referrals, maritime services, and BSSD partner organizations. Each site had its own vendor pages, but the underlying data was scattered:
/Users/cb/icloud-jada-ops/vendors/vendors.json— canonical vendor data/Users/cb/icloud-repos/sites/queenofsandiego.com/tests/test_vendor_pages.py— URL/phone validation tests- Three separate S3 buckets with outdated vendor HTML
The core issue: vendor URLs were stale, tests were failing against live sites due to timeout and DNS issues, and there was no deterministic way to rebuild and redeploy all three sites from a single source of truth.
Technical Solution: Centralized Build + Multi-Site Deploy
Step 1: Consolidate vendor data
We curated the master vendors.json file with cleaned URLs and phone numbers, then built HTML pages using a templated approach:
# Hypothetical structure (actual filenames redacted)
vendors.json
├── name
├── description
├── serviceType (wedding | maritime | nonprofit)
├── url (with timeout-safe verification)
├── phone (validated format)
└── tags (filtering for site-specific display)
Step 2: Deterministic test suite
We wrote test_vendor_pages.py with retry logic and higher timeouts (15s instead of 5s) because some partner sites (notably bssd.org) serve slowly. The tests verify:
- HTTP 200 or 301 (redirect-safe)
- Phone numbers match regex format
- No DNS resolution failures
- Content-Type headers are sensible
# Example test structure
def test_vendor_url_with_retry(url, max_retries=3, timeout=15):
for attempt in range(max_retries):
try:
response = requests.head(url, timeout=timeout, allow_redirects=True)
assert response.status_code in [200, 301]
return True
except requests.exceptions.Timeout:
if attempt < max_retries - 1:
time.sleep(2 ** attempt) # exponential backoff
raise AssertionError(f"Failed after {max_retries} retries")
Step 3: Single deploy command
Rather than manual S3 uploads to three buckets, we created a jada-deploy wrapper that:
- Runs
build.pyto generate vendor pages from vendors.json - Deploys to three S3 buckets:
dc-sites(primary),sailjada-web,queenofsandiego-web - Invalidates CloudFront cache for affected distributions
- Verifies live endpoints match the local build
Infrastructure Details
S3 Bucket Structure:
s3://dc-sites/vendors/— canonical vendor HTML pagess3://dc-sites/danielaferro/— Daniela's professional subdomain content- Cross-bucket sync via AWS CLI with
--sse AES256for compliance
CloudFront Distributions:
Three distributions handle traffic:
dc-sitesdistribution — covers *.dangerouscentaur.com (includes danielaferro. subdomain via wildcard cert)sailjada-distribution— independent domain, separate originqueenofsandiego-distribution— independent domain, separate origin
Routing was handled by dc-sites-router, a CloudFront function that maps incoming hostnames to S3 prefixes:
// dc-sites-router function logic (pseudocode)
if (host === "danielaferro.dangerouscentaur.com") {
return "/danielaferro/index.html"
} else if (host.includes("dangerouscentaur.com")) {
return "/vendors/" + path
}
Domain Registration:
morenoferrolaw.com was registered via Namecheap API from a Lightsail instance (for secure credential handling outside local dev machines). The domain was then CNAME'd to the CloudFront distribution. ACM certificate was requested for the domain and validated via DNS CNAME records in Route53.
Key Decisions & Why
Why CloudFront routing instead of virtual hosts in Apache/nginx?
Static hosting on S3 + CloudFront scales without server overhead. The dc-sites-router function is edge-computed, so latency is minimal. Adding a new subdomain is now a JSON config change, not a server redeployment.
Why retry logic in vendor tests?
Partner sites are not always fast or stable. A test that fails on transient network jitter creates false alerts. Exponential backoff matches real-world DNS resolution delays and temporary service degradation without creating flaky CI/CD.
Why morenoferrolaw.com as a separate domain?
dangerouscentaur.com is the organization brand; Daniela's professional presence needed its own domain for credibility (law firms, NGOs, and academic institutions won't link to a dangerouscentaur.com subdomain). The CNAME approach lets us maintain one CloudFront distribution and one S3 bucket while serving multiple domains.
Why centralize vendor data in vendors.json?
Single source of truth prevents divergence. Each site rebuild is deterministic. If a phone number changes, one edit updates all three sites in the next deploy cycle.
Deployment Process
# Build locally
python build.py --output dist/ --vendors vendors.json
# Run tests
pytest tests/test_vendor_pages.py -v
# Deploy to all three sites (mocked command)
jada-deploy --targets dc-sites,sailjada,queenofsandiego --invalidate
# Verify live
curl -I https://morenoferrolaw.com/
curl -I https://vendors.sailjada.com/
What's Next
- Handoff documentation at
/Users/cb/icloud-jada-ops/HANDOFF-2026-07-02.mdcovers credential inventory and infra ownership - Vendor data sync can now be automated via Lambda + EventBridge if vendors.json is updated via API
- ACM certificate auto-renewal is managed by AWS; no manual action needed
- CloudFront cache TTLs are set to 3600s for vendor pages (balance between freshness and edge cache efficiency)
The infrastructure now supports adding new partner sites or domains with minimal friction — just add an S3 prefix, update the router function, and redeploy.
```