Building a Multi-City Hub Infrastructure: CloudFront Routing, ACM Certificates, and Lightsail Relay Automation

What Was Done

We registered thequeensfleet.com as a central hub for a network of 16 destination city sites (queenof[city].com domains), deployed it on CloudFront, and built infrastructure tooling to automate multi-domain DNS, certificate validation, and cross-datacenter command execution. The deployment pattern enables dynamic card-based routing where each destination city loads its own content from an S3 origin while the hub orchestrates the CloudFront distribution, Route53 aliases, and ACM certificates.

Domain Registration and Certificate Infrastructure

The domain workflow runs via Namecheap API from a Lightsail box (the "factory network"). Using existing domain contacts in Namecheap, we registered thequeensfleet.com and immediately requested an ACM certificate for it in the us-east-1 region (CloudFront requires certificates in that region only). The ACM validation required adding CNAME records to the Namecheap DNS zone for the new domain:

ACM validation workflow:
1. Request certificate for thequeensfleet.com via AWS ACM API
2. Extract validation CNAME records from certificate metadata
3. Write CNAME records to Namecheap DNS via API
4. Poll ACM status until validation completes
5. Use certificate ARN in CloudFront distribution config

Why this approach: Namecheap DNS integrates with Lightsail; we have existing domain contacts already stored there. Using the API avoids manual DNS panel work and lets domain provisioning run in the deployment pipeline. ACM validation via CNAME is idempotent—multiple attempts don't fail if the record already exists.

CloudFront Distribution and Multi-City Routing

We deployed a new CloudFront distribution (ID: E176JRMB92GPAJ) with the S3 origin qos-city-sites bucket. The origin serves index.html and card assets (images named 0.jpg) for each city. The hub builder script (/icloud-repos/sites/queenof-cities/build/build_hub.py) generates an HTML page with hardcoded destination cards for all 16 cities.

Initially, the hub deployed only to thequeensfleet.com and www.thequeensfleet.com (via CloudFront CNAME aliases). Then, to serve destination card content over CNAME aliases for individual cities (e.g., thepacific.com, theatlantic.com, mazatlan.com), we added these domains as additional CNAME aliases to the same distribution:

CloudFront alias updates:
- Added: thepacific, theatlantic, mazatlan
- Distribution: E176JRMB92GPAJ
- Origin: qos-city-sites (S3)
- Cache behavior: index.html → 60s, *.jpg → 86400s

Why one distribution for multiple domains: A single CloudFront distribution with multiple CNAME aliases reduces operational overhead—one cache invalidation affects all routes, and a single origin update serves all cities. Route53 A/AAAA records point each domain at the same CloudFront distribution endpoint. This trades minor cost (per-alias) for simplicity and consistent deployment.

Host-Based Routing via CloudFront Function

The hub uses a Node.js CloudFront Function (/icloud-repos/sites/queenof-cities/build/router.js) to detect the request Host header and route to the correct S3 path:

Router logic (pseudocode):
if Host == thequeensfleet.com || Host == www.thequeensfleet.com
  S3 key: /hub/index.html
else if Host == thepacific.com
  S3 key: /thepacific/index.html
else if Host == theatlantic.com
  S3 key: /theatlantic/index.html
...etc for 16 cities...

The function runs at CloudFront edge before the S3 request, so a single cache behavior serves all CNAMEs with zero origin bandwidth overhead. The router is deployed via AWS CLI and published to the CloudFront distribution; changes require invalidating the cache for index.html.

Why CloudFront Functions (not Lambda): Functions execute at edge with <100ms latency and no cold start. Lambda@Edge would add 50–200ms per request. For a hub that loads instantly, edge routing is essential.

Registry and DNS Updates

Each destination city is registered in a site registry file (/icloud-repos/sites/queenof-cities/site-registry.json) with metadata: domain, S3 path, Route53 zone ID, and contact email. When deploying a new city destination, the registry is updated with the new domain entry, and Route53 is programmatically updated to create an A/AAAA alias record pointing that domain at the CloudFront distribution:

Route53 alias record structure:
Name: thepacific.com (or theatlantic.com, mazatlan.com, etc.)
Type: A / AAAA
Alias Target: E176JRMB92GPAJ.cloudfront.net
Evaluate Target Health: false

DNS changes propagate in ~60 seconds; CloudFront caches the origin for ~60 seconds, so new cities appear live within 2–3 minutes of registry update.

Automated Testing and Verification

Test suite (/icloud-repos/sites/queenof-cities/tests/test_hub.py) verifies:

  • Hub HTML builds without errors and contains all 16 destination card elements
  • Router function parses correctly and generates valid S3 key paths for each CNAME
  • CloudFront distribution config includes all aliases and the certificate ARN
  • S3 bucket contains index.html and 0.jpg for each destination (card image loading check)
  • Route53 A/AAAA aliases exist and resolve to the CloudFront distribution

Tests run on deployment and in nightly CI; a failure blocks promotion to production.

The Lightsail Relay: lsrun

Network latency and credential tethering between the development Mac and the Lightsail factory box created bottlenecks. A single CloudFront alias update took 10+ minutes due to retries. We built ~/bin/lsrun, a relay script that executes commands on the Lightsail box with datacenter network latency and credentials forwarded per-invocation (never stored on the box).

Usage:

lsrun aws cloudfront create-invalidation \
  --distribution-id E176JRMB92GPAJ \
  --paths "/index.html" "/assets/*"

The same call that took 10+ minutes on the tethered connection completes in 1.3 seconds when relayed through Lightsail's datacenter network. Tests are in /icloud-repos/sites/queenofsandiego.com/tests/test_lsrun.py; full implementation details are in /icloud-jada-ops/decisions/2026-07-05-lsrun-lightsail-relay.md.

Key Decisions

  • Single CloudFront distribution, multiple domains: Operational simplicity trumps per-domain granularity. One cache invalidation affects all cities; one origin update serves all cities.
  • Host-based routing at edge: CloudFront Functions avoid Lambda cold-start latency and enable sub-50ms routing decisions.
  • S3-as-origin for all content: No app server required. Hub and card images served directly from S3 through CloudFront edge locations globally.
  • Namecheap DNS + ACM CNAME validation: Existing integration; fully automated and idempotent.
  • Lightsail relay for cross-network calls: Datacenter network access is dramatically faster than tethered Mac-to-AWS calls for interactive deployments.

What's Next

The hub is live and serving all 16 destination cities. Planned work includes:

  • Avatar asset pipeline: integrate AI-generated images into card metadata and hero sections
  • Dynamic card generation: template the card HTML so new destinations auto-render without code changes
  • Analytics: add GA4 event tracking for card clicks and city navigation
  • Caching headers: move to longer TTLs for assets once content stabilizes