A production-ready Terraform/OpenTofu starter kit for AWS infrastructure with built-in best practices, testing, and modular design.
-
Install OpenTofu (or Terraform):
# macOS brew install opentofu # Linux/Windows - see https://opentofu.org/docs/intro/install/
-
Install Docker (required for container deployments):
# macOS brew install docker # Linux/Windows - see https://docs.docker.com/get-docker/
-
Install AWS CLI and configure AWS credentials:
Option A: AWS CLI Configuration (Quick Start)
aws configure # Enter your AWS Access Key ID, Secret Key, and region (e.g., us-east-1)
To create access keys:
- Sign in to AWS Console β IAM β Users β Your username
- Security credentials tab β Access keys β Create access key
- Select "Command Line Interface (CLI)"
- Download the credentials (you won't see the secret key again!)
β οΈ Security Note: Access keys are convenient but not the most secure option. For production use, consider:- IAM roles (for EC2/Lambda)
- AWS SSO/Identity Center
- Temporary credentials with
aws sts assume-role
- AWS Vault for local credential management
This creates the S3 bucket and DynamoDB table for storing Terraform state:
cd deploy/aws/bootstrap/terraform
tofu init
tofu apply
# Type 'yes' when prompted
This creates a backend-config.hcl
file with your S3 bucket details.
If it doesn't get created, just touch one in the root of your directory that looks like this (you get the values when apply is run in bootstrap):
bucket = "terraform-state-myAccountId-randomShaHash"
key = "CHANGE_ME/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-state-locks"
encrypt = true
Now you can deploy any service. Let's start with the Lambda example:
# Navigate to the Lambda deployment
cd deploy/preview/lambda
# Initialize Terraform with the backend config
# The ../../../backend-config.hcl path always points to your root directory
tofu init -backend-config=../../../backend-config.hcl
# Review what will be created
tofu plan
# Create the infrastructure (type 'yes' when prompted)
tofu apply
π‘ First time using Terraform?
init
downloads providers and configures state storageplan
shows what will be created without making changesapply
actually creates the AWS resources
π Path structure: All deployments follow deploy/{environment}/{service}/
, so the backend config is always 4 levels up (../../../../
)
Best for:
- Event-driven workloads (API Gateway, S3 events, SQS)
- Infrequent or unpredictable traffic
- Simple microservices under 15 minutes execution time
- Rapid scaling from 0 to thousands of concurrent executions
Example use cases:
- REST APIs with sporadic traffic
- Image/file processing triggered by S3 uploads
- Scheduled jobs (cron-like tasks)
- Webhooks and event handlers
Limitations:
- 15-minute maximum execution time
- 10GB memory limit
- 512MB temporary storage
- Cold starts can impact latency
Best for:
- Long-running services
- Predictable workloads
- When you need full control over the runtime
- Applications requiring more than Lambda's limits
Example use cases:
- Web applications with consistent traffic
- Background job processors
- Microservices that need persistent connections
- Applications requiring specific runtime configurations
Benefits:
- No server management
- Pay per task
- Scales automatically
- Supports any containerized workload
Best for:
- Cost optimization at scale
- GPU or specialized compute requirements
- When you need maximum control over the infrastructure
- High-memory or high-CPU applications
Example use cases:
- Large-scale web applications
- Machine learning inference
- High-performance databases
- Applications requiring local NVMe storage
Trade-offs:
- Must manage EC2 instances
- More complex scaling
- Better cost efficiency at scale
- Full control over instance types
cd deploy/aws/preview/lambda
tofu init -backend-config=../../../../backend-config.hcl
tofu plan
tofu apply
That's it! Everything is created in one go:
- ECR repository
- Docker image built and pushed
- Lambda function with the container
- API endpoint with authentication
Test it:
curl -H "X-API-Key: $(tofu output -raw api_key)" $(tofu output -raw lambda_function_url)
View logs:
aws logs tail $(tofu output -raw log_group_name) --follow --region us-east-1
cd deploy/aws/preview/ecs-fargate
tofu init -backend-config=../../../../backend-config.hcl
tofu plan
tofu apply
Creates everything automatically:
- VPC with subnets
- ECR repository
- Docker image built and pushed
- ECS cluster and service
- Application Load Balancer with public access
- Security groups
Test the API:
# Health check (no auth required)
curl $(tofu output -raw api_url)/health
# API with authentication
curl -H "X-API-Key: $(tofu output -raw api_key)" $(tofu output -raw api_url)
View logs:
aws logs tail $(tofu output -raw log_group_name) --follow --region us-east-1
cd deploy/aws/preview/ecs-ec2
tofu init -backend-config=../../../../backend-config.hcl
tofu plan
tofu apply
Creates everything automatically:
- VPC with subnets
- ECR repository
- Docker image built and pushed
- ECS cluster with Auto Scaling Group
- EC2 instances with ECS agent
- Application Load Balancer with public access
- Security groups
Test the API:
# Health check (no auth required)
curl $(tofu output -raw api_url)/health
# API with authentication
curl -H "X-API-Key: $(tofu output -raw api_key)" $(tofu output -raw api_url)
View logs:
aws logs tail $(tofu output -raw log_group_name) --follow --region us-east-1
All services send logs to CloudWatch automatically:
# Lambda logs
aws logs tail /aws/lambda/{function-name} --follow --region us-east-1
# ECS logs (Fargate or EC2)
aws logs tail /ecs/{cluster-name}/{service-name} --follow --region us-east-1
# Search for errors in the last hour
aws logs filter-log-events \
--log-group-name /aws/lambda/{function-name} \
--filter-pattern "ERROR" \
--start-time $(date -u -d '1 hour ago' +%s)000 \
--region us-east-1
Since all deployments build and push images automatically:
# Make changes to your code in docker/rest-server/
# Then just run:
tofu apply
# Terraform will rebuild, push, and update the service
For ECS services with execute command enabled:
# List running tasks
aws ecs list-tasks --cluster {cluster-name} --region us-east-1
# Connect to a container
aws ecs execute-command \
--cluster {cluster-name} \
--task {task-arn} \
--container rest-api \
--interactive \
--command "/bin/sh" \
--region us-east-1
# Without API key returns 401
curl $FUNCTION_URL
π‘ Tip: Save your API key somewhere safe - you can always retrieve it later with tofu output -raw api_key
To remove all resources:
cd deploy/preview/lambda
tofu destroy
- ποΈ Pure Terraform - No shell scripts, fully declarative
- π Remote State - S3 backend with DynamoDB locking
- π Automatic Backend Config - No manual key management needed
- π¦ Modular Design - Reusable modules in
lib/terraform/
- π§ͺ Testing Built-in - Terratest framework included
- π― Type Safety - Validation through Terraform patterns
- π Organized Structure - Environment/service based deployments
βββ bootstrap/ # Backend infrastructure setup
βββ lib/terraform/ # Reusable Terraform modules
βββ deploy/ # Environment deployments
β βββ preview/
β βββ staging/
β βββ production/
βββ tests/ # Module tests
# Working with deployments - navigate to the deployment directory first
cd deploy/preview/lambda
# Initialize a deployment
tofu init -backend-config=../../../backend-config.hcl
# Preview changes
tofu plan
# Apply changes
tofu apply
# Destroy infrastructure
tofu destroy
# Development helpers
tofu fmt -recursive # Format all Terraform files
tofu validate # Validate configuration
go test ./tests/... # Run module tests (requires Go)
-
Create your deployment directory (always use the pattern
deploy/{environment}/{service}
):mkdir -p deploy/preview/my-service
-
Copy an existing deployment as a template:
cp -r deploy/preview/lambda/* deploy/preview/my-service/
-
Update the backend key in
main.tf
to match your path:backend "s3" { key = "preview/my-service/terraform.tfstate" # Matches your directory path }
-
Initialize and deploy:
cd deploy/preview/my-service tofu init -backend-config=../../../backend-config.hcl tofu apply
π― Key point: The backend config path is always ../../../backend-config.hcl
because all deployments are exactly 3 directories deep
- OpenTofu or Terraform >= 1.5.0 (Install OpenTofu)
- AWS CLI configured with credentials (Install AWS CLI)
- Docker for building and pushing container images (Install Docker)
- Go (for testing - optional) (Install Go)
- TFLint (optional) (Install TFLint)
- terraform-docs (optional) (Install terraform-docs)
The project includes automated tests using Terratest (Go-based testing framework for infrastructure).
cd tests
go mod download
go test -v -timeout 30m
Note: Tests create real AWS resources and incur costs. Run them in a test account.
tests/
- Test files using Terratesttest-fixtures/
- Minimal Terraform configurations for testing modules
Example test output:
=== RUN TestECRRepository
=== RUN TestLambdaContainer
--- PASS: TestECRRepository (45.2s)
--- PASS: TestLambdaContainer (52.1s)
PASS
- Create modules in
lib/terraform/
- Write tests in
tests/
- Follow the established patterns
- Run
tofu validate
andtofu fmt -recursive
before committing
MIT