Production-style cloud portfolio project showing how I build and operate a small serverless application with Terraform, GitHub Actions, OIDC, Astro, and AWS.
Live site: atoumbre.me
- Infrastructure as Code with reusable Terraform modules
- Serverless backend design with Lambda, API Gateway, and DynamoDB
- Edge delivery with S3, CloudFront, and Cloudflare-managed DNS
- GitHub Actions deployment using OIDC instead of long-lived AWS keys
- A static Astro frontend that consumes a live backend API at build/runtime boundaries
flowchart LR
User["Visitor Browser"] --> DNS["Cloudflare DNS"]
DNS --> CDN["AWS CloudFront"]
CDN --> S3["S3 Static Site Bucket"]
User --> API["API Gateway HTTP API"]
API --> Lambda["Python Lambda"]
Lambda --> DDB["DynamoDB Visitor Counter"]
GHA["GitHub Actions"] --> OIDC["AWS OIDC Deploy Role"]
OIDC --> TF["Terraform Apply"]
TF --> CDN
TF --> API
TF --> Lambda
TF --> DDB
cloud_resume_challenge/
├── infra-bootstrap/ # First-run local Terraform root (OIDC + GitHub repo vars)
├── frontend/ # Astro frontend
├── backend/ # Lambda handler and backend tests
├── infra/ # Main application infrastructure root
│ ├── main.tf # Root Terraform composition
│ ├── variables.tf # Root input variables
│ ├── outputs.tf # Deployment outputs
│ └── modules/
│ ├── backend_api/ # Lambda, HTTP API, and DynamoDB resources
│ └── s3_cloudfront/ # S3 origin, CloudFront distribution, ACM, DNS
├── infra-bootstrap/modules/
│ └── oidc/ # OIDC provider and GitHub Actions deploy role
└── deploy_local.py # Local deploy helper
Run the one-time bootstrap root from a local machine before relying on GitHub Actions:
export GITHUB_TOKEN="YOUR_GITHUB_TOKEN"
terraform -chdir=infra-bootstrap init
terraform -chdir=infra-bootstrap applyThat bootstrap step creates the GitHub OIDC deploy role and writes the repository variables used by the deploy workflow, including AWS_ROLE_ARN.
Pull requests are validated by .github/workflows/ci.yml. Deployments happen through .github/workflows/deploy.yml on pushes to main.
Shared validation and build logic is centralized in reusable workflows under .github/workflows/.
- Pull requests run Terraform formatting and validation, backend tests, and the Astro production build.
- A one-time local apply from
infra-bootstrap/creates the OIDC deploy role and repository variables for GitHub Actions. - GitHub Actions assumes an AWS role using OIDC only for the deploy workflow on
main. - Terraform applies infrastructure from
infra/. - The frontend builds with
PUBLIC_API_URLset from Terraform outputs. - The generated
frontend/dist/assets are synced to S3 and CloudFront is invalidated.
deploy_local.py is an helper for manual deployment. It follows the same high-level flow as CI: Terraform apply, Astro build, S3 sync, CloudFront invalidation.
Run it from the repo root:
python3 deploy_local.py- AWS CLI
- Terraform
>= 1.5 - Node.js
>= 24.0.0 - Python
>= 3.12
This repository commits both infra/terraform.tfvars and infra-bootstrap/terraform.tfvars because they contain stable, non-secret project configuration. Keep only the Cloudflare API token out of source control.
export TF_VAR_cloudflare_api_token="YOUR_CLOUDFLARE_API_TOKEN"
export GITHUB_TOKEN="YOUR_GITHUB_TOKEN"GITHUB_TOKEN is only needed when running the bootstrap root locally.
Deploys require this repository secret:
CLOUDFLARE_API_TOKEN
The bootstrap root manages the non-secret repository variables used by deploys, including AWS_ROLE_ARN and AWS_REGION.
Current automated checks in this repository are lightweight but real:
- Pull request validation for Terraform formatting and validation
- Backend unit tests with
pytestandmoto - Production frontend build with Astro
- Deployment verification through Terraform outputs and the live site
Run the main checks locally:
(cd backend && python3 -m pip install -r requirements-dev.txt && pytest test_app.py)
(cd frontend && npm ci && npm run build)
(cd infra-bootstrap && terraform init)
(cd infra && terraform fmt -check -recursive && terraform init -backend=false && terraform validate)- Use OIDC for GitHub Actions to avoid storing long-lived AWS credentials in GitHub secrets.
- Keep the frontend static and let only the visitor counter use serverless compute.
- Provision the visitor counter table inside the
backend_apiTerraform module because the Lambda, API, and DynamoDB resources are tightly coupled. - Use CloudFront in front of S3 rather than direct bucket hosting so the site can use a proper CDN, TLS, and controlled origin access.
- AWS access for CI is federated through the IAM role created from
infra-bootstrap/. - The S3 bucket is private and exposed through CloudFront origin access control.
- Cloudflare handles DNS while AWS serves the site and API.
- Terraform remains the source of truth for infrastructure state.
- Add frontend smoke tests for the counter behavior against a deployed preview or local mock API.
- Narrow the GitHub Actions deploy role permissions further as the infrastructure stabilizes.
