A production-grade Infrastructure as Code (IaC) implementation using Terraform and Terragrunt to deploy a serverless product catalog system on AWS. This project demonstrates multi-environment deployments with support for both AWS production and LocalStack for local development.
This system provides a REST API for managing a product catalog with event-driven logging:
- API Gateway receives POST requests to create products
- Lambda validates and stores products in DynamoDB
- EventBridge routes product creation events asynchronously
- Event Logger Lambda persists events to S3 for audit trails
| Category | Tools |
|---|---|
| IaC | Terraform, Terragrunt |
| Cloud | AWS (Lambda, API Gateway, DynamoDB, EventBridge, S3, IAM, CloudWatch) |
| Local Dev | LocalStack, Docker |
| CI/CD | GitHub Actions with OIDC authentication |
| Runtime | Python 3.12 |
.
├── .github/workflows/ # CI/CD pipelines
│ ├── deploy-aws.yaml # Production deployment
│ ├── deploy-localstack.yaml # LocalStack deployment (PR validation)
│ └── destroy-aws.yaml # Infrastructure teardown
│
├── backend/ # Terraform state backend (S3 + DynamoDB)
│
├── catalog/
│ ├── modules/ # Reusable Terraform modules
│ │ ├── api-gateway/ # REST API configuration
│ │ ├── dynamodb/ # NoSQL table
│ │ ├── eventbridge/ # Event bus and rules
│ │ ├── iam/ # Roles and policies
│ │ ├── lambda/ # Serverless functions
│ │ └── s3/ # Object storage
│ │
│ └── units/ # Terragrunt composition units
│ ├── api-gateway/
│ ├── dynamodb/
│ ├── eventbridge/
│ ├── iam-api-gateway/
│ ├── iam-eventbridge/
│ ├── lambda-api-gateway/
│ ├── lambda-eventbridge/
│ └── s3/
│
├── dist/ # Lambda deployment packages
│ ├── create-product.py # Product creation handler
│ └── logger.py # Event logging handler
│
├── environment/
│ ├── root.hcl # Global Terragrunt config
│ ├── prod/ # AWS production environment
│ └── dev/ # LocalStack development environment
│
└── images/ # Documentation assets
- Terraform (latest)
- Terragrunt (v0.96.1+)
- Docker (for LocalStack)
- AWS CLI (optional, for manual testing)
docker run -d \
--name localstack \
-p 4566:4566 \
-e SERVICES=s3,dynamodb,lambda,apigateway,events,iam,sts,cloudwatch \
-e DEBUG=1 \
-e LAMBDA_EXECUTOR=local \
localstack/localstackcd backend
terraform init
terraform apply -var="use_localstack=true" -var="localstack_endpoint=http://localhost:4566" -auto-approvecd environment/dev
terragrunt run --all -- init -reconfigure \
-backend-config="endpoint=http://localhost:4566" \
-backend-config="access_key=test" \
-backend-config="secret_key=test"
terragrunt run --all apply \
-var="use_localstack=true" \
-var="localstack_endpoint=http://localhost:4566"curl -X POST http://localhost:4566/restapis/<api-id>/dev/_user_request_/products \
-H "Content-Type: application/json" \
-d '{
"Name": "Test Product",
"Category": "Electronics",
"Price": 99.99,
"Stock": 100
}'Production deployments are handled automatically via GitHub Actions when pushing to main. The workflow:
- Authenticates to AWS using OIDC (no hardcoded credentials)
- Creates/updates the Terraform state backend
- Deploys all infrastructure using Terragrunt
- Validates the deployment with an API test
cd environment/prod
terragrunt run --all -- init
terragrunt run --all -- apply| Workflow | Trigger | Purpose |
|---|---|---|
deploy-aws.yaml |
Push to main, Manual |
Deploy to AWS production |
deploy-localstack.yaml |
Pull Request, Manual | Validate changes with LocalStack |
destroy-aws.yaml |
Manual only | Tear down AWS infrastructure |
| Secret | Description |
|---|---|
| AWS Account ID | (configured in workflow) |
| OIDC Role ARN | arn:aws:iam::<account-id>:role/github-actions |
| Environment | Target | Region | Endpoint |
|---|---|---|---|
prod |
AWS Cloud | eu-west-2 |
AWS Default |
dev |
LocalStack | eu-west-2 |
http://localhost:4566 |
cd environment/dev
terragrunt run --all -- destroy
docker stop localstack && docker rm localstackUse the GitHub Actions workflow destroy-aws.yaml or:
cd environment/prod
terragrunt run --all -- destroy This project is provided as-is for demonstration purposes.
