```html

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:

  1. Using AWS CLI's built-in credential refresh (via aws sts get-caller-identity) to verify active sessions
  2. Implementing re-authentication when needed (AWS credentials are stored in ~/.aws/credentials and automatically refreshed by boto3)
  3. 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