case study · cloud engineering
how this site ships — a serverless AWS stack, defined in Terraform.
Every piece of dram-soc.org lives in one Terraform state — the static site, the CDN, the DNS, the certificate, the contact form endpoint, and the log retention policies. `git push` triggers plan + apply. Total spend: roughly $0.80/month.
the diagram
┌─────────────┐
user ───────▶│ Route 53 │ dram-soc.org A → CloudFront
└──────┬──────┘
│
▼
┌─────────────────┐ ┌────────────────┐
│ CloudFront │◀───────│ ACM (us-east-1)│
│ + rewrite fn │ │ wildcard cert │
└────┬────────┬───┘ └────────────────┘
│ │
GET * │ │ POST /contact
▼ ▼
┌───────────┐ ┌──────────────────┐
│ S3 origin │ │ HTTP API v2 │
│ (private) │ │ (API Gateway) │
└───────────┘ └────────┬─────────┘
│
▼
┌────────────────┐
│ Lambda │
│ (Node 20) │
└───┬────────┬───┘
│ │
▼ ▼
┌────────┐ ┌────────────┐
│ SES │ │ DynamoDB │
│ send │ │ audit │
└────────┘ └────────────┘
services + purpose
| Service | Purpose | Why this one |
|---|---|---|
S3 | Origin for the static Astro build. | Private bucket. Only CloudFront reads it via OAC; direct S3 URLs return 403. |
CloudFront | HTTPS-terminating CDN + edge cache. | PriceClass_100 (US/EU only) is the cheapest tier that still gives full edge coverage for the target audience. |
CloudFront Function | URL rewrite: /process → /process/index.html. | Cheaper than Lambda@Edge (~$0/mo at this traffic), sub-millisecond. |
Response Headers Policy | HSTS, X-Frame-Options DENY, Referrer-Policy, XSS-Protection, Content-Type-Options. | Managed at the edge instead of in HTML — one place to update, no static-file churn. |
Route 53 | DNS zone for dram-soc.org — apex + www A-record aliases. | Same account as the rest of the stack, aliases are free (unlike CNAME targeting CloudFront). |
ACM (us-east-1) | TLS certificate for dram-soc.org + www.dram-soc.org. | Free. Must live in us-east-1 to attach to CloudFront (single non-negotiable rule). |
API Gateway HTTP v2 | Public POST /contact endpoint. CORS enforced at gateway. | ~70% cheaper than REST API, faster, native OIDC. CORS at gateway saves a Lambda invoke per preflight. |
Lambda (Node 20) | Validates contact form, writes to DynamoDB, sends via SES. | Cold-start < 200ms for a tiny handler. Free tier covers >10× expected traffic. |
SES | Sends the contact email to my inbox. | IAM policy scopes ses:SendEmail to a single verified sender — no impersonation. |
DynamoDB | Audit log of every contact submission (PITR + encryption at rest). | Pay-per-request billing = $0 idle. Cheaper than an RDS instance for <1000 rows. |
CloudWatch Logs | Lambda logs + HTTP API access logs, 30-day retention. | Retention cap avoids infinite log growth cost. Never let default settings ship to prod. |
the cost breakdown
| Item | $/mo | Notes |
|---|---|---|
| Route 53 hosted zone | $0.50 | Fixed regardless of usage |
| CloudFront egress | ~$0.20 | < 10 GB egress at portfolio scale |
| S3 storage + requests | ~$0.05 | Site + logs, ~1 GB total |
| CloudWatch logs | ~$0.05 | 30-day retention keeps this bounded |
| Lambda / API GW / DynamoDB / SES | $0.00 | All inside free tier at this volume |
| ACM certificate | $0.00 | Free forever |
| Total | ≈ $0.80 | Domain registration ($12/yr) not included |
CI/CD flow
- Push to
mainin the repo. - GitHub Actions assumes an AWS IAM role via OIDC — zero long-lived credentials in the repo.
- Site change →
npm ci → astro build → aws s3 sync → cloudfront invalidate /*. - Infra change →
terraform fmt → init → validate → planon PR;applyon merge. - Both workflows use path filters so a doc-only change doesn't trigger a full redeploy.
deliberate choices worth calling out
- Terraform over CloudFormation. Same expressive power, better job market coverage, plan output is human-readable.
- HTTP API v2 over REST API. Contact form doesn't need request validation plugins, API keys, or usage plans — the fancy stuff REST API gives you. HTTP v2 is the right default for a serverless contact endpoint.
- Static Astro, not Next.js SSR. The site is 100% content. Every dynamic affordance (contact form) is a separate serverless endpoint. No point paying for warm Node servers on every request.
- OAC, not signed URLs / OAI. Origin Access Control is the current AWS-recommended path — supports SigV4 and all S3 features, unlike legacy OAI.
- Free-tier native, but not free-tier locked. Nothing here breaks if traffic 100×'s — CloudFront + S3 + Lambda all scale down to $0 idle and up to production-scale linearly. This exact template is what I'd use for a real product's MVP.
code
read the terraform →
The full IaC lives at
github.com/dram64/dram-soc.org
— infra/ for Terraform, site/ for the Astro source,
.github/workflows/ for the deploy pipeline.