The Cloud Resume Challenge: CI/CD, Python, and Surviving a Security Scare
Source: Dev.to
Introduction
Theoretical knowledge is foundational, but engineering competency is built through implementation.
I realized that understanding the theory of services like S3, Lambda, and DynamoDB wasn’t enough—I wanted to demonstrate that I could orchestrate them into a secure, automated, production‑grade application.
The Cloud Resume Challenge (created by Forrest Brazeal) was the perfect framework to bridge the gap between architectural concepts and real‑world DevOps implementation.
Here is how I built a serverless resume website, automated the deployment with GitHub Actions, and solved the engineering hurdles along the way.
The Architecture
The project requirement is simple: “Host a resume online.”
The architecture I implemented is pure Cloud Engineering:
| Layer | Technology |
|---|---|
| Frontend | HTML/CSS hosted on S3, accelerated by CloudFront (HTTPS) for global caching |
| Backend | AWS Lambda (Python) handling API requests |
| Database | DynamoDB (NoSQL) for storing the visitor count |
| Infrastructure | Fully automated CI/CD pipelines using GitHub Actions |
Level 1: The Frontend Glue (JavaScript)
The website itself is static HTML/CSS, but making it dynamic required JavaScript. I wrote a script to fetch the visitor count from my API Gateway trigger.
The biggest hurdle was CORS (Cross‑Origin Resource Sharing). My JavaScript running on aminetraibi.com was trying to talk to an AWS Lambda URL, and the browser blocked it for security reasons.
Fix: Configure the Python Lambda function to return the header Access-Control-Allow-Origin: * so the browser accepts the response.
// Fetching the view count
fetch(apiUrl)
.then(response => response.json())
.then(data => {
document.getElementById('counter').innerText = data.views;
});Level 2: The Backend Logic (Python & DynamoDB)
I needed an atomic counter that increments every time the page loads. I wrote a Python script using boto3 to talk to DynamoDB.
A specific challenge was handling DynamoDB reserved words. Since views is a reserved keyword, I used ExpressionAttributeNames to map it correctly.
# Atomic update using UpdateExpression
response = table.update_item(
Key={'id': 'page_view'},
UpdateExpression='SET #v = #v + :val',
ExpressionAttributeNames={'#v': 'views'},
ExpressionAttributeValues={':val': 1},
ReturnValues='UPDATED_NEW'
)Level 3: The Security Lesson 🚨
This was the most critical part of my learning journey.
I learned a hard lesson about secrets management. Early in the build I accidentally committed credentials to the repository.

I immediately identified the risk, revoked the keys in IAM, and migrated to GitHub Secrets.
Key takeaways
- Use a dedicated IAM user with least‑privilege permissions.
- Inject keys only at runtime via the CI/CD pipeline.
- Never store secrets in source control.
Level 4: Automated Testing & Mocking
The challenge requires Python unit tests, which turned out to be one of the hardest parts.
When I ran tests locally, boto3 tried to connect to the real DynamoDB. In the CI/CD environment I didn’t want my tests to depend on an internet connection or live database permissions.
The Solution: Mocking
I used the unittest.mock library to simulate AWS services. By mocking the DynamoDB table resource, I could test the function logic without making any actual API calls.

# Using MagicMock to fake the database response
mock_table = MagicMock()
mock_table.update_item.return_value = {'Attributes': {'views': 123}}
lambda_function.table = mock_tableThis keeps the pipeline fast, robust, and free of AWS costs during testing.
Level 5: CI/CD (The “Green Checkmark”)
The ultimate goal was to move away from the AWS Console (“ClickOps”) and adopt proper DevOps practices.
I built two separate GitHub Actions workflows:
| Workflow | Trigger | Actions |
|---|---|---|
| Frontend Pipeline | Changes in HTML/CSS | Sync to S3, invalidate CloudFront cache |
| Backend Pipeline | Changes in Python code | Run unit tests, zip code, update Lambda function |
Now a simple git push to main updates the entire application automatically.

Conclusion & Next Steps
This project forced me to touch every part of the stack: Networking (DNS/Route53), Security (IAM), Compute (Lambda), Database (DynamoDB), and Frontend logic (JavaScript).
My next milestone is to continue building on this foundation—specifically diving deeper into Infrastructure as Code with Terraform and exploring containerization with Docker/ECS.
If you are looking for a Cloud Engineer who learns by doing, breaks things, and fixes them—let’s connect!
Live Site: https://aminetraibi.com
GitHub Repository: https://github.com/AmineTra/cloud-resume-challenge

