Introduction
In real-world DevOps practices, managing multiple environments (development, staging, and production) efficiently is crucial. Terraform, an Infrastructure as Code (IaC) tool, allows us to automate cloud infrastructure provisioning. In this blog, we will build a multi-environment setup (Dev, Stage, and Prod) using Terraform modules, while also implementing best practices like state management with S3 and DynamoDB.
Project Overview
We will create three environments (Dev, Stage, and Prod) with different configurations for:
- EC2 Instances (varying counts per environment)
- S3 Buckets (varying counts per environment)
- VPC with Public & Private Subnets
- Security Groups
- RDS Database
- DynamoDB for state locking
- S3 for Terraform state storage
π Folder Structure
terraform-project/
βββ modules/
β βββ ec2/
β β βββ main.tf
β β βββ variables.tf
β β βββ outputs.tf
β βββ s3/
β β βββ main.tf
β β βββ variables.tf
β β βββ outputs.tf
β βββ vpc/
β β βββ main.tf
β β βββ variables.tf
β β βββ outputs.tf
β βββ security-groups/
β β βββ main.tf
β β βββ variables.tf
β β βββ outputs.tf
β βββ rds/
β β βββ main.tf
β β βββ variables.tf
β β βββ outputs.tf
β βββ dynamodb/
β β βββ main.tf
β β βββ variables.tf
β β βββ outputs.tf
βββ environments/
β βββ dev/
β β βββ main.tf
β β βββ backend.tf
β β βββ variables.tf
β β βββ terraform.tfvars
β βββ stage/
β β βββ main.tf
β β βββ backend.tf
β β βββ variables.tf
β β βββ terraform.tfvars
β βββ prod/
β β βββ main.tf
β β βββ backend.tf
β β βββ variables.tf
β β βββ terraform.tfvars
βββ global/
β βββ s3-backend/
β β βββ main.tf
β β βββ variables.tf
β β βββ outputs.tf
βββ provider.tf
βββ variables.tf
βββ terraform.tfvars
βββ outputs.tf
βββ README.md
1οΈβ£ Configure Terraform Backend (State Management)
File: global/s3-backend/main.tf
resource "aws_s3_bucket" "terraform_state" {
bucket = "mycompany-terraform-state"
acl = "private"
versioning {
enabled = true
}
lifecycle {
prevent_destroy = true
}
}
resource "aws_dynamodb_table" "terraform_lock" {
name = "terraform-lock"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
2οΈβ£ Backend Configuration for Each Environment
File: environments/dev/backend.tf
terraform {
backend "s3" {
bucket = "mycompany-terraform-state"
key = "dev/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-lock"
encrypt = true
}
}
3οΈβ£ VPC Module
File: modules/vpc/main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
}
resource "aws_subnet" "public" {
count = length(var.public_subnets)
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnets[count.index]
map_public_ip_on_launch = true
}
resource "aws_subnet" "private" {
count = length(var.private_subnets)
vpc_id = aws_vpc.main.id
cidr_block = var.private_subnets[count.index]
}
4οΈβ£ EC2 Module
File: modules/ec2/main.tf
resource "aws_instance" "app" {
count = var.instance_count
ami = var.ami_id
instance_type = var.instance_type
subnet_id = var.subnet_id
tags = {
Name = "EC2-${var.environment}"
}
}
5οΈβ£ RDS Module
File: modules/rds/main.tf
resource "aws_db_instance" "database" {
allocated_storage = var.db_storage
engine = "mysql"
instance_class = var.db_instance_class
db_name = var.db_name
username = var.db_username
password = var.db_password
publicly_accessible = false
skip_final_snapshot = true
}
6οΈβ£ Dev Environment Implementation
Each environment (dev, stage, prod) will have its own terraform.tfvars file inside its respective folder.
File: environments/dev/main.tf
environments/dev/terraform.tfvars
# VPC
vpc_cidr = "10.0.0.0/16"
public_subnets = ["10.0.1.0/24"]
private_subnets = ["10.0.2.0/24"]
# EC2
instance_count = 2
ami_id = "ami-12345678"
instance_type = "t2.micro"
# RDS
db_storage = 20
db_instance_class = "db.t3.micro"
db_name = "devdb"
db_username = "admin"
db_password = "password123"
environments/stage/terraform.tfvars
# VPC
vpc_cidr = "10.1.0.0/16"
public_subnets = ["10.1.1.0/24"]
private_subnets = ["10.1.2.0/24"]
# EC2
instance_count = 3
ami_id = "ami-87654321"
instance_type = "t3.medium"
# RDS
db_storage = 50
db_instance_class = "db.t3.medium"
db_name = "stagedb"
db_username = "admin"
db_password = "securepassword456"
environments/prod/terraform.tfvars
# VPC
vpc_cidr = "10.2.0.0/16"
public_subnets = ["10.2.1.0/24"]
private_subnets = ["10.2.2.0/24"]
# EC2
instance_count = 5
ami_id = "ami-11223344"
instance_type = "t3.large"
# RDS
db_storage = 100
db_instance_class = "db.t3.large"
db_name = "proddb"
db_username = "admin"
db_password = "supersecurepassword789"
Now, environments/dev/main.tf (or stage/main.tf and prod/main.tf) will only reference variables, making it more reusable.
module "vpc" {
source = "../../modules/vpc"
vpc_cidr = var.vpc_cidr
public_subnets = var.public_subnets
private_subnets = var.private_subnets
}
module "ec2" {
source = "../../modules/ec2"
instance_count = var.instance_count
ami_id = var.ami_id
instance_type = var.instance_type
subnet_id = module.vpc.public_subnets[0]
environment = "dev"
}
module "rds" {
source = "../../modules/rds"
db_storage = var.db_storage
db_instance_class = var.db_instance_class
db_name = var.db_name
db_username = var.db_username
db_password = var.db_password
}
8οΈβ£ Apply Terraform
Step 1: Initialize Backend
terraform init
Step 2: Plan
terraform plan -var-file="terraform.tfvars"
Step 3: Apply
terraform apply -var-file="terraform.tfvars" -auto-approve
Steps to Run the Terraform Project in All 3 Environments (Dev, Stage, Prod)
Here we will understand how to set up, initialize, and deploy the Terraform project for each environment: Dev, Stage, and Prod.
1οΈβ£ Prerequisites
Before running Terraform, ensure you have the following installed:
- Terraform CLI (β₯ v1.0) β Install from Terraform Download
- AWS CLI (β₯ v2.0) β Install from AWS CLI
- AWS Credentials Configured (~/.aws/credentials)
aws configure
S3 Bucket for Remote Backend (Created in global/s3-backend/)
2οΈβ£ Initialize Backend (S3 & DynamoDB)
Since we are using remote backend for Terraform state, we first create the S3 bucket and DynamoDB table.
π Navigate to global/s3-backend/ and Apply
cd global/s3-backend/
terraform init
terraform apply -auto-approve
This creates an S3 bucket for storing Terraform state and a DynamoDB table for state locking.
3οΈβ£ Deploy a Specific Environment (Dev, Stage, Prod)
Each environment (dev, stage, prod) has its own directory. You must navigate to the specific environment before running Terraform commands.
βΆοΈ Deploy Dev Environment
cd environments/dev/
terraform init
terraform plan -var-file="terraform.tfvars"
terraform apply -var-file="terraform.tfvars" -auto-approve
β This provisions the Dev environment.
βΆοΈ Deploy Stage Environment
cd environments/stage/
terraform init
terraform plan -var-file="terraform.tfvars"
terraform apply -var-file="terraform.tfvars" -auto-approve
β This provisions the Stage environment.
βΆοΈ Deploy Prod Environment
cd environments/prod/
terraform init
terraform plan -var-file="terraform.tfvars"
terraform apply -var-file="terraform.tfvars" -auto-approve
β This provisions the Prod environment.
4οΈβ£ Verify Deployments
After running Terraform, verify that resources have been created or not either through console or through aws cli commands.
5οΈβ£ Destroy Resources (When Needed)
To destroy an environment, navigate to its directory and run:
terraform destroy -var-file="terraform.tfvars" -auto-approve
Example:
cd environments/dev/
terraform destroy -var-file="terraform.tfvars" -auto-approve
π΄ β οΈ WARNING: This permanently deletes all resources in the environment!
Top comments (0)