We built and deployed a secure static resume website on AWS with Terraform, Amazon S3, and Amazon CloudFront. GitHub Actions tests and deploys the infrastructure through OIDC. CloudFront logs can be queried in Amazon Athena, and scheduled checks detect Terraform drift.
- Frontend: HTML, CSS, and JavaScript
- Infrastructure as code: Terraform
- AWS: Amazon S3, Amazon CloudFront, Amazon CloudWatch, AWS Glue Data Catalog, Amazon Athena, and AWS IAM
- CI/CD: GitHub Actions with OpenID Connect (OIDC) authentication to AWS
- Code quality and security: TFLint and Trivy
- Tools: Go and AWS SDK for Go
- Notifications: Telegram Bot API
Install these tools before you start:
- Git
- Terraform
1.16.1 - AWS CLI v2
- TFLint
0.64.0 - Trivy
0.70.0 uvand Python3.13or later- Graphviz for architecture diagrams
- GitHub CLI, if you want to manage repository configuration from a terminal
Use an AWS account and a local AWS role that can create S3 and IAM resources. Do not use the AWS root user. The bootstrap role also needs permission to create the project resources.
git clone https://github.com/DanielHemmati/CVAsCode.git
cd CVAsCode
uv syncGenerate the architecture header after you change diagram.py:
uv run python diagram.pyConfigure a named AWS CLI profile with credentials from your AWS account:
aws configure --profile cvascode
export AWS_PROFILE=cvascode
aws sts get-caller-identityThe last command must return the AWS account that will contain the project. Do not store access keys or AWS credentials in this repository.
This repository contains values for the original AWS account and GitHub repository. Replace them before you create resources:
- Replace the state bucket name in every
terraform/live/**/backend.tffile. - Update the GitHub OIDC subject and role name in
terraform/live/global/github-oidc/main.tf. - Update the allowed principals in
terraform/live/global/plan-artifacts/main.tf. - Replace the AWS role ARN and plan bucket in
.github/workflows/. - Keep the GitHub environment name as
production, or update it in Terraform and every workflow.
The current GitHub Actions role uses AdministratorAccess. Replace it with a least-privilege policy before you use this project for production workloads.
Terraform uses separate S3 state files for each live stack. Create the state bucket first with local state:
terraform -chdir=terraform/live/global/s3-backend init -backend=false
terraform -chdir=terraform/live/global/s3-backend plan -out=terraform.tfplan
terraform -chdir=terraform/live/global/s3-backend show terraform.tfplan
terraform -chdir=terraform/live/global/s3-backend apply terraform.tfplan
terraform -chdir=terraform/live/global/s3-backend init -migrate-stateRead the saved plan before you apply it. Accept the state migration only after the new bucket name is correct.
Next, bootstrap these stacks in order:
terraform/live/global/github-oidcterraform/live/global/plan-artifactsterraform/live/prod/static-site
Run this process for each stack. Replace <stack> with one path from the list:
terraform -chdir=<stack> init -input=false -lockfile=readonly
terraform -chdir=<stack> plan -input=false -lock-timeout=5m -out=terraform.tfplan
terraform -chdir=<stack> show terraform.tfplan
terraform -chdir=<stack> apply -input=false -lock-timeout=5m terraform.tfplanThe static-site stack must exist before the CloudFront logging stack can read its remote-state outputs. After this bootstrap, use GitHub Actions for changes to production infrastructure.
Create a GitHub environment named production. Protect main and require pull-request reviews because a merge to main can apply infrastructure changes.
The workflows use OIDC to request short-lived AWS credentials. Do not add AWS access keys as GitHub secrets.
Add these repository secrets if you want Telegram notifications:
gh auth login
gh secret set TELEGRAM_BOT_TOKEN
gh secret set TELEGRAM_CHAT_IDThe workflows have these responsibilities:
tf-module.yamlformats, validates, lints, and scans reusable Terraform modules.tf-live.yamlchecks live stacks on pull requests. Onmain, it creates saved plans, stores them in private S3, and applies the exact plans.deploy-resume.yamluploadsresume-page/to S3 and invalidates the CloudFront cache. Run it manually frommain.drift-detection.yamlcompares AWS resources with Terraform state every 12 hours.telegram-notifications.yamlreports workflow results to Telegram.
Create reusable infrastructure in terraform/modules/. Add environment values in a focused root under terraform/live/. Update one phase from the current document in docs/, then run local checks:
terraform fmt -check -recursive -diff terraform
tflint --config=.tflint.hcl --init
trivy config terraformOpen a pull request and wait for both Terraform workflows to pass. Review the change before you merge it into main.
The agent first creates an implementation plan in the docs/ directory and divides the work into phases. It completes one phase at a time, then waits for my review and approval before it starts the next phase.
The project uses these agent skills:
terraform-skill: Guides Terraform development, testing, and troubleshooting.simple-english: Keeps documentation clear and concise.grill-me: Refines a plan through focused questions.grill-with-docs: Refines a plan and creates supporting documents.to-spec: Converts an agreed plan into a specification.to-tickets: Divides a specification into implementation tasks.implement: Implements work from a specification or task.code-review: Reviews changes against project standards and requirements.retro: Reviews a completed session and identifies workflow improvements.
- Use aws bedrock (nova micro model) to summarize the resume using single prompt
- Use DynamoDB for visitor counts (not page on page load, base on unique IP at first then make it more advance)
- SBOM & Supply-Chain Security — Syft, Grype, and artifact integrity
- Infracost - Terraform cost estimation (not useful for this project b/c we are using serverless services and they are usage base mostly)
The foundation of this project is ready you just have to continue the loops of:
- Use grill-me so you know what you exactly want
- Generate a and put it under
docs/folder - Turn the plan into small phases so you can review (I much rather review & i don't have enough token for agent review)
- See agent first create module and live terraform
- Then push and
tf-module.yaml,tf-live.yamlwill do the job
Currently I am working on creating k8s platform which start simple (just software workload, dev/staging/prod env) and then expand to have have inference and AI workload as well, which is far more interesting to me than this project.
And I want to finish iximuiz lab and all of the 100 days challenge from kodekloud.
