- Published on
Terraform IaC Complete Guide 2025: Managing AWS/GCP Infrastructure as Code
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- Introduction
- 1. Why IaC Matters
- 2. Terraform Fundamentals
- 3. State Management
- 4. Modules
- 5. Real AWS Infrastructure
- 6. Real GCP Infrastructure
- 7. Advanced Patterns
- 8. Import and Migration
- 9. Terragrunt
- 10. CI/CD Automation
- 11. Cost Management (Infracost)
- 12. Security (tfsec, Checkov)
- 13. Terraform Certification Guide
- 14. Interview Questions (15)
- 15. Quiz
- References
Introduction
The era of managing infrastructure manually is over. When cloud resources grow to dozens or hundreds, console clicking makes reproducibility, audit trails, and collaboration impossible. Terraform is an open-source IaC (Infrastructure as Code) tool by HashiCorp, supporting over 3,000 providers including AWS, GCP, and Azure.
This guide covers everything from Terraform basics to production use. HCL syntax, State management, module design, real AWS/GCP infrastructure, Terragrunt, CI/CD automation, security scanning, and cost estimation — all the knowledge needed to build production-level IaC.
1. Why IaC Matters
1.1 Manual Management vs IaC
| Aspect | Manual (Console) | IaC (Terraform) |
|---|---|---|
| Reproducibility | Impossible | 100% via code |
| Audit Trail | Limited | Git history |
| Collaboration | Impossible | PR reviews |
| Environment Clone | Hours | Minutes |
| Rollback | Manual recovery | terraform apply |
| Documentation | Separate | Code is the doc |
1.2 Terraform vs Other IaC Tools
| Tool | Language | Cloud | State | Learning Curve |
|---|---|---|---|---|
| Terraform | HCL | Multi-cloud | State file | Medium |
| CloudFormation | JSON/YAML | AWS only | Stacks | Low |
| Pulumi | Python/TS/Go | Multi-cloud | State file | High |
| AWS CDK | Python/TS/Go | AWS only | CloudFormation | High |
| OpenTofu | HCL | Multi-cloud | State file | Medium |
Why choose Terraform:
- Multi-cloud support (AWS + GCP + Azure)
- Largest community and module registry
- Declarative syntax makes intent clear
- Plan -> Apply workflow for safe changes
2. Terraform Fundamentals
2.1 HCL Basic Syntax
# Provider configuration
terraform {
required_version = ">= 1.7.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.40"
}
}
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Environment = var.environment
ManagedBy = "terraform"
Project = var.project_name
}
}
}
2.2 Variables
# variables.tf
variable "aws_region" {
description = "AWS region to deploy resources"
type = string
default = "ap-northeast-2"
}
variable "environment" {
description = "Environment name (dev, staging, prod)"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "instance_config" {
description = "EC2 instance configuration"
type = object({
instance_type = string
volume_size = number
enable_monitoring = bool
})
default = {
instance_type = "t3.medium"
volume_size = 50
enable_monitoring = true
}
}
variable "allowed_cidrs" {
description = "List of CIDR blocks allowed for ingress"
type = list(string)
default = ["10.0.0.0/8"]
}
variable "tags" {
description = "Additional tags"
type = map(string)
default = {}
}
2.3 Resources
# main.tf
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "main-vpc-${var.environment}"
}
}
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = {
Name = "public-subnet-${count.index + 1}"
Type = "public"
}
}
2.4 Data Sources
# Latest Amazon Linux 2023 AMI lookup
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
# Current AWS account info
data "aws_caller_identity" "current" {}
# Current region
data "aws_region" "current" {}
2.5 Outputs
# outputs.tf
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "Public subnet IDs"
value = aws_subnet.public[*].id
}
output "db_password" {
description = "Database password"
value = random_password.db.result
sensitive = true
}
2.6 Locals
locals {
name_prefix = "${var.project_name}-${var.environment}"
common_tags = merge(var.tags, {
Environment = var.environment
ManagedBy = "terraform"
Project = var.project_name
})
env_config = {
dev = {
instance_type = "t3.small"
min_size = 1
max_size = 2
}
staging = {
instance_type = "t3.medium"
min_size = 2
max_size = 4
}
prod = {
instance_type = "t3.large"
min_size = 3
max_size = 10
}
}
config = local.env_config[var.environment]
}
3. State Management
3.1 What is State?
Terraform State stores mapping information between actual infrastructure and code. All resource states are recorded as JSON in the terraform.tfstate file.
Without State:
- Terraform cannot know which resources it manages
- Plan tries to create all resources from scratch
- Cannot track dependencies between resources
3.2 Remote State (S3 + DynamoDB)
# backend.tf
terraform {
backend "s3" {
bucket = "mycompany-terraform-state"
key = "environments/prod/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
Bootstrap S3 bucket and DynamoDB table:
# bootstrap/main.tf
resource "aws_s3_bucket" "terraform_state" {
bucket = "mycompany-terraform-state"
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
}
}
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
3.3 State Commands
# List resources in state
terraform state list
# Show details for a specific resource
terraform state show aws_vpc.main
# Rename a resource (during refactoring)
terraform state mv aws_instance.old aws_instance.new
# Remove resource from state (keeps actual infrastructure)
terraform state rm aws_instance.legacy
# Pull remote state
terraform state pull > backup.tfstate
4. Modules
4.1 Creating Modules
modules/
vpc/
main.tf
variables.tf
outputs.tf
ec2/
main.tf
variables.tf
outputs.tf
rds/
main.tf
variables.tf
outputs.tf
# modules/vpc/main.tf
resource "aws_vpc" "this" {
cidr_block = var.cidr_block
enable_dns_support = true
enable_dns_hostnames = true
tags = merge(var.tags, {
Name = "${var.name_prefix}-vpc"
})
}
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.this.id
cidr_block = var.public_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = merge(var.tags, {
Name = "${var.name_prefix}-public-${count.index + 1}"
Type = "public"
})
}
resource "aws_subnet" "private" {
count = length(var.private_subnet_cidrs)
vpc_id = aws_vpc.this.id
cidr_block = var.private_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
tags = merge(var.tags, {
Name = "${var.name_prefix}-private-${count.index + 1}"
Type = "private"
})
}
resource "aws_internet_gateway" "this" {
vpc_id = aws_vpc.this.id
tags = merge(var.tags, {
Name = "${var.name_prefix}-igw"
})
}
resource "aws_nat_gateway" "this" {
count = var.enable_nat_gateway ? length(var.public_subnet_cidrs) : 0
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = merge(var.tags, {
Name = "${var.name_prefix}-nat-${count.index + 1}"
})
}
resource "aws_eip" "nat" {
count = var.enable_nat_gateway ? length(var.public_subnet_cidrs) : 0
domain = "vpc"
}
# modules/vpc/outputs.tf
output "vpc_id" {
value = aws_vpc.this.id
}
output "public_subnet_ids" {
value = aws_subnet.public[*].id
}
output "private_subnet_ids" {
value = aws_subnet.private[*].id
}
4.2 Using Modules
# environments/prod/main.tf
module "vpc" {
source = "../../modules/vpc"
name_prefix = "prod"
cidr_block = "10.0.0.0/16"
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]
availability_zones = ["ap-northeast-2a", "ap-northeast-2b", "ap-northeast-2c"]
enable_nat_gateway = true
tags = local.common_tags
}
4.3 Terraform Registry Modules
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.5.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["ap-northeast-2a", "ap-northeast-2b", "ap-northeast-2c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = true
single_nat_gateway = false
tags = local.common_tags
}
5. Real AWS Infrastructure
5.1 VPC + EC2 + ALB + RDS
resource "aws_launch_template" "app" {
name_prefix = "${local.name_prefix}-app-"
image_id = data.aws_ami.amazon_linux.id
instance_type = local.config.instance_type
vpc_security_group_ids = [aws_security_group.app.id]
user_data = base64encode(templatefile("userdata.sh", {
environment = var.environment
db_host = aws_db_instance.main.endpoint
}))
iam_instance_profile {
name = aws_iam_instance_profile.app.name
}
block_device_mappings {
device_name = "/dev/xvda"
ebs {
volume_size = var.instance_config.volume_size
volume_type = "gp3"
encrypted = true
}
}
}
resource "aws_autoscaling_group" "app" {
name = "${local.name_prefix}-app-asg"
desired_capacity = local.config.min_size
min_size = local.config.min_size
max_size = local.config.max_size
vpc_zone_identifier = module.vpc.private_subnet_ids
target_group_arns = [aws_lb_target_group.app.arn]
launch_template {
id = aws_launch_template.app.id
version = "$Latest"
}
health_check_type = "ELB"
health_check_grace_period = 300
}
5.2 ALB Configuration
resource "aws_lb" "app" {
name = "${local.name_prefix}-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = module.vpc.public_subnet_ids
enable_deletion_protection = var.environment == "prod"
tags = local.common_tags
}
resource "aws_lb_target_group" "app" {
name = "${local.name_prefix}-app-tg"
port = 8080
protocol = "HTTP"
vpc_id = module.vpc.vpc_id
health_check {
path = "/actuator/health"
port = "traffic-port"
healthy_threshold = 3
unhealthy_threshold = 3
timeout = 5
interval = 30
matcher = "200"
}
deregistration_delay = 30
}
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.app.arn
port = 443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
certificate_arn = aws_acm_certificate.main.arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}
5.3 RDS (PostgreSQL)
resource "aws_db_instance" "main" {
identifier = "${local.name_prefix}-db"
engine = "postgres"
engine_version = "15.5"
instance_class = "db.r6g.large"
allocated_storage = 100
max_allocated_storage = 500
storage_type = "gp3"
storage_encrypted = true
db_name = "myapp"
username = "admin"
password = random_password.db.result
multi_az = var.environment == "prod"
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.db.id]
backup_retention_period = 7
skip_final_snapshot = var.environment != "prod"
performance_insights_enabled = true
tags = local.common_tags
}
6. Real GCP Infrastructure
6.1 VPC + GKE
provider "google" {
project = var.gcp_project_id
region = var.gcp_region
}
resource "google_compute_network" "main" {
name = "${local.name_prefix}-vpc"
auto_create_subnetworks = false
}
resource "google_compute_subnetwork" "main" {
name = "${local.name_prefix}-subnet"
ip_cidr_range = "10.0.0.0/20"
region = var.gcp_region
network = google_compute_network.main.id
secondary_ip_range {
range_name = "pods"
ip_cidr_range = "10.1.0.0/16"
}
secondary_ip_range {
range_name = "services"
ip_cidr_range = "10.2.0.0/20"
}
}
resource "google_container_cluster" "primary" {
name = "${local.name_prefix}-gke"
location = var.gcp_region
remove_default_node_pool = true
initial_node_count = 1
network = google_compute_network.main.name
subnetwork = google_compute_subnetwork.main.name
ip_allocation_policy {
cluster_secondary_range_name = "pods"
services_secondary_range_name = "services"
}
workload_identity_config {
workload_pool = "${var.gcp_project_id}.svc.id.goog"
}
release_channel {
channel = "REGULAR"
}
}
resource "google_container_node_pool" "primary" {
name = "${local.name_prefix}-node-pool"
location = var.gcp_region
cluster = google_container_cluster.primary.name
node_count = 3
node_config {
machine_type = "e2-standard-4"
disk_size_gb = 100
disk_type = "pd-ssd"
}
autoscaling {
min_node_count = 3
max_node_count = 10
}
}
6.2 Cloud SQL + Cloud Run
resource "google_sql_database_instance" "main" {
name = "${local.name_prefix}-db"
database_version = "POSTGRES_15"
region = var.gcp_region
settings {
tier = "db-custom-4-16384"
ip_configuration {
ipv4_enabled = false
private_network = google_compute_network.main.id
}
backup_configuration {
enabled = true
point_in_time_recovery_enabled = true
start_time = "03:00"
}
availability_type = var.environment == "prod" ? "REGIONAL" : "ZONAL"
}
deletion_protection = var.environment == "prod"
}
resource "google_cloud_run_v2_service" "app" {
name = "${local.name_prefix}-app"
location = var.gcp_region
template {
containers {
image = "gcr.io/${var.gcp_project_id}/myapp:latest"
ports {
container_port = 8080
}
resources {
limits = {
cpu = "2"
memory = "1Gi"
}
}
env {
name = "SPRING_PROFILES_ACTIVE"
value = var.environment
}
startup_probe {
http_get {
path = "/actuator/health"
}
initial_delay_seconds = 10
period_seconds = 3
}
}
scaling {
min_instance_count = var.environment == "prod" ? 2 : 0
max_instance_count = 10
}
}
}
7. Advanced Patterns
7.1 Workspaces
# Create workspaces
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
# Switch workspace
terraform workspace select prod
locals {
env_config = {
dev = { instance_type = "t3.small", db_instance = "db.t3.medium" }
staging = { instance_type = "t3.medium", db_instance = "db.r6g.large" }
prod = { instance_type = "t3.large", db_instance = "db.r6g.xlarge" }
}
config = local.env_config[terraform.workspace]
}
7.2 Dynamic Blocks
resource "aws_security_group" "app" {
name = "${local.name_prefix}-app-sg"
description = "Application security group"
vpc_id = module.vpc.vpc_id
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = "tcp"
cidr_blocks = ingress.value.cidr_blocks
description = ingress.value.description
}
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
7.3 for_each vs count
# count -- index-based (recreation issues on deletion)
resource "aws_subnet" "example" {
count = 3
cidr_block = "10.0.${count.index}.0/24"
vpc_id = aws_vpc.main.id
}
# for_each -- key-based (safe deletion/addition)
resource "aws_subnet" "example" {
for_each = {
"public-a" = { cidr = "10.0.1.0/24", az = "ap-northeast-2a" }
"public-b" = { cidr = "10.0.2.0/24", az = "ap-northeast-2b" }
"private-a" = { cidr = "10.0.11.0/24", az = "ap-northeast-2a" }
}
cidr_block = each.value.cidr
availability_zone = each.value.az
vpc_id = aws_vpc.main.id
tags = {
Name = "${local.name_prefix}-${each.key}"
}
}
7.4 Lifecycle Rules
resource "aws_instance" "app" {
ami = data.aws_ami.amazon_linux.id
instance_type = local.config.instance_type
lifecycle {
create_before_destroy = true
prevent_destroy = false
ignore_changes = [ami]
}
}
moved {
from = aws_instance.old_name
to = aws_instance.new_name
}
8. Import and Migration
8.1 terraform import Command
terraform import aws_vpc.main vpc-0123456789abcdef0
terraform import aws_instance.web i-0123456789abcdef0
8.2 Import Block (Terraform 1.5+)
import {
to = aws_vpc.main
id = "vpc-0123456789abcdef0"
}
import {
to = aws_instance.web
id = "i-0123456789abcdef0"
}
# Auto-generate code
terraform plan -generate-config-out=generated.tf
# Review and apply
terraform apply
8.3 Migration Strategy
Step-by-step approach:
- Inventory — List current resources
- Write Code — Create HCL for each resource
- Import — Bring existing resources into State
- Verify Plan — Confirm
terraform planshows no changes - Gradual Adoption — Create new resources with Terraform first
9. Terragrunt
9.1 DRY Environment Management
# infrastructure/terragrunt.hcl (root)
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite"
}
config = {
bucket = "mycompany-terraform-state"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
generate "provider" {
path = "provider.tf"
if_exists = "overwrite"
contents = <<EOF
provider "aws" {
region = "ap-northeast-2"
}
EOF
}
# infrastructure/environments/prod/vpc/terragrunt.hcl
include "root" {
path = find_in_parent_folders()
}
terraform {
source = "../../../modules/vpc"
}
inputs = {
name_prefix = "prod"
cidr_block = "10.0.0.0/16"
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24"]
availability_zones = ["ap-northeast-2a", "ap-northeast-2b"]
enable_nat_gateway = true
}
9.2 Dependency Management
# infrastructure/environments/prod/rds/terragrunt.hcl
include "root" {
path = find_in_parent_folders()
}
terraform {
source = "../../../modules/rds"
}
dependency "vpc" {
config_path = "../vpc"
}
inputs = {
name_prefix = "prod"
vpc_id = dependency.vpc.outputs.vpc_id
subnet_ids = dependency.vpc.outputs.private_subnet_ids
}
10. CI/CD Automation
10.1 GitHub Actions + Terraform
# .github/workflows/terraform.yml
name: Terraform CI/CD
on:
pull_request:
paths:
- 'infrastructure/**'
push:
branches:
- main
paths:
- 'infrastructure/**'
permissions:
id-token: write
contents: read
pull-requests: write
jobs:
plan:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/terraform-ci
aws-region: ap-northeast-2
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.7.0
- name: Terraform Init
working-directory: infrastructure/environments/prod
run: terraform init
- name: Terraform Plan
working-directory: infrastructure/environments/prod
run: terraform plan -no-color -out=tfplan
apply:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/terraform-ci
aws-region: ap-northeast-2
- uses: hashicorp/setup-terraform@v3
- name: Terraform Init & Apply
working-directory: infrastructure/environments/prod
run: |
terraform init
terraform apply -auto-approve
11. Cost Management (Infracost)
# Run Infracost
infracost breakdown --path=infrastructure/environments/prod
# JSON output for CI
infracost breakdown \
--path=infrastructure/environments/prod \
--format=json \
--out-file=infracost.json
Tagging Strategy
locals {
required_tags = {
Environment = var.environment
Project = var.project_name
Team = var.team_name
CostCenter = var.cost_center
ManagedBy = "terraform"
}
}
12. Security (tfsec, Checkov)
12.1 tfsec Static Analysis
tfsec ./infrastructure/
12.2 Checkov
checkov -d ./infrastructure/ --framework terraform
12.3 Secrets Management (Vault)
data "vault_generic_secret" "db" {
path = "secret/data/myapp/database"
}
resource "aws_db_instance" "main" {
username = data.vault_generic_secret.db.data["username"]
password = data.vault_generic_secret.db.data["password"]
}
13. Terraform Certification Guide
HashiCorp Certified: Terraform Associate (003)
Exam overview:
- Duration: 60 minutes
- Questions: 57 (multiple choice + fill in the blank)
- Passing score: 70%
- Validity: 2 years
- Cost: approximately 70 USD
Core domains:
- IaC Concepts (15%) — IaC benefits, Terraform workflow
- Terraform Fundamentals (20%) — Providers, resources, variables, outputs
- State Management (15%) — Remote state, locking, commands
- Modules (15%) — Creating, using, registry
- Terraform CLI (15%) — init, plan, apply, destroy, import
- Workflow (10%) — Team workflows, Terraform Cloud
- Implementation and Maintenance (10%) — Debugging, upgrades
14. Interview Questions (15)
Basic (1-5)
Q1. Describe the core Terraform workflow.
Write (author code) -> Plan (preview changes) -> Apply (execute). terraform plan shows what will change without modifying infrastructure, and terraform apply actually changes it.
Q2. What is the role and importance of Terraform State?
State stores the mapping between actual infrastructure and code. Without it, Terraform cannot know which resources it manages. Use remote backends (S3+DynamoDB) for team sharing and locking.
Q3. What is the difference between a Provider and a Resource?
A Provider is a plugin handling API connections to clouds/services (AWS, GCP, etc.). A Resource is an actual infrastructure object (VPC, EC2, etc.) managed through a Provider.
Q4. Difference between terraform plan and terraform apply?
plan shows a preview of changes without modifying infrastructure. apply actually modifies infrastructure. apply internally runs plan first.
Q5. Explain variable precedence in Terraform.
Environment variables TF_VAR_xxx, terraform.tfvars, terraform.tfvars.json, auto.tfvars, -var or -var-file CLI options in increasing priority order.
Advanced (6-10)
Q6. Difference between count and for_each, and when to use each?
count is index-based; deleting a middle element recreates all subsequent resources. for_each is key-based; specific elements can be safely added/removed. Generally prefer for_each.
Q7. Why is State Locking needed?
If two people run terraform apply simultaneously, State conflicts can corrupt infrastructure. DynamoDB lock tables prevent concurrent execution.
Q8. Module benefits and design principles?
Reusability, DRY principle, and encapsulation. Design with single responsibility, externalize settings via variables, connect to other modules via outputs.
Q9. Difference between terraform import and Import Block?
terraform import imports one resource at a time via CLI without auto-generating code. Import Block (1.5+) declaratively imports multiple resources at once with -generate-config-out for auto code generation.
Q10. How to safely manage secrets in Terraform?
State files store sensitive data, so encrypt remote backends. Use sensitive = true variables, integrate HashiCorp Vault or AWS Secrets Manager, add .tfvars to .gitignore, inject via environment variables.
Practical (11-15)
Q11. What is Drift Detection and how do you handle it?
Drift is when actual infrastructure differs from Terraform code. Detect with terraform plan, resolve by applying code state or updating code. Run periodic plans to monitor drift.
Q12. Why use Terragrunt?
Compensates for Terraform's DRY principle gaps. Manages environment configs via inheritance, automates remote State setup, and handles inter-module dependencies.
Q13. Terraform CI/CD pipeline best practices?
Run plan on PRs and post results as comments. Apply on main branch merge. Use OIDC for AWS auth (no secret keys). Validate with terraform fmt + tfsec. Apply environment-specific approval processes (GitHub Environments).
Q14. When should you use Terraform alongside CloudFormation?
Use CFN when AWS-only and needing native features like StackSets or Service Catalog. Use Terraform for multi-cloud or SaaS resource management.
Q15. How to manage costs with Infracost?
Auto-estimates cost impact of infrastructure changes on PRs. Visualizes monthly cost increases/decreases so teams approve changes with cost awareness.
15. Quiz
Q1. What AWS service is needed for State Locking when using S3 as remote backend?
DynamoDB. S3 stores the State file, while DynamoDB provides the lock table to prevent concurrent execution. Configure with the dynamodb_table attribute.
Q2. Why is for_each preferred over count?
count is index-based, so removing a middle element recreates all subsequent resources. for_each is key-based, allowing safe addition and removal of specific elements.
Q3. What is the purpose of terraform plan -generate-config-out?
It auto-generates HCL code for resources declared in Import Blocks. When migrating existing infrastructure to Terraform, you can refine the auto-generated code instead of writing it from scratch.
Q4. What is the role of Terragrunt's dependency block?
It manages execution order and data passing between modules. For example, if an RDS module depends on a VPC module's outputs (subnet_ids), Terragrunt applies VPC first and passes its outputs to the RDS module.
Q5. What is the difference between tfsec and Checkov?
tfsec is a Terraform-specific security scanner that checks HCL code for security vulnerabilities. Checkov is a general-purpose policy checking tool supporting Terraform, CloudFormation, Kubernetes, Docker, and more.
References
- Terraform Official Documentation
- Terraform Registry
- Terraform AWS Provider
- Terraform GCP Provider
- Terragrunt Documentation
- Infracost Documentation
- tfsec Guide
- Checkov Documentation
- HashiCorp Learn Terraform
- Terraform Up and Running (O'Reilly)
- AWS VPC Terraform Module
- Terraform Best Practices
- Atlantis Documentation
- OpenTofu
- HashiCorp Vault Terraform Provider
- Terraform Associate Exam Guide
- AWS IAM Terraform Best Practices
- GKE Terraform Module