Publishing Charter Manifests to CloudFront: AWS S3 + CDN Cache Invalidation Workflow
Over the weekend, we deployed a new workflow for publishing charter passenger manifests and trip sheets to our CloudFront-backed S3 distribution. This post covers the infrastructure decisions, authentication challenges, and cache invalidation strategy we implemented for the JADA operations crew page.
What Was Done
We created a repeatable process for generating HTML manifests (passenger lists, crew assignments, payment details) and publishing them to S3 with proper CloudFront invalidation. The workflow handles two document types:
- Manifests — passenger rosters with names, contact info, and crew assignments (e.g.,
quinn-male-manifest.html) - Trip sheets — operational details for crew reference (e.g.,
quinn-male-trip-sheet.html)
Both documents are generated from calendar event data, published to S3 at predictable paths, and invalidated through CloudFront to ensure crew members see the latest version immediately.
Technical Details: The Publishing Pipeline
Step 1: Document Generation
We generate manifests as static HTML files in /tmp:
/tmp/quinn-male-manifest.html
/tmp/quinn-male-trip-sheet.html
Each file contains passenger names extracted from JADA calendar events via the calendar API, formatted with CSS for printability. The manifest includes:
- Passenger full names and contact phone numbers
- Crew assignments (captain, mate, engineer)
- Vessel name and departure time
- Payment status and amounts
Step 2: S3 Publishing with Dual Paths
Documents are published to two S3 prefixes in the shipcaptaincrew bucket:
- docs/ prefix — persistent storage used by the crew page frontend to fetch documents on-demand
- print/ prefix — snapshot for archive/backup purposes
Example S3 paths:
s3://shipcaptaincrew/docs/quinn-male-manifest.html
s3://shipcaptaincrew/docs/quinn-male-trip-sheet.html
s3://shipcaptaincrew/print/quinn-male-manifest.html
Live URLs through CloudFront follow this pattern:
https://shipcaptaincrew.com/docs/quinn-male-manifest.html
We verified HTTP 200 responses immediately after publishing, confirming S3 bucket permissions and CloudFront origin configuration were correct.
Step 3: CloudFront Cache Invalidation
The critical insight: S3 publishes happen instantly, but CloudFront edge caches can serve stale content for hours. We solved this by invalidating the CloudFront distribution after each publish.
The CloudFront distribution serving shipcaptaincrew.com uses S3 as its origin, with the /docs prefix configured to serve documents directly. After publishing new manifests, we invalidate paths:
/docs/quinn-male-manifest.html
/docs/quinn-male-trip-sheet.html
/docs/*
This forces CloudFront edge nodes to fetch fresh content from S3 on the next request, typically within 5-10 seconds.
Infrastructure: AWS Services & Configuration
S3 Bucket: shipcaptaincrew
Bucket configuration:
- Region: us-west-2 (coast-local for crew access)
- Public read access via CloudFront origin access identity (OAI)
- Prefixes:
/docs(live crew page),/print(archives),/snapshots(Lambda build outputs)
CloudFront Distribution
Distribution ID: found via AWS CLI query to avoid hardcoding
Key settings:
- Origin: S3 bucket with OAI for secure access
- Default TTL: 3600 seconds (1 hour) — allows browser caching while staying reasonably fresh
- Cache behaviors:
/docs/*inherits default settings; manifest documents cache aggressively - Invalidation patterns: Supports wildcard paths like
/docs/*for bulk invalidations
Authentication: AWS Credentials & Session Management
The biggest operational challenge was AWS session management. Initial attempts to publish failed with authorization errors because the AWS session had expired. We solved this by:
- Using AWS CLI's built-in credential refresh (via
aws sts get-caller-identity) to verify active sessions - Implementing re-authentication when needed (AWS credentials are stored in
~/.aws/credentialsand automatically refreshed by boto3) - Separating S3 publish operations from CloudFront invalidation into atomic steps, allowing retry on auth failure
Key commands used for verification:
aws sts get-caller-identity
aws s3 ls s3://shipcaptaincrew/docs/
aws cloudfront list-invalidations --distribution-id [DIST_ID]
Integration with Existing Systems
Crew Page Document Rendering
The frontend SPA (in the shipcaptaincrew repository) renders event pages that list associated documents. The page builder function (build_event_pages in the Lambda backend) generates links to documents stored in S3:
https://shipcaptaincrew.com/docs/[event_id]-manifest.html
By publishing manifests to the /docs prefix with naming convention [vessel-name]-manifest.html, the frontend can discover and display them without code changes.
Local Archive Strategy
We also maintain local copies in the jada-ops repository for durability and audit trails:
/Users/cb/Documents/repos/jada-ops/quinn-male/quinn-male-manifest.html
/Users/cb/Documents/repos/jada-ops/quinn-male-charter.md
This creates a three-tier storage model:
- Local git repos — version-controlled source of truth
- S3 /docs — live crew-facing documents served via CloudFront
- S3 /print — immutable snapshots for compliance/archive
Key Decisions & Why
Why dual S3 prefixes? The /docs prefix is for live operations (crew members fetch on-demand), while /print is immutable. This prevents accidental overwrites of archived documents while allowing the frontend to refresh manifests as passenger data changes.
Why CloudFront invalidation? S3 updates are instant, but users in remote locations rely on CloudFront for performance. Without invalidation, a captain in San Diego might see stale passenger data until the cache expires naturally. Invalidation ensures everyone sees the latest version within seconds.
Why HTML instead of