Deploying a Static Charter Proposal Page: S3 + CloudFront + Route53 Content Distribution
Overview
This post documents the deployment of a new charter proposal landing page for Queen of San Diego. The work involved creating a static HTML proposal document, uploading it to S3, invalidating CloudFront cache, and verifying DNS resolution through Route53. This is a practical example of how to manage content updates in a serverless static site architecture.
What Was Done
A new proposal page was created and deployed to production at queenofsandiego.com/proposals/jada-charter-proposal-sue.html. The page includes pricing options, legal compliance notes, historical content, and a booking inquiry form. The deployment followed the existing static site pipeline: local development → S3 upload → CloudFront invalidation → live verification.
Technical Details: File Structure and Content
Source Repository Location:
/Users/cb/Documents/repos/sites/queenofsandiego.com/proposals/jada-charter-proposal-sue.html
The file was created as a single HTML document with inline CSS styling. This approach was chosen over a template-based system because:
- Proposal pages are one-off documents that rarely need component reuse
- Self-contained HTML eliminates template rendering overhead on deployment
- Easy to version control and audit specific proposal content changes
- Faster load time—no server-side template processing required
Content Sections:
- Proposal header with greeting and vessel details
- Two pricing tiers (2-hour charter with captain only, 3-hour charter with full crew)
- Legal compliance notes covering USCG licensing and EPA/MPRSA ash scattering regulations
- Historical context section featuring Hollywood maritime connections
- Booking inquiry form with fields for name, email, date preferences, and message
Infrastructure: S3 and CloudFront Configuration
S3 Bucket:
s3://queenofsandiego.com/proposals/
The file was uploaded to the existing S3 bucket configured to serve the primary domain. S3 bucket structure mirrors the local repository to simplify deployment scripting:
s3://queenofsandiego.com/
├── proposals/
│ ├── jada-charter-proposal-sue.html
│ └── [other proposal files]
├── assets/
├── index.html
└── [other static content]
Upload Command:
aws s3 cp proposals/jada-charter-proposal-sue.html \
s3://queenofsandiego.com/proposals/jada-charter-proposal-sue.html \
--region us-east-1 \
--cache-control "public, max-age=3600"
The cache-control header was set to 1 hour (3600 seconds) for proposal pages. This balances freshness with CDN efficiency—proposals are relatively stable documents, but may receive urgent updates. Longer caching (e.g., 24 hours) would reduce CloudFront hits but increase update latency.
CloudFront Distribution:
The content is served through a CloudFront distribution (ID stored in environment secrets file, accessed via $CLOUDFRONT_DIST_ID at deploy time). CloudFront behavior was configured with:
- Origin: S3 bucket with origin access identity (OAI) to restrict direct S3 access
- Viewer protocol policy: HTTPS only, with automatic HTTP-to-HTTPS redirect
- Default TTL: 86400 seconds (24 hours)
- Compress: Enabled (gzip for HTML, CSS, JSON)
CloudFront Invalidation:
After S3 upload, CloudFront cache was invalidated to ensure users see the new content immediately:
aws cloudfront create-invalidation \
--distribution-id $CLOUDFRONT_DIST_ID \
--paths "/proposals/jada-charter-proposal-sue.html"
This creates an invalidation request that removes the cached version from all edge locations. Invalidation typically completes within 60 seconds but can take up to 15 minutes during peak traffic. We monitored the status with:
aws cloudfront get-invalidation \
--distribution-id $CLOUDFRONT_DIST_ID \
--id [invalidation-id]
Route53 Verification:
The domain queenofsandiego.com is managed via Route53 with an alias record pointing to the CloudFront distribution endpoint. No changes were needed to DNS configuration for this deployment—the proposal lives within the existing CloudFront distribution's namespace.
Key Decisions and Trade-offs
1. Single HTML File vs. Template System
Proposal documents are inherently static and proposal-specific. Rather than introduce a template layer (Jinja2, Handlebars, etc.), we opted for a self-contained HTML file. This eliminates:
- Template rendering at build time
- Dependency management for templating libraries
- Complexity in the deployment pipeline
2. Cache Duration Strategy
CloudFront default TTL is 24 hours, but S3 cache-control is 1 hour. This two-tier caching approach provides:
- Long-term caching at edge locations (cost reduction)
- Faster update propagation via S3 metadata control
- Ability to invalidate critical content without waiting 24 hours
3. Legal Compliance in Content
The proposal includes explicit language regarding USCG licensing and EPA/MPRSA regulations. This was included because charter services involving passenger transport and ash scattering fall under federal maritime and environmental law. The documentation protects both the business and customers by setting clear expectations.
Deployment Automation
The deployment process is automated via a shell script at:
/Users/cb/Documents/repos/sites/queenofsandiego.com/publish_static_site.sh
This script:
- Sources credentials from
/Users/cb/Documents/repos/.secrets/repos.env - Runs
aws s3 cpfor all modified files in the proposals directory - Triggers CloudFront invalidation for updated paths
- Logs output for audit trails
Typical execution:
cd /Users/cb/Documents/repos/sites/queenofsandiego.com
set -a; source /Users/cb/Documents/repos/.secrets/repos.env; set +a
./publish_static_site.sh
Verification
Post-deployment verification confirmed the page is live and accessible:
curl -s "https://queenofsandiego.com/proposals/jada-charter-proposal-sue.