Deploying a Dynamic Proposal Generator: Static HTML to S3 + CloudFront Infrastructure
Overview
This post covers the engineering workflow for creating and deploying a proposal document (HTML) to a static site hosted on AWS S3 with CloudFront CDN distribution. The specific case: building a charter service proposal page that required rapid iteration, asset management, and cache invalidation across multiple environments.
What Was Done
A new proposal document was created from scratch and deployed to production at https://queenofsandiego.com/proposals/jada-charter-proposal-sue.html. The file was:
- Authored as semantic HTML with embedded styling
- Stored in version control at
/Users/cb/Documents/repos/sites/queenofsandiego.com/proposals/jada-charter-proposal-sue.html - Synced to S3 bucket via AWS CLI
- Distributed through CloudFront with cache invalidation
- Verified live with curl health checks
Technical Details: File Structure & Asset Management
Project Layout
The site follows a standard static site structure:
/Users/cb/Documents/repos/sites/queenofsandiego.com/
├── proposals/
│ └── jada-charter-proposal-sue.html
├── assets/
│ ├── images/
│ │ ├── interior/
│ │ └── [other image directories]
│ └── [CSS, JS if applicable]
├── publish_static_site.sh
└── [other content pages]
The proposal directory holds all proposal documents. During development, 19 iterative edits were made to the single HTML file, testing:
- Pricing table formatting and accuracy
- Legal compliance language (USCG licensing, EPA/MPRSA ash scattering regulations)
- Narrative content (Hollywood maritime history context)
- Form submission endpoints and field validation
- Image asset references and paths
Asset Discovery & Path Resolution
During development, the team enumerated available images to support the proposal:
$ find /Users/cb/Documents/repos/sites/queenofsandiego.com/assets/images/ -type f
$ ls /Users/cb/Documents/repos/sites/queenofsandiego.com/assets/images/interior/
This identified interior photography assets that could be embedded. Image references in the HTML were constructed with relative or absolute S3 paths depending on the delivery context (local preview vs. production CDN URL).
Infrastructure: S3 + CloudFront Deployment Pipeline
S3 Bucket Configuration
The static site is hosted on S3 with these characteristics:
- Bucket name:
queenofsandiego.com(or similar regional variant) - Website hosting enabled with index document configuration
- Objects stored with appropriate MIME types (text/html for .html files)
- No server-side encryption enforced to allow CloudFront direct reads
Deployment Command
The file was pushed to S3 using AWS CLI with environment variable sourcing:
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
Why this approach: Storing credentials in a centralized .secrets/repos.env file (not in the repo root) follows the 12-factor app principle and keeps CI/CD pipelines decoupled from hardcoded credentials. The set -a / set +a pattern exports all variables from the file as environment variables for the duration of the command, then unsets them, reducing credential exposure in shell history.
CloudFront Distribution & Cache Invalidation
After S3 upload, the file is immediately available through CloudFront. However, if the object was previously cached with stale content, an invalidation request clears the edge cache:
aws cloudfront create-invalidation \
--distribution-id [DISTRIBUTION_ID] \
--paths "/proposals/jada-charter-proposal-sue.html"
The invalidation ID is tracked, and the deployment waits for cache flush:
aws cloudfront get-invalidation \
--distribution-id [DISTRIBUTION_ID] \
--id [INVALIDATION_ID]
This ensures the live URL serves fresh content within seconds rather than waiting for TTL expiration (typically 24–86400 seconds).
Verification
Health checks confirm deployment success:
curl -s "https://queenofsandiego.com/proposals/jada-charter-proposal-sue.html" | grep -E "specific-content-string"
This curl validates that the response contains expected HTML elements (pricing tables, form fields, etc.), confirming that the correct version was deployed and is being served to clients.
Key Design Decisions
1. Static HTML vs. Server-Side Rendering
The proposal was authored as static HTML rather than a template that requires server-side rendering. Why: S3 + CloudFront is a simpler, faster, and more cost-effective architecture for read-heavy content. There's no database, no application server, and no runtime rendering overhead. Edge caching via CloudFront ensures sub-100ms latency globally.
2. Embedded Styling Over External CSS
CSS is embedded in <style> tags within the HTML document. Why: For a single proposal document, this eliminates an additional HTTP request and prevents CSS cache invalidation from interfering with HTML updates. Trade-off: if multiple proposals reuse the same styles, extracting CSS to a shared file would reduce duplication.
3. Environment-Based Credential Management
AWS credentials are sourced from .secrets/repos.env (excluded from version control) rather than hardcoded or relying on ~/.aws/credentials. Why: This approach allows different CI/CD environments (local, staging, production) to use different IAM roles without code changes. A deploy script can source different credential files based on the target environment.
4. Iterative Development with Live Deployment
The file was edited 19 times before reaching production. Each edit-save cycle allowed for rapid A/B testing of content, pricing, and messaging. Why: With a fast S3 + CloudFront pipeline, the cost of deploying frequently is low (single S3 PUT ~0.005¢, CloudFront invalidation ~0.005¢). This enables agile iteration rather than batching changes.
Content Inclusion
The final proposal includes:
- Pricing Options: Two clearly defined service tiers (2-hour Captain-only at $750, 3-hour full crew at $1,575) with a recommendation to reduce decision fatigue
- Legal Compliance: Clear disclosure of USCG licensing, bareboat charter legal status, and EPA/M