Terraform
Terraform is an Infrastructure as Code (IaC) tool by HashiCorp used to define, provision, and manage infrastructure across cloud providers (AWS, Azure, GCP, etc.) using a declarative configuration language called HCL (HashiCorp Configuration Language).
- Declarative: You describe the desired end state; Terraform figures out how to get there.
- Provider-agnostic: Same workflow across AWS, Azure, GCP, on-prem, SaaS.
- State-driven: Terraform tracks real-world resources in a state file to know what exists and what needs to change.
2. Terraform Workflow
flowchart LR
A[Write HCL Config] --> B[terraform init]
B --> C[terraform plan]
C --> D[terraform apply]
D --> E[Resources Provisioned]
E --> F[terraform state]
E --> G[terraform destroy]
| Command | Purpose |
|---|---|
terraform init |
Initializes working directory, downloads providers/modules, configures backend |
terraform fmt |
Formats .tf files to canonical style |
terraform validate |
Checks configuration syntax and internal consistency |
terraform plan |
Shows execution plan — what will be created/changed/destroyed |
terraform apply |
Applies the plan and provisions/updates real infrastructure |
terraform destroy |
Tears down all resources managed by the configuration |
terraform state |
Inspect/modify the state file (list, show, mv, rm) |
terraform import |
Brings existing infrastructure under Terraform management |
terraform taint / -replace |
Forces a resource to be destroyed and recreated on next apply |
terraform output |
Displays output values |
terraform workspace |
Manages multiple state environments within one configuration |
3. Key Concepts
3.1 Providers
Plugins that let Terraform interact with an API (AWS, Azure, GCP, Kubernetes, etc.).
provider "aws" {
region = "us-east-1"
}
3.2 Resources
The fundamental building block — describes an infrastructure object to be created and managed.
resource "aws_instance" "web" {
ami = "ami-0abcdef1234567890"
instance_type = "t2.micro"
}
3.3 Data Sources
Fetch read-only information from a provider (existing infra not managed by this config).
data "aws_ami" "latest_amazon_linux" {
most_recent = true
owners = ["amazon"]
}
3.4 Arguments & Attributes
- Arguments: Inputs you set inside a resource/data block (e.g.,
ami,instance_type). - Attributes: Values exported by a resource after creation, referenced as
resource_type.name.attribute(e.g.,aws_instance.web.public_ip).
3.5 Variables
Parameterize configuration for reusability.
variable "instance_type" {
type = string
default = "t2.micro"
}
Assigned via .tfvars files, CLI flags (-var), environment variables (TF_VAR_name), or defaults.
3.6 Outputs
Expose values after apply — useful for chaining modules or displaying key info.
output "instance_ip" {
value = aws_instance.web.public_ip
}
3.7 State
Terraform maintains a JSON file (terraform.tfstate) mapping configuration to real-world resource IDs. It’s the source of truth for diffing during plan.
- Local state: Stored on disk — fine for individual/learning use.
- Remote state: Stored in a shared backend (S3, Azure Storage, Terraform Cloud) — required for team collaboration.
- State locking: Prevents concurrent modifications (e.g., DynamoDB for AWS, blob leases for Azure).
3.8 Backend
Defines where state is stored.
terraform {
backend "s3" {
bucket = "my-tf-state-bucket"
key = "prod/network/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "tf-lock-table"
}
}
3.9 Modules
Reusable, self-contained packages of Terraform configuration.
- Root module: The main working directory.
- Child module: Called from root (or another module) via a
moduleblock.
module "vpc" {
source = "./modules/vpc"
cidr_block = "10.0.0.0/16"
}
Sources can be local paths, the Terraform Registry, Git repos, or S3.
3.10 Provisioners
Execute scripts/commands on a resource during creation or destruction (local-exec, remote-exec). Considered a last resort — prefer native provider features or config management tools (Ansible) where possible.
3.11 Meta-Arguments
count— create multiple instances of a resource from a single block.for_each— create multiple instances keyed by a map/set (preferred overcountwhen items need stable identity).depends_on— explicit dependency when Terraform can’t infer it.lifecycle— controls behavior (create_before_destroy,prevent_destroy,ignore_changes).
3.12 Expressions & Functions
- Interpolation:
"${var.name}"(implicit in modern HCL for most cases). - Conditional expressions:
var.env == "prod" ? "m5.large" : "t2.micro" - Built-in functions:
length(),lookup(),merge(),join(),split(),file(),templatefile(),jsonencode(),cidrsubnet(), etc.
3.13 Workspaces
Allow multiple state files (e.g., dev/staging/prod) from the same configuration.
terraform workspace new dev
terraform workspace select dev
3.14 Terraform Datatypes
- Primitive:
string,number,bool - Collection:
list,map,set - Structural:
object,tuple
4. Real-Time Tasks (Hands-On Scenarios)
Task 1 — Provision a VPC with Public/Private Subnets
- Define
aws_vpc,aws_subnet(public + private),aws_internet_gateway,aws_nat_gateway, and route tables. - Use
countorfor_eachto loop across multiple Availability Zones. - Run
terraform planto review the subnet CIDR allocation before apply.
Task 2 — Launch an EC2 Instance with a Dynamic AMI
- Use a
data "aws_ami"block to always fetch the latest Amazon Linux AMI instead of hardcoding. - Pass
instance_typeas a variable with environment-specific.tfvarsfiles (dev.tfvars,prod.tfvars). - Apply with
terraform apply -var-file="dev.tfvars".
Task 3 — Configure Remote State with Locking
- Create an S3 bucket (versioning enabled) and a DynamoDB table with a
LockIDprimary key. - Add the
backend "s3"block and runterraform initto migrate local state to remote. - Verify locking by running
applyfrom two terminals — the second should wait/fail on the lock.
Task 4 — Build and Consume a Reusable Module
- Create a
modules/ec2-instancedirectory with its ownvariables.tf,main.tf,outputs.tf. - Call it from root with different variable values for multiple environments.
- Version-pin a public registry module, e.g.,
source = "terraform-aws-modules/vpc/aws",version = "~> 5.0".
Task 5 — Manage Multiple Environments with Workspaces
terraform workspace new dev/staging/prod.- Reference
terraform.workspaceinside config to adjust naming or sizing per environment. - Confirm each workspace has its own isolated state via
terraform workspace listandterraform state list.
Task 6 — Import Existing (Manually Created) Infrastructure
- Write a resource block matching an existing AWS resource (e.g., an S3 bucket created via console).
- Run
terraform import aws_s3_bucket.existing my-existing-bucket-name. - Run
terraform planand reconcile any drift shown between actual config and imported state.
Task 7 — Handle Configuration Drift
- Manually change a resource attribute in the cloud console (e.g., resize an EC2 instance).
- Run
terraform plan— Terraform detects the drift and shows the corrective diff. - Decide:
applyto revert to code-defined state, or update code +ignore_changesif the manual change should persist.
Task 8 — Zero-Downtime Resource Replacement
- Add
lifecycle { create_before_destroy = true }to a resource such as a launch template or AMI-based instance. - Change an immutable attribute (e.g., AMI ID) and apply — Terraform creates the new resource before destroying the old one.
Task 9 — Secure Secrets in Terraform
- Avoid hardcoding credentials/secrets in
.tfor.tfvarsfiles. - Use environment variables (
TF_VAR_db_password), a secrets manager (AWS Secrets Manager/SSM Parameter Store, Azure Key Vault) viadatasources, or a.tfvarsfile excluded via.gitignore. - Mark sensitive variables/outputs with
sensitive = trueto suppress CLI/log exposure.
Task 10 — CI/CD Integration (GitHub Actions Example)
- On pull request: run
terraform fmt -check,terraform validate, andterraform plan, posting the plan as a PR comment. - On merge to main: run
terraform apply -auto-approveusing a remote backend and least-privilege CI credentials. - Store state remotely so the pipeline and local developers always see the same state.
Task 11 — Refactor State Without Destroying Resources
- Renamed a resource or moved it into a module in code.
- Use
terraform state mv <old_address> <new_address>(ormovedblocks in newer Terraform versions) so Terraform doesn’t destroy/recreate the resource. - Confirm with
terraform plan— expect “no changes.”
Task 12 — Multi-Cloud Setup (AWS + Azure in One Config)
- Declare both
provider "aws"andprovider "azurerm"blocks. - Provision a VPC/VNet pair and a VPN or peering connection between them.
- Use
outputblocks to pass connection details (like gateway IPs) between the two provider resource sets.
