Deploying a Charter Service Proposal Page: From Template to Production S3/CloudFront
Overview
This post covers the end-to-end deployment of a custom HTML proposal page for a charter service, including static site generation, S3 bucket management, CloudFront cache invalidation, and content management patterns for multi-stakeholder approval workflows.
The Problem: Template Without Instance
The repository contained a proposal template structure at /Users/cb/Documents/repos/sites/queenofsandiego.com/proposals/, but the actual proposal file jada-charter-proposal-sue.html was never instantiated. This is a common pattern in static site generators where templates exist but corresponding content files must be explicitly created.
Discovery process:
- Searched the proposals directory:
find /Users/cb/Documents/repos/sites/queenofsandiego.com -name "*proposal*" - Verified the directory existed but was missing the specific proposal instance
- Located the publish script at
/Users/cb/Documents/repos/sites/queenofsandiego.com/publish_static_site.shto understand the build/deploy pipeline
Technical Architecture
Source Control Layout
/Users/cb/Documents/repos/sites/queenofsandiego.com/
├── proposals/
│ └── jada-charter-proposal-sue.html [newly created]
├── assets/
│ └── images/
│ ├── interior/ [charter vessel photos]
│ └── [social proof imagery]
├── publish_static_site.sh [build automation]
└── [other site content]
Deployment Pipeline
The site uses a three-tier static hosting pattern:
- Local source: HTML files in the Git repository
- S3 origin: CloudFront-backed S3 bucket (domain:
queenofsandiego.com) - CDN edge: CloudFront distribution with cache invalidation for updates
Content Creation: Key Decisions
Proposal Structure
The proposal was built with decision-fatigue reduction as a core principle — exactly two pricing options presented:
- Option A (recommended): 2-hour charter with Captain Sergio only — $750 flat
- Option B: 3-hour charter with full crew — $1,575 flat
This binary choice reduces cognitive load compared to a matrix of options, increasing conversion likelihood. The "recommended" label creates a gentle default without being pushy.
Compliance & Legal Language
The page includes critical regulatory notes:
- EPA/MPRSA ash scattering regulations: Specified because this is not a bareboat charter — the vessel operator (Captain Sergio) maintains full responsibility for EPA compliance under the Marine Protection, Research, and Sanctuaries Act
- USCG licensing disclosure: Clarified that this is a licensed charter service, not bareboat rental, which has different insurance and liability implications
Why this matters: Charter services have different regulatory exposure than bareboat rentals. Including this language up-front prevents misunderstandings during the sales cycle.
Branding & Narrative Content
The proposal includes a "Hollywood History" section referencing the San Diego waterfront's connection to classic cinema (Bogart, Bacall, Flynn, Wayne). This serves multiple functions:
- Differentiates the offering from commodity boat rentals
- Builds emotional connection through storytelling
- Justifies premium pricing through cultural positioning
Infrastructure & Deployment Commands
Publishing to S3
After creating the HTML file locally, the publish script handles credentials via environment variables:
cd /Users/cb/Documents/repos/sites/queenofsandiego.com
set -a; source /Users/cb/Documents/repos/.secrets/repos.env; set +a
aws s3 cp proposals/jada-charter-proposal-sue.html s3://queenofsandiego.com/proposals/jada-charter-proposal-sue.html \
--content-type "text/html" \
--cache-control "public, max-age=3600"
Key parameters:
--content-type "text/html"ensures browsers render as HTML, not download--cache-control "public, max-age=3600"sets a 1-hour edge cache TTL (appropriate for content that changes infrequently but needs updates within 1 hour)- Environment variables sourced from
.secrets/repos.envcontain AWS credentials (never hardcoded)
CloudFront Cache Invalidation
S3 bucket updates don't immediately flush CloudFront edge caches. Invalidation is required:
aws cloudfront create-invalidation \
--distribution-id [DISTRIBUTION_ID] \
--paths "/proposals/jada-charter-proposal-sue.html"
The distribution ID is stored in the secrets environment file and sourced at runtime. This pattern separates configuration from code.
Verification
After deployment, verify the object exists and is accessible:
aws s3api head-object \
--bucket queenofsandiego.com \
--key proposals/jada-charter-proposal-sue.html
Then verify it's live on the CDN:
curl -s "https://queenofsandiego.com/proposals/jada-charter-proposal-sue.html" | head -20
Content Management Patterns: Multi-Stakeholder Approval
The session generated two memory artifacts to support future iterations:
feedback_no_meta_copy_in_proposals.md— Tracks the constraint that proposal pages don't include meta tag descriptions (to prevent search engine indexing of internal commercial documents)reference_godaddy_credentials.md— Maps domain registration details (used during CloudFront invalidation verification)
These memory files serve as a decision log, enabling other engineers to understand why certain patterns were followed without needing to reconstruct the reasoning.
Image Asset Management
The proposals directory references interior yacht photography from:
/Users/cb/Documents/repos/sites/queenofsandiego.com/assets/images/interior/
Images are committed to Git (not fetched from external sources) because:
- Proposal pages are self-contained static documents
- External image dependencies create availability risk (broken proposal links)
- Git LFS or S3 direct references would be used at scale, but for proposal documents, embedded asset paths are acceptable
Key Decisions & Trade-offs
| Decision | Rationale |
|---|---|