A production-grade, serverless AI chatbot that acts as a personal digital twin — deployed on AWS with Infrastructure as Code, multi-environment management, and a full CI/CD pipeline.
This project is a full-stack AI Digital Twin that you can converse with, powered by Amazon Bedrock. It's built with a modern serverless architecture on AWS and managed entirely through Terraform and GitHub Actions, demonstrating professional DevOps practices including:
- Serverless backend (AWS Lambda + API Gateway)
- Static frontend hosted on CloudFront + S3
- Persistent conversation memory in S3
- AI responses via Amazon Bedrock (Nova models)
- Infrastructure as Code with Terraform workspaces
- CI/CD pipelines with GitHub Actions and OIDC authentication
GitHub Repository
↓ (Push to main / Manual trigger)
GitHub Actions (CI/CD)
↓ (OIDC authentication — no long-lived keys)
AWS Infrastructure (per environment)
├── CloudFront → S3 (Next.js Frontend)
├── API Gateway → Lambda (Python Backend)
├── Amazon Bedrock (AI — Nova Micro / Lite)
└── S3 (Conversation Memory)
State Management:
├── S3 Bucket (Terraform remote state)
└── DynamoDB Table (State locking)
| Environment | Purpose | Model |
|---|---|---|
dev |
Development & iteration | amazon.nova-micro-v1:0 |
test |
Integration testing | amazon.nova-micro-v1:0 |
prod |
Production (optional custom domain) | amazon.nova-lite-v1:0 |
digital-twin/
├── .github/
│ └── workflows/
│ ├── deploy.yml # Deploy workflow (auto on push, manual for env)
│ └── destroy.yml # Destroy workflow (manual, with confirmation)
├── backend/
│ ├── lambda_handler.py # Lambda entry point
│ └── deploy.py # Lambda packaging script (via uv)
├── frontend/
│ ├── components/
│ │ └── twin.tsx # Main chat UI component
│ ├── app/ # Next.js App Router pages
│ └── public/ # Static assets (add avatar.png here)
├── terraform/
│ ├── versions.tf # Provider configuration
│ ├── variables.tf # Input variable definitions
│ ├── main.tf # All AWS resources
│ ├── outputs.tf # Outputs (URLs, bucket names, etc.)
│ ├── backend.tf # S3 remote state configuration
│ └── terraform.tfvars # Default variable values
├── scripts/
│ ├── deploy.sh # Mac/Linux deploy script
│ ├── deploy.ps1 # Windows deploy script
│ ├── destroy.sh # Mac/Linux destroy script
│ └── destroy.ps1 # Windows destroy script
├── .env.example # Environment variable template
└── .gitignore
- Node.js 20+ and npm
- Python 3.12+
- uv (Python package manager)
- Terraform 1.0+
- AWS CLI configured (
aws configure) - AWS account with Bedrock model access enabled (Nova Micro/Lite)
-
Clone the repository
git clone https://github.com/mrithwik/digital-twin.git cd digital-twin -
Start the backend
cd backend uv run uvicorn main:app --reload --port 8000 -
Start the frontend
cd frontend npm install npm run dev
cd terraform
terraform init
terraform apply \
-target=aws_s3_bucket.terraform_state \
-target=aws_s3_bucket_versioning.terraform_state \
-target=aws_s3_bucket_server_side_encryption_configuration.terraform_state \
-target=aws_s3_bucket_public_access_block.terraform_state \
-target=aws_dynamodb_table.terraform_locksterraform apply \
-target=aws_iam_openid_connect_provider.github \
-target=aws_iam_role.github_actions \
... (see Day 5 guide for full command)
-var="github_repository=mrithwik/digital-twin"
terraform output github_actions_role_arn # Save this valueIn your GitHub repo → Settings → Secrets and variables → Actions:
| Secret | Value |
|---|---|
AWS_ROLE_ARN |
ARN from the previous step |
AWS_ACCOUNT_ID |
Your 12-digit AWS account ID |
DEFAULT_AWS_REGION |
e.g. us-east-1 |
Automatic (push to main → deploys to dev):
git push origin mainManual (choose environment):
- Go to Actions → Deploy Digital Twin
- Click Run workflow
- Select
dev,test, orprod
Mac/Linux:
./scripts/deploy.sh dev # or test, prodWindows (PowerShell):
.\scripts\deploy.ps1 -Environment devVia GitHub Actions (recommended):
- Go to Actions → Destroy Environment
- Select the environment and type its name to confirm
Locally — Mac/Linux:
./scripts/destroy.sh devLocally — Windows:
.\scripts\destroy.ps1 -Environment dev
⚠️ Destruction removes all AWS resources for that environment including Lambda, API Gateway, S3 buckets, and CloudFront. The Terraform state bucket and DynamoDB lock table are not removed automatically.
Key variables in terraform/terraform.tfvars:
| Variable | Default | Description |
|---|---|---|
project_name |
twin |
Resource name prefix |
environment |
dev |
Target environment |
bedrock_model_id |
amazon.nova-micro-v1:0 |
Bedrock model to use |
lambda_timeout |
60 |
Lambda timeout (seconds) |
api_throttle_burst_limit |
10 |
API burst limit |
api_throttle_rate_limit |
5 |
API rate limit (req/s) |
use_custom_domain |
false |
Enable custom domain |
root_domain |
"" |
Your domain (if enabled) |
For production with a custom domain, create terraform/prod.tfvars based on the example in the Day 5 setup guide.
- No long-lived AWS credentials — GitHub Actions authenticates via OIDC
- Secrets in GitHub — No credentials stored in code or
.envfiles - S3 memory bucket is private with all public access blocked
- Terraform state encrypted at rest in S3 with DynamoDB locking
- IAM roles follow least-privilege principles
Running all three environments simultaneously:
| Service | Est. Monthly Cost |
|---|---|
| Lambda (light usage) | < $1 |
| API Gateway | < $1 |
| S3 (storage) | ~$0.05 |
| CloudFront | ~$0.10 |
| Amazon Bedrock | $1–$5 (usage-dependent) |
| DynamoDB (state lock) | ~$0.00 |
| Total | ~$2–$7/month |
Tip: Destroy unused environments (
dev,test) when not in use to minimize costs. The Terraform state bucket (~$0.02/month) and IAM role (free) are safe to leave running.
Could not assume role in GitHub Actions
- Verify
AWS_ROLE_ARNsecret matches the Terraform output exactly - Confirm the OIDC trust policy references
mrithwik/digital-twin
S3 bucket already exists
- Bucket names are globally unique; change
project_nameinterraform.tfvars
Frontend not reflecting latest changes
- Trigger a CloudFront invalidation:
aws cloudfront create-invalidation --distribution-id <ID> --paths "/*"
Terraform state lock
- Check DynamoDB for a stale lock and force-unlock if needed:
terraform force-unlock <LOCK_ID>
Bedrock 403 / AccessDeniedException
- Ensure the model is enabled in the Bedrock console for your region
- Verify the Lambda IAM role has
AmazonBedrockFullAccess
- Terraform AWS Provider Docs
- Amazon Bedrock Documentation
- GitHub Actions OIDC with AWS
- Next.js Documentation
This project is licensed under the MIT License. See LICENSE for details.