shoc-frontend-new/infra/cdk
Alexandre Brandizzi fcf2ce185f
Some checks are pending
Frontend checks / Build and test (push) Waiting to run
Frontend checks / governance (push) Waiting to run
Frontend checks / Visual regression (push) Waiting to run
Deploy staging / Deploy to staging (push) Waiting to run
hotfix(staging): deploy vendor table overflow fix (#155)
* ci: add protected staging frontend deployment lane (#151)

* ci: add protected staging deployment lane

* fix: constrain staging publisher permissions

* fix: handle first-push governance baseline

---------

Co-authored-by: Codex Review Integration <codex-review@local.invalid>

* fix(vendors): truncate long textual table values (#154)

* fix(vendors): truncate long locations in table

* fix(vendors): truncate textual table cells

---------

Co-authored-by: Codex Review Integration <codex-review@local.invalid>

---------

Co-authored-by: Codex Review Integration <codex-review@local.invalid>
2026-08-28 12:31:58 -04:00
..
bin hotfix(staging): deploy vendor table overflow fix (#155) 2026-08-28 12:31:58 -04:00
lib hotfix(staging): deploy vendor table overflow fix (#155) 2026-08-28 12:31:58 -04:00
cdk.json feat(infra): AWS S3 + CloudFront CD pipeline on dev.seahaven.com (#21) 2026-07-07 06:14:06 -03:00
package-lock.json chore: upgrade frontend dependencies (#24) 2026-07-14 21:35:59 -03:00
package.json chore: correct Sea Haven branding and rewrite README (#25) 2026-07-17 13:17:21 -04:00
README.md hotfix(staging): deploy vendor table overflow fix (#155) 2026-08-28 12:31:58 -04:00
tsconfig.json feat(infra): AWS S3 + CloudFront CD pipeline on dev.seahaven.com (#21) 2026-07-07 06:14:06 -03:00

Infrastructure & CI/CD — Sea Haven 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, served on the custom domain dev.seahaven.com (ACM *.seahaven.com, Route 53 apex alias).
  • API: the SPA calls the backend directly over HTTPS at https://api.dev.seahaven.com/api (VITE_API_URL, cross-origin; the backend allows CORS). CloudFront serves static content only — no /api proxy.
  • Domain/cert/zone values live in cdk.json context so the CI cdk deploy picks them up with no flags. VITE_API_URL is baked into the build, so it's per-environment (see the note under "Adding staging / prod").
  • Auth: GitHub Actions → AWS via OIDC (no long-lived keys)
  • CD workflow: .github/workflows/deploy.yml is a thin caller of the org's Sea-Haven-Industries/.github → cd-cdk.yaml. That workflow runs cdk deploy (provisions infra) then scripts/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 central oidc-deploy-roles.yaml.
  • Environments: dev (push to dev, via the org reusable workflow) and staging (push to staging, via the standalone deploy-staging.yml).
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.yaml               quality gates (lint / build / test / e2e)
  deploy.yml            caller of the org reusable cd-cdk.yaml (push to dev)
  deploy-staging.yml    standalone staging deploy (push to staging)

What the stack creates

Resource Purpose
S3 bucket seahaven-shoc-frontend-dev private origin (BLOCK_ALL, SSE, OAC-only reads)
CloudFront distribution HTTPS, gzip/br; serves the static SPA from S3 (the app calls the API directly, cross-origin)
CloudFront Function (viewer request) SPA routing: rewrites extensionless paths to /index.html (scoped to the S3 behavior, so it never touches /api)
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. Domain, cert, and API URL (already wired for dev)

Domain/cert/zone are set in cdk.json context (account 396287094661):

Context key Value
domainNames dev.seahaven.com
certificateArn …:certificate/2b78e74f-… (ACM *.seahaven.com, us-east-1)
hostedZoneId / hostedZoneName Z07671212N75U4YLPWZR8 / dev.seahaven.com

The stack creates the apex A/AAAA alias in the hosted zone (in this account, delegated from the parent seahaven.com zone). The API URL is not infra — it's VITE_API_URL in .env.production (https://api.dev.seahaven.com/api), baked into the build. Per-environment; override for staging/prod.

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.


Staging environment (same account, exact OIDC subject)

Staging lives in the same AWS account (396287094661) but deploys through its own standalone workflow, .github/workflows/deploy-staging.yml, not the org reusable cd-cdk.yaml:

  • Trust: with -c githubEnvironment=staging, the stack's deploy role (githubdeploy-shoc-frontend-new-staging) trusts ONLY the exact GitHub environment subject repo:Sea-Haven-Industries/shoc-frontend-new:environment:staging (StringEquals on both aud and sub). The workflow declares environment: staging, so only runs in that environment can assume the role. Without githubEnvironment, the dev stack keeps its branch-ref trust unchanged.
  • No secret: the role ARN is static (the role name is deterministic), so the workflow pins arn:aws:iam::396287094661:role/githubdeploy-shoc-frontend-new-staging directly — no AWS_DEPLOY_ROLE_ARN-style secret to set.
  • Gates first: the workflow runs the full npm run verify before assuming the staging role, then runs scripts/deploy-web.sh with STACK_NAME=shoc-frontend-staging, VITE_API_URL=https://api.staging.seahaven.com/api, and waits for the CloudFront invalidation to complete.
  • Application-only role: the recurring staging workflow can describe only its exact stack, publish only to its exact bucket, and invalidate only its exact distribution. It cannot assume the shared CDK bootstrap roles or modify infrastructure. Staging infrastructure changes use the Administrator command below.
  • Post-deploy checks: bucket + distribution existence, HTTPS on https://staging.seahaven.com, and the actual post-invalidation remote assets contain the staging API URL and no dev API URL. (Not browser QA.)

One-time setup (run by a human with admin AWS creds + GitHub Admin)

  1. GitHub Admin — create the staging environment (Settings → Environments → New environment → staging). Add protection rules as appropriate (e.g. required reviewers, restrict to the staging branch). If the environment does not exist, GitHub creates it unprotected on first use.

  2. AWS Admin — first deploy with admin creds (same steps 1–3 as dev; the OIDC provider and bootstrap already exist in this account):

    cd infra/cdk
    npx cdk deploy shoc-frontend-staging \
      -c envName=staging \
      -c deployBranch=staging \
      -c githubEnvironment=staging \
      -c domainNames=staging.seahaven.com \
      -c certificateArn=arn:aws:acm:us-east-1:396287094661:certificate/2b78e74f-7b65-4b82-a413-7a498b102f00 \
      -c hostedZoneId=Z02602739VQWBWCAGXP4 \
      -c hostedZoneName=staging.seahaven.com
    

    The DeployRoleArn output must match the ARN pinned in deploy-staging.yml (it will — the role name is deterministic).

  3. Backend CORS: the staging API (https://api.staging.seahaven.com) must allow the https://staging.seahaven.com origin.

  4. Push to staging — ci.yaml runs the quality gates and deploy-staging.yml deploys.

Adding prod later

Same pattern: a prod account/stack with its own contexts and, ideally, its own githubEnvironment=prod trust + workflow. Keep in mind VITE_API_URL is baked into each environment's build, and the bucket's RemovalPolicy.DESTROY + autoDeleteObjects defaults are dev/staging-friendly but should be revisited for prod.

Notes

  • Teardown: npx cdk destroy. The bucket uses RemovalPolicy.DESTROY + autoDeleteObjects (dev artifacts are reproducible) — change this for prod.
  • CI and CD both fire on push to dev and staging in parallel (staging differs only in that its CD workflow also runs npm run verify itself before deploying); a red-CI commit still deploys on dev (matches the org's push-time-CD model). Gating dev deploy on CI is a follow-up, not part of enabling CICD.
  • npm is pinned to v11.16.0; the committed package-lock.json uses lockfileVersion 3, matching the Node 24 / npm 11 CI environment.