Deploying a Dynamic Charter Proposal Page: S3 + CloudFront + Route53 Integration
What Was Done
Created and deployed a production charter proposal page at queenofsandiego.com/proposals/jada-charter-proposal-sue.html
that was previously missing from the static site despite existing in the project template structure. The page required
iterative HTML refinement, asset management, and CDN cache invalidation to ensure live deployment accuracy.
Technical Details: File Structure and Version Control
The project uses a Git-based workflow with the following directory structure:
/Users/cb/Documents/repos/sites/queenofsandiego.com/
├── proposals/
│ └── jada-charter-proposal-sue.html
├── assets/
│ └── images/
│ └── interior/
├── publish_static_site.sh
└── rady-shell-events/
└── sites/blackcoffee/assets/images/
The proposal file itself is a self-contained HTML document with embedded CSS (no external stylesheets required for deployment), making it portable and cacheable as a single object in S3. The file went through multiple iterations—approximately 20+ edits— to refine pricing display, legal compliance language, historical content accuracy, and form validation logic.
Infrastructure: S3, CloudFront, and DNS Resolution
S3 Bucket Configuration
The static site is hosted in an S3 bucket (bucket name follows convention queenofsandiego.com or similar).
The deployment process copies the HTML file to the proposals prefix:
aws s3 cp proposals/jada-charter-proposal-sue.html \
s3://queenofsandiego.com/proposals/jada-charter-proposal-sue.html \
--acl public-read \
--content-type text/html
Note: The S3 bucket is configured with public-read ACLs and static website hosting enabled. Object metadata includes appropriate Content-Type headers to ensure browsers render HTML rather than prompting downloads.
CloudFront Distribution and Cache Invalidation
A CloudFront distribution (identified by a distribution ID stored in encrypted environment variables) sits in front of the S3 bucket as the CDN edge layer. After each S3 upload, the distribution must be invalidated to purge cached versions and serve fresh content:
aws cloudfront create-invalidation \
--distribution-id $CLOUDFRONT_DIST_ID \
--paths "/proposals/jada-charter-proposal-sue.html"
The distribution ID and credentials are stored in /Users/cb/Documents/repos/.secrets/repos.env and sourced
at runtime using set -a; source ... pattern to avoid hardcoding secrets in shell scripts or version control.
Invalidation status can be monitored:
aws cloudfront get-invalidation \
--distribution-id $CLOUDFRONT_DIST_ID \
--id $INVALIDATION_ID
DNS and Route53
The domain queenofsandiego.com is registered with GoDaddy and likely uses Route53 alias records pointing
to the CloudFront distribution domain (e.g., d123xyz.cloudfront.net). This allows the human-readable
domain to resolve through AWS edge locations, providing both caching and DDoS protection.
Key Architectural Decisions
1. Static HTML Over Template Generation
The proposal page is a single, self-contained HTML file rather than a dynamically generated template. Why: Reduces complexity for a content-heavy, low-frequency-update document; eliminates backend dependencies (no Node, Python, or database required); maximizes CloudFront cache hit ratio since the object fingerprint is stable between edits.
2. Embedded CSS to Minimize HTTP Requests
All styling is inlined within <style> tags rather than external .css files.
Why: Single HTTP request; no additional asset lookups; ensures styles travel with the HTML object
through S3/CloudFront as an atomic unit.
3. Multi-Stage Deployment Validation
Before invalidating CloudFront, the file is committed to Git, uploaded to S3, and then validated using curl
to confirm live delivery. Local edits → S3 upload → CloudFront invalidation → curl verification ensures no stale content
reaches users.
curl -s "https://queenofsandiego.com/proposals/jada-charter-proposal-sue.html" | grep -E "pattern"
4. Environment Variable Isolation for Credentials
AWS access keys, CloudFront distribution IDs, and S3 bucket names are stored in a Git-ignored .secrets/repos.env
file, sourced at command execution time. This prevents credential leakage in shell history, logs, or version control while
keeping deployment scripts portable and reusable.
Content and Business Logic
The page implements the following features:
- Two-tier pricing: Option A (2-hr charter, Captain Sergio only, $750) and Option B (3-hr charter, full crew, $1,575)
- Legal compliance section: USCG licensing, bareboat charter disclaimers, and EPA/MPRSA ash scattering regulations
- Historical context: Hollywood era narrative featuring classical actors (Bogart, Bacall, Flynn, Wayne)
- Decision fatigue mitigation: Exactly two mutually exclusive options with clear recommendation (Option A)
- Contact form: Q&A submission form posting to
bookings@queenofsandiego.com
Asset Management and Image Handling
Interior photography and promotional images are stored in assets/images/interior/. The deployment script
references these assets with relative paths in HTML, and CloudFront caches image objects separately from the HTML file,
allowing granular cache invalidation if only images need refreshing without touching the HTML.
For asset uploads:
aws s3 cp assets/images/interior/filename.jpg \
s3://queenofsandiego.com/assets/images/interior/filename.jpg
Monitoring and Debugging
Key diagnostic commands used during development:
find /Users/cb/Documents/repos/sites/queenofsandiego.com -name "*proposal*"— locates all proposal filesgrep -n "pattern" file.html— validates content presence before deploymentaws s3api head-object --bucket queenofsandiego.com --key proposals/jada-charter-proposal-sue.html— confirms object metadata and ETags