```html

Consolidating Multi-Site Infrastructure: Migrating from Per-Bucket to Shared-Bucket + Router Pattern

During this week's infrastructure audit and maintenance cycle, we completed a significant consolidation of the adamcherrycomics.dangerouscentaur.com deployment, removing legacy bucket cruft and validating our shared infrastructure model. This post details the technical decisions, infrastructure cleanup, and lessons learned from moving away from a per-site bucket pattern.

The Problem: Legacy Bucket Overhead

The adamcherrycomics.dangerouscentaur.com site was originally deployed using a per-site S3 bucket pattern: one bucket per hostname, each with its own CloudFront origin. Over time, as we onboarded more dangerouscentaur.com subdomains, this pattern created operational friction:

  • Bucket sprawl: One bucket per site meant N buckets to monitor, patch, and clean up.
  • Origin management: Each CloudFront distribution had a dedicated origin, complicating failover and routing logic.
  • Billing fragmentation: Separate request metrics and storage costs per bucket.
  • Deployment complexity: Different deployment paths for different sites.

The migration to a shared-bucket model had already been completed for adamcherrycomics, but the legacy bucket remained in S3, empty and unused—a remnant of the old pattern.

Infrastructure Audit: Verifying the Shared Model

Before deleting the stale bucket, we verified that adamcherrycomics.dangerouscentaur.com had been fully migrated to the new architecture:

1. DNS Configuration

adamcherrycomics.dangerouscentaur.com CNAME dclu4nl5nln98.cloudfront.net

The DNS record points directly to the CloudFront distribution, not to an S3 website endpoint. This is the correct pattern for our shared infrastructure.

2. CloudFront Distribution: E2Q4UU71SRNTMB

The distribution has a single origin:

Origin: dc-sites.s3.us-east-1.amazonaws.com
Origin Path: /

All dangerouscentaur.com subdomains are served from this one bucket.

3. Request Routing: dc-sites-router CloudFront Function

The key piece of the shared model is a CloudFront Function that intercepts viewer requests and rewrites the origin path based on the Host header:

// Pseudocode logic
if (request.headers.host === 'adamcherrycomics.dangerouscentaur.com') {
  request.origin.s3.path = '/adamcherrycomics.dangerouscentaur.com' + request.uri;
}

This function runs at CloudFront edge locations (viewer-request stage) and dynamically routes requests to the correct prefix within the shared bucket.

4. S3 Bucket Structure

Within dc-sites.s3.us-east-1.amazonaws.com, the bucket is organized by hostname:

dc-sites/
  adamcherrycomics.dangerouscentaur.com/
    index.html
    about-artist/
      index.html
    gallery/
      index.html
    ...
  otherdomain.dangerouscentaur.com/
    ...

No separate bucket for adamcherrycomics is needed.

Technical Decision: Why Delete the Legacy Bucket?

After confirming that all live traffic was routed through the CloudFront distribution and shared bucket, we identified three criteria for safe deletion:

  • Bucket is empty: s3://adamcherrycomics.dangerouscentaur.com/ contained zero objects and was not serving any content.
  • Not on the serving path: CloudFront distribution E2Q4UU71SRNTMB was configured with a different origin (dc-sites), so the hostname-named bucket was never consulted for requests.
  • No DNS dependencies: The CNAME pointed to CloudFront, not to an S3 website endpoint, so deleting the bucket would not break DNS resolution.

The legacy bucket was a vestige of the old per-site pattern and was safe to remove.

Execution: Cleanup and Validation

We used the AWS CLI to verify and delete:

aws s3api head-bucket --bucket adamcherrycomics.dangerouscentaur.com
# Returns 200 (bucket exists)

aws s3api list-objects-v2 --bucket adamcherrycomics.dangerouscentaur.com --max-items 10
# Returns: Contents: [] (empty)

aws s3api delete-bucket --bucket adamcherrycomics.dangerouscentaur.com
# Success

After deletion, we verified that the live site continued to serve 200 responses and content was present:

curl -I https://adamcherrycomics.dangerouscentaur.com/
# HTTP/2 200

curl https://adamcherrycomics.dangerouscentaur.com/ | grep -i ""
# <title>Adam Cherry Comics

All endpoints remained live, confirming that no part of the request path depended on the deleted bucket.

Lessons and Patterns

Shared-Bucket + Router Function Model

This architecture offers several advantages:

  • Single origin: One S3 bucket for all subdomains reduces administrative overhead.
  • Edge routing: The CloudFront Function executes at edge locations, adding negligible latency (microseconds) while providing flexible routing logic.
  • Simplified deployments: All subdomains deploy to the same bucket with consistent naming conventions.
  • Consolidated billing: One bucket's metrics make cost attribution clearer.

The CloudFront Function is the critical piece—it abstracts the multi-tenant structure from the origin and allows the bucket to remain simple.

Bucket Naming and Lifecycle

S3 bucket names must be globally unique and are expensive to re-create if deleted. However, keeping empty legacy buckets has minimal cost (storage is free for empty buckets; the main cost is management operations). In this case, deletion was justified because the bucket name is unlikely to be needed again—the shared-bucket pattern has superseded the per-site pattern organization-wide.

What's Next

  • Monitor for regressions: Watch CloudFront and S3 metrics for 48 hours to ensure no unexpected traffic patterns after bucket deletion.
  • Document the shared model: Update the infrastructure runbook to reflect that all dangerouscentaur.com subdomains use the shared-bucket + router pattern, not per-site buckets.
  • Apply to other legacy sites: Audit other dc-sites deployments for similar stale buckets and consolidate where safe.