Terraform & Infrastructure as Code¶
Why IaC? [B]¶
Infrastructure as Code = managing infrastructure through version-controlled configuration files instead of manual clicks or scripts.
Benefits: - Reproducible environments (no snowflake servers) - Code review for infrastructure changes - Audit trail via git history - Disaster recovery — recreate infra from code
Terraform vs alternatives:
| Tool | Approach | Best for |
|---|---|---|
| Terraform | Declarative, multi-cloud | General-purpose IaC |
| Pulumi | Imperative (real code) | Devs who prefer programming languages |
| AWS CDK | Declarative (code) | AWS-only, TypeScript/Python |
| Ansible | Imperative, agentless | Config management, not infra provisioning |
| CloudFormation | Declarative YAML/JSON | AWS-only, no extra tooling |
Terraform Core Concepts [B]¶
Providers¶
Plugins that let Terraform talk to APIs (AWS, GCP, Azure, Kubernetes, etc.)
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
Resources¶
The things you create:
Data Sources¶
Read existing infrastructure (not managed by this Terraform):
Variables & Outputs¶
variable "environment" {
type = string
default = "staging"
}
output "bucket_arn" {
value = aws_s3_bucket.logs.arn
}
State¶
Terraform tracks what it created in a state file (terraform.tfstate).
Never commit state to git. Use remote state backends:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/app/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-lock" # state locking
encrypt = true
}
}
Core Workflow [B]¶
terraform init # Download providers, initialize backend
terraform plan # Show what will change (always review!)
terraform apply # Apply changes
terraform destroy # Tear down infrastructure
terraform fmt # Format code
terraform validate # Validate syntax
Always review plan output before apply.
Module Structure [I]¶
Modules = reusable, composable infrastructure components.
Recommended Layout¶
infrastructure/
├── environments/
│ ├── prod/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ └── staging/
│ └── ...
└── modules/
├── networking/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── rds/
│ └── ...
└── eks/
└── ...
Using a Module¶
module "vpc" {
source = "../../modules/networking"
environment = var.environment
cidr_block = "10.0.0.0/16"
az_count = 3
}
Public Modules¶
Terraform Registry has battle-tested modules:
State Management [I]¶
State Locking¶
Prevents concurrent applies from corrupting state. Use DynamoDB for S3 backend.
Workspaces¶
Separate state per environment:
Alternative: Use separate state files per environment (more explicit, easier to manage at scale).
State Commands¶
terraform state list # List resources in state
terraform state show aws_instance.web # Inspect a resource
terraform state mv old_name new_name # Rename resource in state
terraform state rm aws_instance.old # Remove from state (not destroy)
terraform import aws_s3_bucket.logs my-bucket # Import existing resource
Terraform Best Practices [I]¶
Code Quality¶
- Pin provider versions (
~> 5.0, notlatest) - Use
terraform fmtandterraform validatein CI - Lock file (
terraform.lock.hcl) must be committed to git - Use
tflint,checkov, orterrascanfor static analysis
Safety¶
- Always run
planbeforeapply - Use
-targetsparingly (creates state drift) - Never edit state manually
- Protect prod with PR reviews + CI plan output
Organization¶
- Small, focused modules (single responsibility)
- Document variables with
descriptionfields - Tag all resources consistently
locals {
common_tags = {
Environment = var.environment
Team = "platform"
ManagedBy = "terraform"
}
}
Advanced Patterns [A]¶
Dynamic Blocks¶
resource "aws_security_group" "web" {
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = "tcp"
cidr_blocks = ingress.value.cidrs
}
}
}
For Expressions¶
locals {
instance_ids = [for i in aws_instance.web : i.id]
sg_map = { for sg in var.security_groups : sg.name => sg.id }
}
Conditional Resources¶
Terragrunt¶
DRY wrapper around Terraform for managing multiple environments/accounts:
- Eliminates backend config repetition
- Manages dependency ordering between modules
- run-all plan across multiple modules
CI/CD Integration [I]¶
# GitHub Actions example
- name: Terraform Plan
run: |
terraform init
terraform plan -out=tfplan
- name: Post Plan to PR
uses: actions/github-script@v6
# Post plan output as PR comment
- name: Terraform Apply (main branch only)
if: github.ref == 'refs/heads/main'
run: terraform apply tfplan
Tools: Atlantis (PR-based workflows), Terraform Cloud, Spacelift
→ See CI/CD Pipelines