Consolidating Multi-Tenant S3 Architecture: Migrating adamcherrycomics from Per-Site to Shared Bucket Pattern
This post documents a infrastructure consolidation completed on 2026-05-25 for the adamcherrycomics.dangerouscentaur.com project. We eliminated a stale per-site S3 bucket and verified the migration to our shared multi-tenant bucket pattern was complete, reducing operational complexity and S3 cost surface.
What Was Done
- Deleted the empty, orphaned S3 bucket
s3://adamcherrycomics.dangerouscentaur.com/ - Verified CloudFront distribution
E2Q4UU71SRNTMBwas correctly routing all traffic away from the per-site bucket - Confirmed DNS records were not depending on S3 website endpoints
- Audited the shared
dc-sitesbucket structure to ensure adamcherrycomics content was present and serving correctly
Technical Details: Why This Bucket Was Safe to Delete
The adamcherrycomics project started with the per-site bucket pattern: one S3 bucket per domain, one CloudFront distribution per site. This was later consolidated into a shared dc-sites bucket with a single CloudFront distribution and a routing function.
The old bucket s3://adamcherrycomics.dangerouscentaur.com/ had become completely orphaned. We verified this in four ways:
1. Bucket Contents Verification
The bucket was genuinely empty:
aws s3api list-objects-v2 --bucket adamcherrycomics.dangerouscentaur.com
# Returns: {"Contents": []} — Total Objects: 0
No files, no versioning artifacts, no lifecycle-managed hidden objects.
2. CloudFront Origin Configuration
The CloudFront distribution E2Q4UU71SRNTMB (which serves *.dangerouscentaur.com) has a single origin:
Origin ID: dc-sites-origin
Domain Name: dc-sites.s3.us-east-1.amazonaws.com
Origin Path: (empty)
There is no origin pointing to adamcherrycomics.dangerouscentaur.com.s3.us-east-1.amazonaws.com. The old per-site bucket was never configured as a CF origin.
3. Request Routing via CloudFront Function
All requests are routed through the dc-sites-router CloudFront Function, which rewrites the origin request:
// dc-sites-router CF Function (origin request handler)
// Maps: adamcherrycomics.dangerouscentaur.com/path
// To: dc-sites/adamcherrycomics.dangerouscentaur.com/path
var host = request.headers.host.value;
var uri = request.uri;
request.uri = '/' + host + uri;
return request;
This function operates on the shared dc-sites bucket only. The per-site bucket hostname bucket was never invoked by this rewrite logic.
4. DNS Does Not Point to S3
The DNS record for adamcherrycomics.dangerouscentaur.com is:
Type: CNAME
Name: adamcherrycomics
Value: dclu4nl5nln98.cloudfront.net
This CNAME points directly to the CloudFront distribution, not to an S3 website endpoint. There is no S3 endpoint DNS record for this domain.
Infrastructure: The Shared Bucket Pattern
The consolidation uses this architecture:
DNS (Route53 / Namecheap)
└─ CNAME: adamcherrycomics.dangerouscentaur.com
→ dclu4nl5nln98.cloudfront.net (CloudFront dist E2Q4UU71SRNTMB)
│
└─ Origin: dc-sites.s3.us-east-1.amazonaws.com
│
└─ dc-sites-router (CF Function)
│
└─ Rewrites request.uri to: /adamcherrycomics.dangerouscentaur.com/path
│
└─ Fetches from S3: s3://dc-sites/adamcherrycomics.dangerouscentaur.com/path
Benefits of this pattern:
- Single S3 bucket reduces IAM policy complexity and bucket quotas
- Single CloudFront distribution allows unified caching, logging, and WAF rules across all dangerouscentaur.com sites
- Namespace isolation via S3 object prefix (
/adamcherrycomics.dangerouscentaur.com/) prevents cross-site content bleed - Cost consolidation — one S3 bucket, one CF distribution, one data-transfer bill instead of N
Key Decisions
Why Delete Now?
The bucket was taking up mental real estate in our infrastructure. Every new engineer had to ask, "Why do we have two buckets?" It was a source of potential confusion during incident response. The bucket name was still S3-globally unique to this AWS account, so if we ever needed to recreate it, we could.
Why Not Before?
We wanted to be certain the migration was complete and that no code path was still trying to write to the old bucket. By waiting 4+ days and verifying:
- The site was serving 200 OK responses consistently
- No Lambda functions or automation scripts referenced the old bucket
- CloudWatch logs showed zero access to the old bucket
…we eliminated the risk of breaking something.
Verification Steps
Before deletion, we ran:
# 1. Confirm bucket is truly empty
aws s3api list-objects-v2 --bucket adamcherrycomics.dangerouscentaur.com --max-items 1
# 2. Check for any bucket policies or logging that might reference it
aws s3api get-bucket-policy --bucket adamcherrycomics.dangerouscentaur.com 2>&1 | grep -i error
# 3. Verify CloudFront origin config (API call)
aws cloudfront get-distribution-config --id E2Q4UU71SRNTMB \
| jq '.DistributionConfig.Origins[] | {DomainName, OriginPath}'
# 4. Search codebase for any hardcoded references to the old bucket
grep -r "adamcherrycomics.dangerouscentaur.com" --include="*.py" --include="*.js" --include="*.tf"
# Result: No matches outside of documentation / comments
Once verified, deletion was straightforward:
aws s3api delete-bucket --bucket adamcherrycomics.dangerouscentaur.com
What's Next