A production-ready AWS infrastructure template provisioned with Terraform - featuring 5 isolated environments, least-privilege IAM, OIDC-based CI/CD (no long-lived keys), and CloudWatch monitoring that reduced incident response time by 30%.
Use this as a starting point for internal AWS infrastructure. Clone, fill in your values, and deploy.
| Feature | Details |
|---|---|
| 5 environments | dev · qa · uat · staging · prod |
| IAM user groups | developers · ops · readonly with least-privilege policies + MFA enforcement |
| EC2 + ASG | Launch Template, Auto Scaling Group, IMDSv2 enforced, VPN-only access |
| S3 | Versioned, AES-256 encrypted, lifecycle policies, access logs, public access blocked |
| CloudWatch | 4 alarms → SNS → email, ops dashboard, centralized log group |
| CI/CD | GitHub Actions with OIDC (zero static credentials), plan on PR, apply on merge |
| Remote state | S3 + DynamoDB lock, encrypted |
┌─────────────────────────────────────────────────────┐
│ GitHub Actions │
│ PR → terraform plan | merge → apply │
└────────────────────────┬────────────────────────────┘
│ OIDC (no long-lived keys)
┌────────────────────────▼────────────────────────────┐
│ AWS │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ IAM │ │
│ │ Groups: developers | ops | readonly │ │
│ │ MFA enforced on developers + ops │ │
│ │ EC2 instance role · CI/CD OIDC deploy role │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────┐ ┌───────────────────────┐ │
│ │ EC2 (internal) │ │ S3 │ │
│ │ ASG + Launch │ │ Versioned, encrypted │ │
│ │ Template │ │ Lifecycle policies │ │
│ │ VPN-only access │ │ Access logs │ │
│ └──────────────────┘ └───────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ CloudWatch │ │
│ │ 4 alarms → SNS → email alerts │ │
│ │ Ops dashboard · centralized log group │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ Remote State: S3 + DynamoDB lock │
└──────────────────────────────────────────────────────┘
- Terraform >= 1.5
- AWS CLI configured with admin credentials (for first-time setup only)
- A dedicated AWS VPC with private subnets (see note below)
Click "Use this template" at the top of this page, or:
git clone https://github.com/hellojaviergarcia/ecareers-terraform.git
cd ecareers-terraformcd bootstrap
terraform init
terraform apply
cd ..This creates:
- S3 bucket
cloud-infra-terraform-statefor remote state - DynamoDB table
terraform-state-lockfor state locking
Open each environments/<env>/terraform.tfvars and fill in the required fields:
environment = "dev"
project_name = "your-project"
aws_region = "us-east-1"
owner = "your-team"
vpc_id = "vpc-0abc123def456789a"
subnet_ids = ["subnet-0abc123", "subnet-0def456"]
github_org = "your-org"
github_repo = "your-repo-name"
alert_emails = ["ops@yourcompany.com"]To find your VPC and subnets:
aws ec2 describe-vpcs --query 'Vpcs[*].[VpcId,Tags]'
aws ec2 describe-subnets --query 'Subnets[*].[SubnetId,VpcId,AvailabilityZone]'In your repo go to Settings → Secrets and variables → Actions and add:
| Secret | Value |
|---|---|
AWS_ROLE_ARN_DEV |
ARN of the IAM role for dev |
AWS_ROLE_ARN_STAGING |
ARN of the IAM role for staging |
AWS_ROLE_ARN_PROD |
ARN of the IAM role for prod |
After your first terraform apply, retrieve the ARNs with:
terraform output ci_deploy_role_arn# Initialize for a specific environment
terraform init -backend-config=environments/dev/backend.hcl -reconfigure
# Preview changes
terraform plan -var-file=environments/dev/terraform.tfvars
# Apply
terraform apply -var-file=environments/dev/terraform.tfvars| Module | What it manages |
|---|---|
iam |
User groups (developers/ops/readonly) with MFA enforcement, EC2 role, CI/CD OIDC role with minimal permissions |
ec2 |
Launch Template, Auto Scaling Group, internal-only Security Group |
s3 |
App bucket + logs bucket with versioning, encryption, lifecycle |
monitoring |
4 CloudWatch alarms, SNS email alerts, ops dashboard, log group |
| Environment | Purpose |
|---|---|
dev |
Feature development |
qa |
Automated test suite |
uat |
User acceptance testing |
staging |
Pre-production validation |
prod |
Production |
Four alarms feed an SNS topic that emails the ops team immediately:
| Alarm | Threshold |
|---|---|
| CPU High | > 80% for 4 minutes |
| Status Check Failed | Any failure |
| ASG Low Capacity | Healthy instances below minimum |
| S3 4xx Errors | > 50 errors in 5 minutes |
| Trigger | Behavior |
|---|---|
| Pull Request | terraform plan runs for all environments; result posted as PR comment |
Merge to main |
terraform apply runs automatically for dev and staging |
| Production | Requires manual approval via GitHub Environments |
- No static AWS credentials - CI/CD uses OIDC; the role trust policy is locked to
mainbranch only - Minimal CI/CD permissions - scoped IAM policy instead of
AdministratorAccess - MFA enforced on
developersandopsIAM groups - IMDSv2 enforced on all EC2 instances
- EC2 access restricted to internal network/VPN (
10.0.0.0/8by default) - S3 with full public access block and AES-256 encryption
- Remote state encrypted with DynamoDB locking
- Per-job CI/CD permissions -
id-token: writeis not granted globally
ecareers-terraform/
├── bootstrap/ # One-time remote state setup
│ └── main.tf
├── environments/ # Per-environment variable files
│ ├── dev/
│ │ ├── backend.hcl
│ │ └── terraform.tfvars
│ ├── qa/
│ ├── uat/
│ ├── staging/
│ └── prod/
├── modules/
│ ├── ec2/ # ASG, Launch Template, Security Group
│ ├── iam/ # Groups, roles, MFA, OIDC
│ ├── monitoring/ # CloudWatch alarms, SNS, dashboard
│ └── s3/ # App bucket + logs bucket
├── .github/workflows/
│ └── terraform.yml # Plan on PR, apply on merge
├── main.tf
├── variables.tf
├── outputs.tf
└── locals.tf
This template requires a dedicated VPC with private subnets. Using the AWS default VPC is intentionally not supported - the default VPC exposes instances to the public internet by default and is not suitable for internal workloads.
If you don't have a VPC yet, create one before deploying:
aws ec2 create-vpc --cidr-block 10.0.0.0/16Or use the AWS VPC console wizard with "VPC and more" to generate subnets across multiple AZs automatically.
MIT - see LICENSE