Skip to main content

Dashboard Deployment

Dev Environment on AWS

The Amply Dashboard is deployed as a static SPA to S3 + CloudFront, with auto-deploy from GitHub Actions using OIDC authentication (no long-lived credentials).

Architecture

┌──────────────────────────────────────────────────────────────┐
│ GitHub Actions │
│ push to 'main' → Lint → Typecheck → Build → Deploy to S3 │
│ (OIDC auth to AWS, no stored credentials) │
└──────────────────────────────────────────────────────────────┘


┌──────────────────────────────────────────────────────────────┐
│ AWS (eu-central-1) │
│ │
│ ┌──────────────┐ ┌──────────────────────────────────┐ │
│ │ S3 │ │ CloudFront │ │
│ │ amply-dev- │◄────│ dashboard.dev.amply-impact.org │ │
│ │ frontend │ OAC │ SPA routing (403/404 → index) │ │
│ └──────────────┘ └──────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Route 53 │ │
│ │ A record → CF │ │
│ └─────────────────────┘ │
│ │
│ API calls from browser: │
│ dashboard.dev.amply-impact.org → api.dev.amply-impact.org │
└──────────────────────────────────────────────────────────────┘

Dev Environment

AspectDetails
URLhttps://dashboard.dev.amply-impact.org/dashboard/
Backend APIhttps://api.dev.amply-impact.org/v1
Deploy triggerPush to main branch
Auth methodGitHub OIDC → AWS IAM role
GitHub repoStayMeaty/amply-dashboard

AWS Resources

S3

ResourceDetails
Bucketamply-dev-frontend
Regioneu-central-1
Public accessBlocked (CloudFront OAC only)
Content path/dashboard/
VersioningDisabled

The bucket is private. CloudFront accesses it via Origin Access Control (OAC), not a public bucket policy.

CloudFront

ResourceDetails
Distribution IDE28IX5PKX6HT9N
Domaindlglnald08jfq.cloudfront.net
Aliasdashboard.dev.amply-impact.org
OAC IDE24GM2YWKHG9EA
Default rootdashboard/index.html
Cache policyCachingOptimized (658327ea-f89d-4fab-a63d-7e88639e58f6)
Price classPriceClass_100 (US, Canada, Europe)
HTTP versionHTTP/2 + HTTP/3
TLSTLSv1.2_2021 minimum

SPA routing: Custom error responses rewrite 403 and 404 to /dashboard/index.html with HTTP 200, enabling client-side routing.

ACM Certificate

ResourceDetails
ARNarn:aws:acm:us-east-1:640168439659:certificate/79b635dd-145d-4b1a-ada5-805c7fd6beab
Domaindashboard.dev.amply-impact.org
Regionus-east-1 (CloudFront requirement)
ValidationDNS (Route 53)

Route 53

RecordTypeTarget
dashboard.dev.amply-impact.orgA (Alias)CloudFront E28IX5PKX6HT9N

IAM

The existing amply-dev-github-actions-role is shared between backend and dashboard:

Trust policy allows both repos:

repo:StayMeaty/amply-backend:*
repo:StayMeaty/amply-dashboard:*

Permissions (inline policy amply-dev-deploy-policy):

StatementActionsResource
S3DeployFrontends3:PutObject, s3:DeleteObject, s3:ListBucket, s3:GetBucketLocationamply-dev-frontend and amply-dev-frontend/*
CloudFrontInvalidationcloudfront:CreateInvalidationDistribution E28IX5PKX6HT9N
ECRAuthecr:GetAuthorizationToken* (backend)
ECRPushECR push actionsamply-backend repo (backend)
ECSECS update/describe* (backend)
ECSPassRoleiam:PassRoleECS execution role (backend)

CI/CD Pipeline

Workflow file: .github/workflows/deploy-dev.yml

Pipeline Flow

Push to main


┌─────────────────┐
│ Lint & Typecheck│ ← ESLint + tsc --noEmit
└────────┬────────┘


┌─────────────────┐
│ Build & Deploy │ ← npm run build (VITE_API_URL baked in)
│ │ ← OIDC auth to AWS
│ │ ← Two-pass S3 sync
│ │ ← CloudFront invalidation
└─────────────────┘

Two-Pass S3 Sync Strategy

The deployment uses two separate aws s3 sync commands with different cache headers:

Pass 1 — Static assets (JS, CSS, images):

  • Excludes *.html and *.json
  • Cache: public, max-age=31536000, immutable (1 year)
  • Uses --delete to remove old assets
  • Safe because filenames contain content hashes (e.g., index-D4J36Lm_.js)

Pass 2 — HTML and JSON:

  • Only includes *.html and *.json
  • Cache: public, max-age=0, must-revalidate
  • No --delete flag (avoids race condition with pass 1)

CloudFront invalidation only targets /dashboard/index.html since all other files are content-hashed and naturally cache-bust.

Build-Time Environment Variables

VariableValueInjected By
VITE_API_URLhttps://api.dev.amply-impact.org/v1Workflow env

Vite replaces import.meta.env.VITE_API_URL at build time. The API URL is embedded in the JS bundle — it is not configurable at runtime.

CORS Configuration

The backend must accept requests from the dashboard origin. The ECS task definition includes:

CORS_ORIGINS=["https://dashboard.dev.amply-impact.org","http://localhost:3000","http://localhost:5173"]

This is set as an environment variable on the backend container in the amply-backend-dev task definition (revision 17+).

Local Development

# Clone and install
git clone https://github.com/StayMeaty/amply-dashboard.git
cd amply-dashboard
npm install

# Copy env and configure
cp .env.example .env
# Edit .env if you want to point to a different API

# Run dev server
npm run dev
# → http://localhost:5173/dashboard/

The dev server at localhost:5173 is included in the backend CORS origins.

Vite Base Path

vite.config.ts sets base: '/dashboard/', which means:

  • All built assets have paths prefixed with /dashboard/
  • The dist/ output contains index.html, assets/, etc.
  • Files are synced to s3://amply-dev-frontend/dashboard/
  • The app is accessed at /dashboard/ (not root /)

This matches the production pattern where the dashboard lives at dashboard.amply-impact.org.


Related: