My Cloud Resume Challenge: AWS + GCP in Practice
The Challenge
The Cloud Resume Challenge is a hands-on project designed to help cloud practitioners build a real-world portfolio piece. The idea is simple: build a personal resume website hosted in the cloud, with a visitor counter that tracks how many people have viewed your page. But the implementation is where the learning happens.
Here's what I built — across two cloud providers.
The Architecture
AWS (Primary)
The main site runs on AWS, using a fully serverless architecture:
- Amazon S3 — hosts the static website files (HTML, CSS, JavaScript)
- CloudFront — serves as the CDN, caching content at edge locations worldwide
- WAF (Web Application Firewall) — filters malicious traffic using AWS managed rule groups
- Lambda — a Python function that increments and retrieves the visitor count
- API Gateway — exposes the Lambda function as a REST endpoint
- DynamoDB — stores the visitor count in a single-item table
- SQS — a dead-letter queue for failed Lambda executions
- KMS — encryption keys for DynamoDB and Lambda runtime
The Terraform configuration defines all of this infrastructure as code. Here's a simplified view of the Lambda function:
import json
import boto3
import os
def lambda_handler(event, context):
dynamodb = boto3.resource('dynamodb')
table_name = os.environ.get('DYNAMODB_TABLE', 'VisitorCounter')
table = dynamodb.Table(table_name)
try:
response = table.update_item(
Key={'id': 'visitors-count'},
UpdateExpression='ADD #count :incr',
ExpressionAttributeNames={'#count': 'count'},
ExpressionAttributeValues={':incr': 1},
ReturnValues='UPDATED_NEW'
)
updated_count = response.get('Attributes', {}).get('count', 0)
return {
'statusCode': 200,
'headers': {
'Access-Control-Allow-Origin': '*',
},
'body': json.dumps({'count': int(updated_count)})
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps({'error': str(e)})
}
GCP (Parallel)
I also set up a parallel deployment on Google Cloud using Firebase Hosting:
- Firebase Hosting — serves static files from two hosting targets
- Cloud Storage — the underlying storage for Firebase Hosting
This gives me a multi-cloud deployment to compare approaches side by side.
What Worked Well
Terraform made the AWS infrastructure manageable. Despite the number of resources (S3 buckets, CloudFront distributions, IAM roles, WAF rules), having everything as code meant I could review, version, and reproduce the entire setup.
The serverless approach for the visitor counter is elegant. No servers to manage, no scaling concerns, and the pay-per-use model means it costs pennies per month for a personal project.
Firebase Hosting was straightforward for the GCP side. Just point it at a directory of static files and deploy. No server configuration, no CDN setup — it just works.
What Was Friction
AWS WAF added complexity without much value for a personal site. The managed rule groups are powerful, but configuring them for a simple static website felt like overkill.
Lambda cold starts — while not an issue for a visitor counter with low traffic, the first invocation after a period of inactivity adds ~200ms of latency. Not noticeable for a personal site, but worth keeping in mind.
Multi-cloud consistency — keeping the AWS and GCP deployments in sync required careful coordination. Different tools, different deployment pipelines, different mental models.
The Numbers
| Component | Monthly Cost (Est.) |
|---|---|
| S3 (storage + requests) | ~$0.02 |
| CloudFront (data transfer) | ~$0.05 |
| Lambda (1M requests) | $0.00 |
| API Gateway (1M requests) | ~$1.50 |
| DynamoDB (on-demand) | ~$0.01 |
| Total | ~$1.58 |
The Firebase side is free within the Spark plan limits.
What's Next
I'm planning to add a blog to this site (you're reading it now), and I'm also experimenting with running AI models locally on my Apple hardware. More on that in the next post.