Provision the frontend's AWS hosting as a CDK app local to this repo (private S3 + OAC, CloudFront with SPA 403/404 -> index.html fallback) and a GitHub OIDC deploy role scoped to the dev branch. Deploy on push to dev via the org reusable cd-cdk.yaml: cdk deploy provisions infra, then scripts/deploy-web.sh builds the SPA, syncs to S3 (immutable hashed assets, no-cache index.html) and invalidates CloudFront. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6 KiB
Infrastructure & CI/CD — SeaHaven SHOC frontend
AWS hosting for the Vite SPA, defined as an AWS CDK app local to this repo, deployed through the org's reusable GitHub Actions workflow.
- Hosting: private S3 bucket (origin) + CloudFront (CDN, HTTPS, SPA fallback)
- Auth: GitHub Actions → AWS via OIDC (no long-lived keys)
- CD workflow:
.github/workflows/deploy.ymlis a thin caller of the org'sSea-Haven-Industries/.github→cd-cdk.yaml. That workflow runscdk deploy(provisions infra) thenscripts/deploy-web.sh(builds + uploads the SPA). - Infra is local to this repo (CDK in
infra/cdk); the deploy role is created by this stack, not added to the centraloidc-deploy-roles.yaml. - Environments:
devonly today, deployed on push to thedevbranch.
infra/cdk/
bin/app.ts entry point (reads -c context)
lib/frontend-stack.ts S3 + CloudFront + OAC + OIDC deploy role
scripts/deploy-web.sh build SPA -> s3 sync -> CloudFront invalidation
.github/workflows/
ci.yml quality gates (lint / build / test / e2e)
deploy.yml caller of the org reusable cd-cdk.yaml (push to dev)
What the stack creates
| Resource | Purpose |
|---|---|
S3 bucket seahaven-shoc-frontend-dev |
private origin (BLOCK_ALL, SSE, OAC-only reads) |
| CloudFront distribution | HTTPS, 403/404 → 200 /index.html SPA fallback, gzip/br |
IAM role githubdeploy-shoc-frontend-new-dev |
assumed by GitHub Actions via OIDC, scoped to repo:Sea-Haven-Industries/shoc-frontend-new:ref:refs/heads/dev |
The whole cd-cdk.yaml job runs as that role, so it holds: sts:AssumeRole on
cdk-hnb659fds-* (for cdk deploy), cloudformation:DescribeStacks (cd-cdk's
pre-flight/health-check + output reads), read/write on the bucket (s3 sync),
and cloudfront:CreateInvalidation (cache bust). The OIDC provider is a
singleton account resource — the stack only imports it (created in step 2),
so cdk destroy can't delete a resource shared by other roles.
One-time setup (run by a human with admin AWS creds)
1. Authenticate to the AWS account
aws configure # or: aws sso login --profile <admin>
aws sts get-caller-identity # confirm the right account + region (us-east-1)
2. Ensure the GitHub OIDC provider exists (once per account)
aws iam list-open-id-connect-providers
# If none ends in token.actions.githubusercontent.com, create it (thumbprint is
# no longer required — AWS validates GitHub against its own trust store):
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com
3. CDK bootstrap (once per account/region)
cd infra/cdk
npm ci
npx cdk bootstrap aws://<ACCOUNT_ID>/us-east-1
4. Set the dev API URL (gates a working app)
The build reads VITE_API_URL from .env.production (committed; it's a
public URL, not a secret). The reusable workflow can't inject it, so it must
live there. It must be HTTPS and end in /api — CloudFront serves the app
over HTTPS and browsers block HTTP (mixed-content) API calls.
# .env.production
VITE_API_URL=https://<dev-api-host>/api
5. First deploy (locally, with admin creds)
The deploy role doesn't exist until the first cdk deploy, so bootstrap it
locally. This provisions infra + the role:
cd infra/cdk
npx cdk deploy
Note the DeployRoleArn output. Then push the first content (or just push to
dev and let CI do everything from here on):
# from repo root, optional manual first content publish:
STACK_NAME=shoc-frontend-dev AWS_REGION=us-east-1 bash scripts/deploy-web.sh
6. Set the one GitHub secret
cd-cdk.yaml takes the role ARN as a secret (not a variable):
REPO=Sea-Haven-Industries/shoc-frontend-new
gh secret set AWS_DEPLOY_ROLE_ARN --repo "$REPO" \
--body "arn:aws:iam::<acct>:role/githubdeploy-shoc-frontend-new-dev"
(Or Settings → Secrets and variables → Actions → Secrets.)
7. From now on: push to dev
git push origin dev
ci.yml runs the quality gates and deploy.yml calls cd-cdk.yaml, which runs
cdk deploy then scripts/deploy-web.sh. Watch the Actions tab, then open
the SiteUrl output.
First-run verification: this first push is what actually exercises the role's permissions and the OIDC trust through the reusable workflow (the local bootstrap used admin creds and tested none of that). Watch for credential/OIDC errors and a green post-deploy step.
Adding staging / prod later
Separate accounts: deploy this stack there with -c envName=staging
(and -c deployBranch=<branch>), set that repo's AWS_DEPLOY_ROLE_ARN secret,
and add a job to deploy.yml. Because .env.production bakes a single API URL
into every vite build, multi-env needs a per-account value — the AWS-native
fit is to have scripts/deploy-web.sh do aws ssm get-parameter →
export VITE_API_URL before the build (one SSM param per account; add
ssm:GetParameter to the role). Not needed for dev-only.
Notes
- Teardown:
npx cdk destroy. The bucket usesRemovalPolicy.DESTROY+autoDeleteObjects(dev artifacts are reproducible) — change this for prod. - CI and CD both fire on push to
devin parallel; a red-CI commit still deploys (matches the org's push-time-CD model). Gating deploy on CI is a follow-up, not part of enabling CICD. - Local npm is pinned to v6; the committed
package-lock.jsonis lockfileVersion 1. CI (Node 24 / npm 11) reads it fine vianpm ci.