Professional headshot of Dan Phillips

How I built a serverless website on AWS without EC2 or a VPC

A walkthrough of the Cloud Resume Challenge AWS stack, what tripped me up, and what I'd do differently.

April 18, 2026

Multi-cloud means understanding that every provider works differently. You only really learn that by actually building on them.

Most "host a website on AWS" tutorials start with an EC2 instance. You set up a VPC, configure security groups, SSH in, install a web server. It works, but it's a lot of overhead for a static site.

There's a simpler approach. I built my resume site using the Cloud Resume Challenge stack: five managed AWS services, no servers, no VPC, and a total cost of $0.50/month for DNS. Here's how it works and what I learned building it.


How the architecture works

The architecture splits into two parts: serving the site, and handling the visitor counter.

Serving the site: S3 + CloudFront + Route 53

Static files live in S3. CloudFront sits in front of it as a CDN, handles HTTPS via ACM, caches files at edge locations globally, and keeps the S3 bucket private through Origin Access Control. Nobody hits S3 directly. Route 53 maps your custom domain to the CloudFront distribution.

The visitor counter: API Gateway + Lambda + DynamoDB

When the page loads, a JavaScript fetch call hits an API Gateway endpoint, which triggers a Lambda function, which increments a counter in DynamoDB and returns the value. Lambda runs only when called so there's no idle compute cost. DynamoDB is overkill for a single counter but it's free tier eligible and scales to whatever traffic you throw at it.

Nothing here runs in a VPC. Lambda runs in AWS's managed environment by default, which is free tier eligible and removes a whole layer of networking overhead. Most tutorials put Lambda inside a VPC. For this use case, you don't need to.


Infra as code and deployment

The stack is defined in CloudFormation with AWS SAM handling the Lambda and API Gateway pieces. Ansible playbooks automate deployment and file sync, including CloudFront cache invalidation on every upload. No manual console steps. The full repo is at danphillips-cloud/cloud-resume-challenge.


AWS vs GCP: same project, different experience

I deployed the same site on GCP using Terraform, Cloud Functions, Firestore, and Cloudflare for CDN. Same architecture, completely different experience. GCP IAM is genuinely complex with organization policies, project-level restrictions, and service-specific permissions stacked together. I was hitting permission walls constantly and had to grant Firestore access to the Cloud Function service account manually because org policy blocked Terraform from touching that binding. AWS IAM felt simple by comparison.

Having CloudFormation and Terraform side by side in the same repo was the point. Multi-cloud means understanding that every provider works differently, and you only really learn that by actually building on them.


What bit me

CORS lives in two places. API Gateway handles the OPTIONS preflight. Lambda still has to return CORS headers on every response, including errors. Misconfigure either one and the failures look unrelated to CORS.

Regional vs Edge endpoints matter in CloudFormation. Regional and Edge API Gateway endpoints use different CloudFormation attributes. Use DistributionDomainName instead of RegionalDomainName, or CertificateArn instead of RegionalCertificateArn, and your deployment breaks in non-obvious ways.

Never manually delete CloudFormation resources. I did this once. The stack drifted and subsequent deployments failed. The only fix was deleting the whole stack and redeploying. CloudFormation manages its own state -- don't touch resources outside of it.

Check .gitignore before your first commit. I committed vault files with credentials. Had to scrub them from git history. Won't happen again.


What I'd do differently

Automate CloudFront cache invalidation from the first deploy, not as an afterthought. Use OAC over OAI from day one since OAI is the legacy option. And set WWW as the primary domain upfront -- it keeps your bucket structure clean and leaves the apex free for subdomains.


Worth doing?

Yes. One project that touches storage, CDN, serverless compute, a NoSQL database, DNS, IaC, and deployment automation is genuinely useful for understanding how AWS services connect in a real architecture. For a job search it's something concrete you built and can walk through, not just another cert.


Full project on GitHub with AWS and GCP implementations, CloudFormation templates, Terraform config, and Ansible playbooks: danphillips-cloud/cloud-resume-challenge